Wordy.java

Denna kod är public domain. Om ni hittar fel eller vill ändra något i koden blir jag jätteglad om ni skickar dessa ändringar till jesper [at] fantasi [punkt] se.


/**
 * This is the game Wordy. The computer thinks of a random five letter word,
 * and your task is to guess it. To begin with, the first letter in the word is
 * revealed and as you guess more letters will become visible.
 * Letters in your guess that is in the right place, is copied down to the
 * next line. Letters that should be in the word, but is in the wrong place,
 * is marked with a circle.
 *
 * ToDo:
 *  Sound
 *  The bingo part
 *  Timeout
 *  Språkval bör inte köra introt om man är på titelsidan.
 *  Tio ord, sedan medelpoäng och avslut.
 **/

import javax.swing.*;
import javax.swing.border.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.io.*;

//public class Wordy extends JApplet implements ActionListener, ItemListener
public class Wordy extends JFrame implements ActionListener, ItemListener
{
	public static final long serialVersionUID = 1;
	
    public final static int FREE    = 0;
    public final static int CORRECT = 1;
    public final static int PLACE   = 2;
    public final static int WRONG   = 3;

    public final static Color C_FREE      = new Color(50,50,255);
    public final static Color C_FREE_L    = new Color(100,100,255);
    public final static Color C_FREE_D    = new Color(0,0,150);
    public final static Color C_CORRECT   = new Color(50,200,50);
    public final static Color C_CORRECT_L = new Color(80,230,80);
    public final static Color C_CORRECT_D = new Color(0,150,0);
    public final static Color C_PLACE     = new Color(230,230,150);
    public final static Color C_WRONG     = new Color(0,0,200);
    public final static Color C_WRONG_L   = new Color(50,50,250);
    public final static Color C_WRONG_D   = new Color(0,0,150);
    public final static Color C_TEXT      = new Color(255,255,255);
    public final static Color C_TYPE      = new Color(0,0,0);

    public final static ImageIcon[] ASCII = new ImageIcon[30];

    private Box[][] playfield = new Box[5][5];
    private boolean[] bits = new boolean[5];
    private int line;
    private int pos;
    private int wordPoints;
    private String word;
    private JLabel score;
    private JLabel messageBox;
    private JButton button;
    private Choice dictionary;
    private HashSet selectableWords;
    private HashSet otherWords;
    private boolean wordsChanged;
    private boolean textlock;

    public Wordy()
    {
        /**
         * Tell the applet to ignore the system event queue to avoid
         * error messages in JDK 1.1 and JFC 1.1
         */
        getRootPane().putClientProperty("defeatSystemEventQueueCheck", Boolean.TRUE);
    }

	public static void main(String[] args)
    {
        // This is just for debugging...
        Wordy w = new Wordy();
        w.setTitle("Wordy");
        w.init();
    }

    public void init()
    {
        Container gameContainer = getContentPane();
        gameContainer.setBackground(C_FREE);
        gameContainer.setLayout(new BorderLayout());

        JPanel game = new JPanel();
        game.setLayout(new GridLayout(5, 5));
        game.setBackground(C_FREE);

        for (int row = 0; row < 5; row++) {
            for (int col = 0; col < 5; col++) {
                game.add(playfield[row][col] = new Box());
            }
        }

        JPanel upperControl = new JPanel();
        upperControl.setLayout(new BorderLayout());
        upperControl.add(messageBox = new JLabel(" "), "Center");
        upperControl.add(dictionary = new Choice(), "East");

        JPanel control = new JPanel();
        control.setLayout(new BorderLayout());
        control.add(new JLabel(" Score: "), "West");
        control.add(score = new JLabel("0", Label.RIGHT), "Center");
        control.add(upperControl, "North");
        control.add(button = new JButton("Start"), "East");

        gameContainer.add(game, "Center");
        gameContainer.add(control, "South");

        dictionary.addItem("Svenska");
        dictionary.addItem("English");
        dictionary.select("Svenska");

        button.setEnabled(false);
        dictionary.setEnabled(false);
        button.addActionListener(this);
        dictionary.addItemListener(this);
        textlock = true;
        this.addKeyListener(new WordyKeys());

        setSize(278,330);
        setVisible(true);

        message("Loading...");
        loadLetters();
        message("Play ball!");

        newGame();
        readWords();
    }

    public void loadLetters()
    {
        for (int i = 0; i < 26; i++) {
            ASCII[i] = new ImageIcon((char)(97 + i) + ".gif");
        }
        ASCII[26] = new ImageIcon("aa.gif");
        ASCII[27] = new ImageIcon("ae.gif");
        ASCII[28] = new ImageIcon("oe.gif");
        ASCII[29] = new ImageIcon("blank.gif");
    }

