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;
}
}