/** ---------------------------------------------- Snake @author Kyle Florence @version Sunday, May 02, 2004 ---------------------------------------------- Special thanks to: {aaron} on freenode's #java http://brainjar.com caffeine ---------------------------------------------- keys: UP - moves the snake up DOWN - moves the snake down LEFT - moves the snake left RIGHT - moves the snake right S - starts a new round / game P - pauses the game ---------------------------------------------- todo: Sound's? Level's? Bonus's? ---------------------------------------------- **/ import java.awt.*; import java.applet.*; import java.awt.event.*; import java.util.*; public class SnakePanel extends Applet implements Runnable, KeyListener { // Environment Variables static final int DELAY = 15; static final int DEBUGDELAY = 100; static final int RADIUS = 5; static final int CELL_WIDTH = RADIUS * 2; static final int WIDTH = 250; static final int HEIGHT = 250; // Directions static final int NONE = 0; static final int UP = 1; static final int DOWN = 2; static final int LEFT = 3; static final int RIGHT = 4; // Game States static final int PLAY = 5; static final int OVER = 6; static final int START = 7; static final int DEAD = 8; // Lives static final int LIVES = 3; // Colors static final Color bgColor = new Color(21, 42, 89); static final Color gmPlay = new Color(204, 204, 204); static final Color fgColor = new Color(255, 255, 255); static final Color grid = new Color(195, 195, 195); static final Color food = new Color(0, 0, 0); static final Color snake = new Color(96, 98, 102); static final Color tailcolor = new Color(255, 0, 0); static final Color slackcolor = new Color(255, 0, 255); // Segment Class private static class Segment { public int direction = NONE; public int x, y; public Segment(int direction, int x, int y) { this.direction = direction; this.x = x; this.y = y; } } int gameState; Thread loopThread; // Snake-Related Variables int x1; int y1; int x2; int y2; int dx; int dy; int score; int high; int lives; int distance; int delay; // Direction Variables int direction = NONE; int newdirection = NONE; int prevDirection = NONE; int slackdir = NONE; // Boolean Variables boolean paused = false; boolean debug = false; boolean keyLock = false; // Off screen buffer. Dimension offDimension; Image offImage; Graphics offGraphics; // Font Font font = new Font("Arial", Font.PLAIN, 12); FontMetrics fm = getFontMetrics(font); int fontWidth = fm.getMaxAdvance(); int fontHeight = fm.getHeight(); // Tail LinkedList tail = new LinkedList(); public void init() { addKeyListener(this); requestFocus(); gameState = START; paused = true; lives = LIVES; delay = DELAY; create_snake(); move_food(); Dimension d = getSize(); // Off screen graphics context (brainjar.com) if (offGraphics == null || d.width != offDimension.width || d.height != offDimension.height) { offDimension = d; offImage = createImage(d.width, d.height); offGraphics = offImage.getGraphics(); } } public void start() { if (loopThread == null) { loopThread = new Thread(this); loopThread.start(); } } public void stop() { if (loopThread != null) { loopThread.stop(); loopThread = null; } } public void run() { while (Thread.currentThread() == loopThread) { if (!paused) { move_snake(); if (score > high) high = score; } repaint(); try { Thread.currentThread().sleep(debug ? DEBUGDELAY : delay); } catch (InterruptedException e) { } } } public void die() { paused = true; if (lives-- > 0) { gameState = DEAD; } else { gameState = OVER; lives = LIVES; score = 0; delay = DELAY; } tail.clear(); create_snake(); move_food(); } public void move_food() { if (tail.size() > 0) { Iterator it = tail.iterator(); while (it.hasNext()) { x2 = (int)(Math.random() * (WIDTH - 40) + 10); y2 = (int)(Math.random() * (HEIGHT - 80) + 20); x2 -= (x2 % CELL_WIDTH); y2 -= (y2 % CELL_WIDTH); Segment body = (Segment) it.next(); if(x2 != body.x && y2 != body.y) return; } } else { x2 = (int)(Math.random() * (WIDTH - 40) + 10); y2 = (int)(Math.random() * (HEIGHT - 80) + 20); x2 -= (x2 % CELL_WIDTH); y2 -= (y2 % CELL_WIDTH); } } public void create_snake() { x1 = WIDTH / 2; y1 = HEIGHT / 2; x1 += (x1 % CELL_WIDTH); y1 += (y1 % CELL_WIDTH); dx = 0; dy = 0; direction = NONE; prevDirection = NONE; } public void move_snake() { keyLock = true; int i; int pos = 0; if (direction == LEFT || direction == RIGHT) pos = x1; else pos = y1; if (pos % CELL_WIDTH == 0) { if (newdirection != NONE) { direction = newdirection; newdirection = NONE; } if (direction != NONE) { if (tail.size() > 0) { tail.addFirst(new Segment(direction, x1, y1)); Segment segment = (Segment) tail.removeLast(); slackdir = segment.direction; } } } switch (direction) { case LEFT: dx = -1; dy = 0; break; case RIGHT: dx = 1; dy = 0; break; case UP: dy = -1; dx = 0; break; case DOWN: dy = 1; dx = 0; break; } keyLock = false; prevDirection = direction; if (direction == NONE) return; // Adjust snake direction x1 += dx; y1 += dy; // Check for wall hit if(x1 >= WIDTH - ((CELL_WIDTH * 2) - 1) || x1 <= CELL_WIDTH - 1){ die(); } else if(y1 >= HEIGHT - ((CELL_WIDTH * 3) - 1) || y1 <= (CELL_WIDTH * 2) - 1){ die(); } Iterator it = tail.iterator(); // Check for body hit while (it.hasNext()) { Segment body = (Segment) it.next(); if(x1 == body.x && y1 == body.y) { die(); return; } } // Distance between snake and food distance = (int)Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2)); // Check for food hit if(distance <= (CELL_WIDTH / 2)) { score++; int dir; if (tail.size() > 0) { Segment last = (Segment) tail.getLast(); dir = last.direction; } else { dir = direction; } tail.addFirst(new Segment(dir, x1, y1)); //prevx1 prevy1 slackdir = dir; // change delay (speed) if (tail.size() % 3 == 0) delay--; move_food(); } } public void keyPressed (KeyEvent e) { char c; if (!paused && !keyLock) { // Only set direction if one hasn't been set already if (e.getKeyCode() == KeyEvent.VK_UP && prevDirection != DOWN) { if (newdirection == NONE) newdirection = UP; } else if (e.getKeyCode() == KeyEvent.VK_DOWN && prevDirection != UP) { if (newdirection == NONE) newdirection = DOWN; } else if (e.getKeyCode() == KeyEvent.VK_LEFT && prevDirection != RIGHT) { if (newdirection == NONE) newdirection = LEFT; } else if (e.getKeyCode() == KeyEvent.VK_RIGHT && prevDirection != LEFT) { if (newdirection == NONE) newdirection = RIGHT; } } c = Character.toLowerCase(e.getKeyChar()); if (c == 'p') { paused = !paused; } if (c == 's') { paused = false; gameState = PLAY; } if (c == 'd') { debug = !debug; } } public void keyTyped (KeyEvent e) { } public void keyReleased (KeyEvent e) { } public void update(Graphics g) { paint(g); } // Tail slack (thanks {aaron}) public int getSlack(int x, int y, int direction) { switch (direction) { case LEFT: return x % CELL_WIDTH; case RIGHT: { int mod = x % CELL_WIDTH; if (mod == 0) return 0; else return CELL_WIDTH - (x % CELL_WIDTH); } case UP: return y % CELL_WIDTH; case DOWN: { int mod = y % CELL_WIDTH; if (mod == 0) return 0; else return CELL_WIDTH - (y % CELL_WIDTH); } default: throw new RuntimeException("INVALID DIRECTION: " + direction); } } public void paint(Graphics g) { int i; String s; offGraphics.setColor(bgColor); offGraphics.fillRect(0, 0, WIDTH, HEIGHT); offGraphics.setColor(gmPlay); offGraphics.fillRect(10, 20, WIDTH - 20, HEIGHT - 40); // Paint Grid offGraphics.setColor(grid); for (i = 1; i < (WIDTH / CELL_WIDTH); i++) { offGraphics.drawLine((CELL_WIDTH * i) + CELL_WIDTH, CELL_WIDTH * 2, (CELL_WIDTH * i) + CELL_WIDTH, HEIGHT - (CELL_WIDTH * 2)); } for (i = 1; i < ((HEIGHT / CELL_WIDTH) - 2); i++) { offGraphics.drawLine(CELL_WIDTH, CELL_WIDTH * i + CELL_WIDTH, WIDTH - CELL_WIDTH, CELL_WIDTH * i + CELL_WIDTH); } offGraphics.setFont(font); offGraphics.setColor(fgColor); s = "Lives: " + lives + "/" + LIVES; offGraphics.drawString(s, 10, 15); s = "Highscore: " + high * 10; offGraphics.drawString(s, 4 * (WIDTH - fm.stringWidth(s)) / 4 - CELL_WIDTH, 15); s = "Score: " + score * 10; offGraphics.drawString(s, 3 * (WIDTH - fm.stringWidth(s)) / 4 - (CELL_WIDTH * 6), 15); if (gameState == OVER) { s = "Game Over. 'S' to start."; offGraphics.drawString(s, (WIDTH - fm.stringWidth(s)) / 2 + 1, HEIGHT - 5); } if (gameState == DEAD) { s = "Uh oh! You died. 'S' to restart."; offGraphics.drawString(s, (WIDTH - fm.stringWidth(s)) / 2 + 1, HEIGHT - 5); } else if (gameState == START) { s = "'S' to start."; offGraphics.drawString(s, (WIDTH - fm.stringWidth(s)) / 2 + 1, HEIGHT - 5); } else if (paused && gameState == PLAY) { s = "Paused."; offGraphics.drawString(s, (WIDTH - fm.stringWidth(s)) / 2 + 1, HEIGHT / 3 + 1); } offGraphics.setColor(snake); // Head offGraphics.fillRect(x1, y1, RADIUS * 2, RADIUS * 2); String debugstring = "(" + x1 + "," + y1 + ")"; // Tail if (debug) offGraphics.setColor(tailcolor); Iterator it = tail.iterator(); // Building the tail (thanks {aaron}) if (tail.size() > 0) { int slack = getSlack(x1, y1, direction); debugstring += " [" + slack + "]"; Segment segment = null; while (it.hasNext()) { segment = (Segment) it.next(); offGraphics.fillRect(segment.x, segment.y, RADIUS * 2, RADIUS * 2); } if (debug) offGraphics.setColor(slackcolor); switch (slackdir) { case UP: offGraphics.fillRect(segment.x, segment.y + CELL_WIDTH, CELL_WIDTH, slack); break; case DOWN: offGraphics.fillRect(segment.x, segment.y - slack, CELL_WIDTH, slack); break; case LEFT: offGraphics.fillRect(segment.x + CELL_WIDTH, segment.y, slack, CELL_WIDTH); break; case RIGHT: offGraphics.fillRect(segment.x - slack, segment.y, slack, CELL_WIDTH); break; } } if (debug) { offGraphics.setColor(fgColor); offGraphics.drawString(debugstring, (WIDTH - fm.stringWidth(s)) / 2 + 1, HEIGHT - 5); } // Food offGraphics.setColor(food); offGraphics.fillRect(x2, y2, RADIUS * 2, RADIUS * 2); // Copy the off screen buffer to the screen. g.drawImage(offImage, 0, 0, this); } }