    public void actionPerformed(ActionEvent e)
    {
        if (e.getSource() == button) {
            newWord();
        }
    }

    /**
     * Called when someone selects a language from the choser.
     */
    public void itemStateChanged(ItemEvent e)
    {
        newGame();
        readWords();
    }

    /**
     * Clear the playfield and display the first letter in a new word.
     */
    private void newWord()
    {
        for (int i = 0; i < 5; i++) {
            for (int j = 0; j < 5; j++) {
                playfield[i][j].clear();
                playfield[i][j].repaint();
            }
            bits[i] = false;
        }
        selectWord();
        bits[0] = true;
        playfield[0][0].setForeground(C_TEXT);
        playfield[0][0].setText(word.charAt(0));
        playfield[0][0].repaint();
        line = 0;
        pos = 0;
        textlock = false;
        wordPoints = 500;
        message(" ");
        button.setEnabled(false);
        dictionary.setEnabled(false);
        this.requestFocus();
    }

    /**
     * Start a new game. Begin with showing the intro.
     */
    private void newGame()
    {
        textlock = true;
        button.setEnabled(false);
        dictionary.setEnabled(false);
        for (int i = 0; i < 5; i++) {
            for (int j = 0; j < 5; j++) {
                playfield[i][j].clear();
                playfield[i][j].repaint();
            }
        }
        score.setText("0");
        button.setText("Start");
        new Thread(new IntroRunner()).start();
    }

    /**
     * Read a dictionary file from disk.
     * The dictionary is assumed to only contain five letter words.
     * The first character in the file is ignored - There is one dummy
     * character (\n) before each word to tell us that another word is comming.
     * Selectable words are those that the game will chose from. Other
     * words will be considered as good guesses, but the game will never
     * pick one as the "secret" word. A line of five '-' separates
     * selectable words from other words.
     * Other words is the place to put "bad language" and words that may
     * offend pepole, like sertain body parts or sevear illneses.
     */
    private void readWords()
    {
        try {
            String filename =
                new String("wordy." + dictionary.getSelectedItem().toLowerCase() + ".dat");
            BufferedReader file = new BufferedReader(new FileReader(filename));
            HashSet words = selectableWords = new HashSet();

            int ch = file.read();
            while (ch != -1)
            {
                String txt = new String("");
                for (int i = 0; i < 5; i++)
                    txt += (char)file.read();
                if (txt.equals("-----"))
                    words = otherWords = new HashSet();
                else
                    words.add(txt.toUpperCase());
                ch = file.read();
            }
            file.close();
            wordsChanged = false;
        }
        catch (IOException e) {}
    }

    /**
     * Save the dictionary to a file.
     * The file name is "wordy.<language>.dat"
     */
    private void saveWords()
    {
        try
        {
            String filename =
                new String("wordy." + dictionary.getSelectedItem().toLowerCase() + ".dat");
            BufferedWriter file = new BufferedWriter(new FileWriter(filename));
            Iterator it = selectableWords.iterator();

            while (it.hasNext())
            {
                String txt = (String)it.next();
                file.write('\n');
                for (int i = 0; i < 5; i++)
                    file.write(txt.charAt(i));
            }

            file.write('\n');
            for (int i = 0; i < 5; i++)
                file.write('-');

            it = otherWords.iterator();
            while (it.hasNext())
            {
                String txt = (String)it.next();
                file.write('\n');
                for (int i = 0; i < 5; i++)
                    file.write(txt.charAt(i));
            }
            file.close();
            wordsChanged = false;
        }
        catch (IOException e) {}
    }

    /**
     * Pick a word from the selectable ones.
     */
    private void selectWord()
    {
        int which = (int)(Math.random() * selectableWords.size());
        Iterator it = selectableWords.iterator();

        while (which-- > 0)
        {
            it.next();
        }
        word = (String)it.next();
    }

    /**
     * Give the player some points.
     */
    private void points(int more)
    {
        int old = Integer.parseInt(score.getText());
        score.setText(old + more + "");
    }

    /**
     * Display a message in the status line.
     */
    private void message(String mess)
    {
        messageBox.setText(mess);
    }

    /**
     * Set the letter in the box currently indicated by line and pos.
     */
    private void setLetter(char ch)
    {
        playfield[line][pos].setForeground(C_TYPE);
        playfield[line][pos].setText(Character.toUpperCase(ch));
        playfield[line][pos].repaint();
        pos++;
        if (pos == 5)
        {
            textlock = true;
            String tmp = playfield[line][0].getChar() +
                         playfield[line][1].getChar() +
                         playfield[line][2].getChar() +
                         playfield[line][3].getChar() +
                         playfield[line][4].getChar();
            new Thread(new GuessRunner(tmp.toUpperCase())).start();
        }
    }

