Yama Habib's Webpage

CS 133 Final Project: Snake


Final Project Presentation
Code Documentation
Snake.java
ScoreQueue.java
ScoreKeeper.java
Coord.java
Score.java
FrameThread.java
ReplayThread.java

The following is an interactive HTML version of the source code for my Snake program

import java.util.Scanner;
import java.applet.AudioClip;
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.GridLayout;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.ImageIcon;
import javax.swing.JApplet;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
/**
 * Arena - The first (and perhaps the only) stage for Snake.
 * @author Yama H
 * @version 1.0
 */
public class Arena extends JApplet implements KeyListener
{
    // Made for an 800x600 applet
    /**
     * Constant for declaring that the image has loaded successfully.
     */
    public static final int IMAGE_LOAD = 39;
    /**
     * Number of grids in panel.
     */
    public static final int SIZE = 30;
    /**
     * Amount of time in between frames.
     */

    public static final long PAUSETIME = 100;
    /**
     * Number of times to recurse certain operations before giving up and failing.
     */

    public static final int RECURSIVE_LIMIT = 10;
    protected static Coord[] choices; // array of Coords
    protected static JLabel[][] arena; // double-array of JLabels
    protected static ScoreQueue hiScores; // heap-implemented priority queue of Scores
    protected static Snake david; // protected in case I feel like making a level 2 Arena
    protected static int chIndex = 0, topScore = 0;
    protected static boolean gameOver, sound = true;
    protected static ImageIcon blankIcon, snakeIcon, heartIcon, titleIcon;
    protected static Container cont;
    protected static String player;
    protected static AudioClip collect;
    protected int x,y;
    protected boolean pressToStart, pause;
    protected Image blank, snake, heart, title;
    protected JPanel arenaPanel;
    protected Scanner scan;
    protected Toolkit toolkit;
    protected FrameThread frameThread;
    protected ReplayThread replayThread;
    protected ScoreKeeper sc = new ScoreKeeper();
    /**
     * init() called automatically by JApplet
     */

    public void init()
    {
        gameOver = false;
        showStatus("Press any key to begin, F1 to pause, F2 to restart, F3 to view Wall of Fame, F4 to toggle sound");
        hiScores = new ScoreQueue();
        player = JOptionPane.showInputDialog("Please enter your name:");
        scan = new Scanner(player);
        if(scan.hasNext())
        {
            player = scan.next();
        }
        else
        {
            player = "Anonymous";
        }
        if(!sc.notSet()) // kind of a double negative, but basically if the scores have already been saved
            hiScores = sc.getScores();
        else // add in built-in top scores
        {
            hiScores.add(new Score(60, "Birdo"));
            hiScores.add(new Score(1000, "Luigi"));
            hiScores.add(new Score(2000, "Toad"));
            hiScores.add(new Score(3000, "Peach"));
            hiScores.add(new Score(4000, "Mario"));
            hiScores.add(new Score(5000, "Jack"));
            hiScores.add(new Score(6000, "David"));
            hiScores.add(new Score(7000, "Queen"));
            hiScores.add(new Score(8000, "King"));
            hiScores.add(new Score(10000, "Ace"));
            sc.saveScores(hiScores);
        }
        chIndex = 0;
        frameThread = new FrameThread();
        x = SIZE/2;
        y = SIZE/2;
        setFocusable(true);
        addKeyListener(this);
        cont = getContentPane();
        cont.setFocusable(true);
        cont.addKeyListener(this);
        cont.setLayout(new BorderLayout());
        arenaPanel = new JPanel(new GridLayout(SIZE,SIZE));
        arena = new JLabel[SIZE][SIZE];
        choices = new Coord[SIZE*SIZE];
        toolkit = getToolkit();
        collect = getAudioClip(getClass().getResource("collect.wav"));
        title = toolkit.getImage(getClass().getResource("title.jpeg"));
        blank = toolkit.getImage(getClass().getResource("blank.gif"));
        snake = toolkit.getImage(getClass().getResource("snake.gif"));
        heart = toolkit.getImage(getClass().getResource("heart.gif"));
        toolkit.prepareImage(title, -1, -1, this);
        toolkit.prepareImage(blank, -1, -1, this);
        toolkit.prepareImage(snake, -1, -1, this);
        toolkit.prepareImage(heart, -1, -1, this);
        // make sure the images load properly before doing anything else
        while(!(toolkit.checkImage(title, -1, -1, this) == IMAGE_LOAD &&
                toolkit.checkImage(blank, -1, -1, this) == IMAGE_LOAD &&
                toolkit.checkImage(snake, -1, -1, this) == IMAGE_LOAD &&
                toolkit.checkImage(heart, -1, -1, this) == IMAGE_LOAD))
        {
            pause(PAUSETIME);
        }
        blankIcon = new ImageIcon(blank);
        snakeIcon = new ImageIcon(snake);
        heartIcon = new ImageIcon(heart);
        titleIcon = new ImageIcon(title);
        for(int i=0; i<SIZE; i++)
        {
            for(int j=0; j<SIZE; j++)
            {
                arena[j][i] = new JLabel(blankIcon);
                arenaPanel.add(arena[j][i]);
                choices[chIndex++] = new Coord(j,i);
            }
        }
        setRandomHeart();
        cont.add(new JLabel(titleIcon), BorderLayout.WEST);
        cont.add(arenaPanel, BorderLayout.EAST);
        david = new Snake(x, y, snakeIcon, heartIcon, SIZE);
        david.move();
        validate();
        repaint();
        cont.requestFocus();
        pressToStart = false;
    }
    /**
     * called for every cycle of the game
     */