    /**
     * Remove last letter written.
     */
    private void delLetter()
    {
        if (pos > 0)
        {
            pos--;
            if (bits[pos])
            {
                playfield[line][pos].setForeground(C_TEXT);
                playfield[line][pos].setText(word.charAt(pos));
            }
            else
                playfield[line][pos].setText(' ');

            playfield[line][pos].repaint();
        }
    }

    /**
     * Compare user guess with the secret word.
     */
    private void guess(String user) throws InterruptedException
    {
        /**
         * If the word is not in the dictionary, we ask the user if it
         * is a correct word (hopefully the user will answer truthfully).
         */
        if (!(selectableWords.contains(user) || otherWords.contains(user)))
        {
            System.out.println("Is " + user + " a valid word?");
            try { 
                char ch = (char)System.in.read();
                while (System.in.read() != '\n');
                if (ch != 'n')
                {
                    selectableWords.add(user);
                    wordsChanged = true;
                    System.out.println("Added " + user + " to the dictionary");
                }
                else
                    System.out.println("Why did you write it then?");
            }
            catch (IOException ioe) {}
        }

        /**
         * If the word is in the dictionary now, we send it to checkWord.
         * When checkWord say 'OK', all's well. The user is politely asked
         * if the new words from this session is to be saved for real.
         * If the guess is wrong we step down one line. If this was the
         * last line, the playfield is moved up one line and a new letter
         * is revealed to the user. (Not the last free letter though.)
         */
        if ((selectableWords.contains(user) || otherWords.contains(user)) && checkWord(user))
        {
            message("That's right!");
            points(wordPoints);

            if (wordsChanged)
            {
                System.out.println("Do you which to save new words?");
                try {
                    char ch = (char)System.in.read();
                    while (System.in.read() != '\n');
                    if (ch != 'n')
                    {
                        saveWords();
                        System.out.println("Saved words");
                    }
                }
                catch (IOException ioe) {}
            }
            button.setText("Next Word");
            button.setEnabled(true);
            dictionary.setEnabled(true);
        }
        else
        {
            line++;
            pos = 0;
            if (line == 5)
            {
                int correct = 0;
                line = 4;
                wordPoints /= 2;
                for (int i = 0; i < 4; i++)
                    for (int j = 0; j < 5; j++)
                        playfield[i][j].copy(playfield[i + 1][j]);

                for (int i = 0; i < 5; i++)
                {
                    playfield[4][i].clear();
                    if (bits[i])
                        correct++;
                }
                repaint();

                if (correct < 4)
                {
                    /**
                     * Reveal another letter to the user
                     */
                    for (int i = 0; i < 5; i++)
                        if (!bits[i])
                        {
                            bits[i] = true;
                            break;
                        }
                }
            }
            else
                wordPoints -= 100;

            for (int i = 0; i < 5; i++)
            {
                if (bits[i])
                {
                    playfield[line][i].setForeground(C_TEXT);
                    playfield[line][i].setText(word.charAt(i));
                    playfield[line][i].repaint();
                }
            }
            textlock = false;
        }
    }

    /**
     * Check if the word is correct.
     */
    private boolean checkWord(String guess) throws InterruptedException
    {
        HashSet letters = new HashSet();
        int[] stats = new int[5];
        int correct = 0;

        /**
         * We need to have all the letters to be able to see if one
         * is in the wrong place.
         */
        for (int i = 0; i < 5; i++)
        {
            letters.add(new Character(word.charAt(i)));
            stats[i] = FREE;
        }

        for (int i = 0; i < 5; i++)
        {
            char ch = Character.toUpperCase(guess.charAt(i));
            if (ch == word.charAt(i))
            {
                stats[i] = CORRECT;
                bits[i] = true;
                correct++;
                letters.remove(new Character(ch));
            }
            else
                stats[i] = WRONG;
        }

        for (int i = 0; i < 5; i++)
        {
            char ch = Character.toUpperCase(guess.charAt(i));
            if (letters.contains(new Character(ch)))
            {
                stats[i] = PLACE;
                letters.remove(new Character(ch));
            }
        }

        Thread.sleep(1000);

        for (int i = 0; i < 5; i++)
        {
            playfield[line][i].setStatus(stats[i]);
            playfield[line][i].repaint();
            Thread.sleep(300);
        }
        return correct == 5;
    }