    public void frame()
    {
        if(pressToStart) david.move();
        arenaPanel.repaint();
        if(pressToStart) showStatus("Top Score: " + topScore
                + ", Current Score: " + david.score());
    }
    /**
     * erases a tile and adds it to the list of blank tiles
     * @param c coordinates of tile
     */

    public static void eraseTile(Coord c)
    {
        arena[c.getX()][c.getY()].setIcon(blankIcon);
        choices[chIndex++] = c;
    }
    /**
     * sets a "tile" to the given image and removes it from list of blank tiles
     * (O(n)| n = no. of free tiles)
     * @param icon Image to set to this tile
     * @param a X coordinate of tile
     * @param b Y coordinate of tile
     */

    public static void setTile(ImageIcon icon, int a, int b)
    {
        // if a snakeIcon wants to be drawn off the screen or on top of another snake icon
        if(a < 0 || a >= SIZE || b < 0 || b >= SIZE || (icon.equals(snakeIcon) && arena[a][b].getIcon().equals(icon)))
        {
            gameOver(false);
        }
        else
        {
            // if a snakeIcon wants to be drawn on a heartIcon
            if(icon.equals(snakeIcon) && arena[a][b].getIcon()v.equals(heartIcon))
            {
                collect.stop();
                if(sound) collect.play();
                david.incScore();
                setRandomHeart();
            }
            arena[a][b].setIcon(icon);
            for(int i=0; i<chIndex; i++)
            {
                if(choices[i].getX() == a && choices[i].getY() == b)
                {
                    choices[i] = choices[chIndex-1];
                    choices[--chIndex] = null;
                    return;
                }
            }
        }
    }
    /**
     * Not used
     */

    public void keyTyped(KeyEvent key){}
    /**
     * listens to pressed keys and tells the snake to move accordingly
     * @param key the key pressed
     */

    public void keyPressed(KeyEvent key)
    {
        switch(key.getKeyCode())
        {
        case KeyEvent.VK_UP:
            david.changeDir("UP");
            break;
        case KeyEvent.VK_LEFT:
            david.changeDir("LEFT");
            break;
        case KeyEvent.VK_RIGHT:
            david.changeDir("RIGHT");
            break;
        case KeyEvent.VK_DOWN:
            david.changeDir("DOWN");
            break;
        default:
            break;
        }
    }
    /**
     * listens to released keys and uses them as options
     * @param key the key released
     */