    /**
     * This class runs the blopp-thread that shows the guess to the user.
     */
    private class GuessRunner implements Runnable
    {
        private String aWord;

        public GuessRunner(String newWord)
        {
            aWord = newWord;
        }

        public void run()
        {
            try {
                guess(aWord);
            }
            catch (InterruptedException e) {}
            System.out.println("Guess runner done");
        }

        public void finalize()
        {
        	System.out.println("Guess runner finalizer");
        }
    }

    /**
     * This class runs the Wordy intro.
     */
    private class IntroRunner implements Runnable
    {
        public void run()
        {
            try {
                Thread.sleep(500);
                playfield[2][0].setForeground(Color.yellow);
                playfield[2][0].setText('W');
                playfield[2][0].repaint();
                Thread.sleep(500);
                playfield[2][1].setForeground(Color.yellow);
                playfield[2][1].setText('O');
                playfield[2][1].repaint();
                Thread.sleep(500);
                playfield[2][2].setForeground(Color.yellow);
                playfield[2][2].setText('R');
                playfield[2][2].repaint();
                Thread.sleep(500);
                playfield[2][3].setForeground(Color.yellow);
                playfield[2][3].setText('D');
                playfield[2][3].repaint();
                Thread.sleep(500);
                playfield[2][4].setForeground(Color.yellow);
                playfield[2][4].setText('Y');
                playfield[2][4].repaint();
                Thread.sleep(1000);
                for (int i = 0; i < 5; i++)
                {
                    playfield[2][i].setStatus(CORRECT);
                    playfield[2][i].setForeground(C_TYPE);
                    playfield[2][i].repaint();
                    Thread.sleep(300);
                }
            }
            catch (InterruptedException e) {}
            button.setEnabled(true);
            dictionary.setEnabled(true);
            System.out.println("Intro runner done");
        }

        protected void finalize()
        {
        	System.out.println("Intro runner finalizer");
        }
    }

    /**
     * Listen to the keyboard...
     */
    private class WordyKeys extends KeyAdapter
    {
        public void keyTyped( KeyEvent e )
        {
            if (!textlock)
            {
                if ((int)e.getKeyChar() == 8)
                    delLetter();
                else
                    setLetter(e.getKeyChar());
            }
        }
    }
}

/**
 * A box...
 */
class Box extends JLabel
{
	public static final long serialVersionUID = 1;
    private int status = Wordy.FREE;
    private char ch = ' ';

    public Box()
    {
        super();
        setForeground(Wordy.C_TEXT);
        setBorder(new BevelBorder(BevelBorder.LOWERED, Wordy.C_FREE_L, Wordy.C_FREE_D));
        setHorizontalAlignment(SwingConstants.CENTER);
    }

    public String getChar()
    {
        return "" + ch;
    }

    public void setText(char g)
    {
        ch = g;
        switch (g)
        {
            case 'Å': setIcon(Wordy.ASCII[26]); break;
            case 'Ä': setIcon(Wordy.ASCII[27]); break;
            case 'Ö': setIcon(Wordy.ASCII[28]); break;
            case ' ': setIcon(Wordy.ASCII[29]); break;
            default: setIcon(Wordy.ASCII[(int)g - 65]);
        }
    }

    public void setStatus(int s)
    {
        status = s;
    }

    public void clear()
    {
        setText(' ');
        status = Wordy.FREE;
        setBorder(new BevelBorder(BevelBorder.LOWERED, Wordy.C_FREE_L, Wordy.C_FREE_D));
    }

    public void copy(Box orig)
    {
        status = orig.status;
        setBorder(orig.getBorder());
        setIcon(orig.getIcon());
    }

    public void paint(Graphics g)
    {
        switch (status)
        {
            case Wordy.FREE:
                break;
            case Wordy.CORRECT:
                setBorder(new BevelBorder(BevelBorder.LOWERED, Wordy.C_CORRECT_L, Wordy.C_CORRECT_D));
                g.setColor(Wordy.C_CORRECT);
                g.fillRect(0,0,size().width,size().height);
                break;
            case Wordy.PLACE:
                g.setColor(Wordy.C_PLACE);
                g.fillOval(2,2,size().width - 5,size().height - 5);
                break;
            case Wordy.WRONG:
                setBorder(new BevelBorder(BevelBorder.LOWERED, Wordy.C_WRONG_L, Wordy.C_WRONG_D));
                g.setColor(Wordy.C_WRONG);
                g.fillRect(0,0,size().width,size().height);
                break;
        }
        setFont(new Font("Helvetica",Font.BOLD, (int)Math.min(size().width * 0.9, size().height * 0.9)));
        super.paint(g);
    }
}