    public void keyReleased(KeyEvent key)
    {
        if (!pressToStart)
        {
            pressToStart = true;
            frameThread.start(this);
        }
        else if(key.getKeyCode() == KeyEvent.VK_F1) // pause
        {
            if(frameThread.isAlive())
            {
                pause = true;
                JOptionPane.showMessageDialog(cont, "PAUSED");
                cont.requestFocus();
                pause = false;
            }
        }
        else if(key.getKeyCode() == KeyEvent.VK_F2) // restart
        {
            gameOver = true;
            cont.removeAll();
            cont.removeKeyListener(this);
            removeKeyListener(this);
            stop();
            destroy();
            arenaPanel.repaint();
            replayThread = new ReplayThread();
            replayThread.start(this);
        }
        else if(key.getKeyCode() == KeyEvent.VK_F3) // wall of fame
        {
            pause = true;
            String message = "";
            ScoreQueue tmp = hiScores.duplicate();
            int place = 1;
            while(!tmp.isEmpty() && place <= 10)
            {
                message += place + ": " + tmp.peek().getName() + " - " + tmp.poll().getScoreInt() + "\n";
                place++;
            }
            JOptionPane.showMessageDialog(cont, message, "The Wall of Fame",
                    JOptionPane.PLAIN_MESSAGE);
            cont.requestFocus();
            pause = false;
        }
        else if(key.getKeyCode() == KeyEvent.VK_F4) // toggle sound
        {
            showStatus("Sound: " + !sound);
            if(sound) sound = false;
            else sound = true;
        }
        else if(key.getKeyCode() == KeyEvent.VK_F5) // just in case anybody doubts that I made this
        {
            pause = true;
            JOptionPane.showMessageDialog(cont, "Snake v1.0\n\nMade by Yama D. Habib",
                    "Credits", JOptionPane.PLAIN_MESSAGE);
            cont.requestFocus();
            pause = false;
        }
    }
    /**
     * assigns a heart to a tile at random and removes the tile from the list of blank tiles
     */

    public static void setRandomHeart()
    {
        if(chIndex <= 0)
        {
            gameOver(true);
        }
        else
        {
            int i = (int)(Math.random()*chIndex);
            int a = choices[i].getX();
            int b = choices[i].getY();
            choices[i] = choices[chIndex-1];
            choices[--chIndex] = null;
            arena[a][b].setIcon(heartIcon);
        }
    }
    /**
     * ends the game
     * @param win true if the game was won (unlikely), and false if not
     * @param score the player's final score
     */

    public static void gameOver(boolean win)
    {
        gameOver = true;
        if(win)
        {
            JOptionPane.showMessageDialog(cont, "Congratulations! You win! \nYour Score: " + david.score(),
                    "Wow...", JOptionPane.PLAIN_MESSAGE);
            if(david.score() < hiScores.peek().getScoreInt())
            {
                JOptionPane.showMessageDialog(cont, "Whoa!! A new record!!!", "Awesome Stuff!",
                        JOptionPane.PLAIN_MESSAGE);
            }
            if(david.score() < topScore)
            {
                topScore = david.score();
            }
            hiScores.add(new Score(david.score(), player));
            JOptionPane.showMessageDialog(cont, "Press F2 to play again, F3 to view high scores",
                    "You know you want to...", JOptionPane.INFORMATION_MESSAGE);
            cont.requestFocus();
        }
        else
        {
            JOptionPane.showMessageDialog(cont, "Game over! \nYour Score: " + david.score(),
                    "Too bad...", JOptionPane.PLAIN_MESSAGE);
            if(david.score() < hiScores.peek().getScoreInt())
            {
                JOptionPane.showMessageDialog(cont, "Whoa!! A new record!!!", "Awesome Stuff!",
                        JOptionPane.PLAIN_MESSAGE);
            }
            if(david.score() < topScore)
            {
                topScore = david.score();
            }
            hiScores.add(new Score(david.score(), player));
            JOptionPane.showMessageDialog(cont, "Press F2 to play again, F3 to view high scores",
                    "You know you want to...", JOptionPane.INFORMATION_MESSAGE);
            cont.requestFocus();
        }
    }
    /**
     * Pauses the thread temporarily. Makes several more attempts if pausing fails.
     * @param time time to pause for
     * @return whether or not the Thread paused successfully
     */

    public boolean pause(long time)
    {
        return recursivePause(time, RECURSIVE_LIMIT);
    }
    protected boolean recursivePause(long time, int tries)
    {
        if(tries != 0)
        {
            try
            {
                Thread.sleep(time);
                return true;
            }
            catch(Exception ignored)
            {
                JOptionPane.showMessageDialog(cont, ignored.toString());
                recursivePause(time, tries-1);
            }
        }
        return false;
    }
}