/************************************************************************************************ SnakePit.java Usage: Keyboard Controls: S - Start Game P - Pause Game M - Mute Sound Cursor Keys - Change Direction ************************************************************************************************/ import java.awt.*; import java.net.*; import java.applet.Applet; import java.applet.AudioClip; /************************************************************************************************ Main applet code. ************************************************************************************************/ public class SnakePit extends Applet implements Runnable { // Thread control variables. Thread loopThread; Thread loadThread; // Constants static final int MAX_DELAY = 75; // Milliseconds between screen updates. static final int MIN_DELAY = 50; static final int DELAY_INCR = 5; static final int GRID_WIDTH = 39; // Size of playing field. static final int GRID_HEIGHT = 25; static final int GRID_SIZE = 16; // Grid size in pixels. static final int NUM_LEVELS = 5; // Number of levels. static final int NUM_MICE = 6; // Number of active mice. static final int NUM_LIVES = 3; // Starting number of lives. static final int INIT = 1; // Game states. static final int PLAY = 2; static final int LEVEL = 3; static final int END = 4; static final int OVER = 5; static final int END_COUNT = 30; // Counter values. static final int LEVEL_COUNT = 40; static final int TYPE_MASK = 0x00FF0000; // Grid cell types. static final int EMPTY = 0x00000000; static final int BLOCK = 0x00010000; static final int SNAKE = 0x00020000; static final int MOUSE = 0x00030000; static final int KEY = 0x00040000; static final int DIR_MASK = 0x0000FF00; // Grid cell directions. static final int NONE = 0x00000000; static final int LEFT = 0x00000100; static final int RIGHT = 0x00000200; static final int UP = 0x00000300; static final int DOWN = 0x00000400; static final int SHAPE_MASK = 0x000000FF; // Grid cell shapes. static final int SQUARE = 0x00000000; static final int SNAKEHEAD = 0x00000001; static final int SNAKEBODY = 0x00000002; static final int SNAKEELB1 = 0x00000003; static final int SNAKEELB2 = 0x00000004; static final int SNAKETAIL = 0x00000005; static final int MOUSEBODY = 0x00000006; static final int KEYSHAPE = 0x00000007; static final int MOUSE_POINTS = 10; // Scoring values. static final int LEVEL_POINTS = 200; static final int EXTRA_LIFE = 500; // Sizing data. int width; int height; // Game data. int score; // Scoring data. int highScore; int lives; int extraLife; int level; // Level data. int levelTotal; int miceNeeded; int miceEaten; int delay; int[][] grid; // Playing field. Point[] snake; // Snake data. int headPtr; int tailPtr; int direction; int lastDirection; boolean lockKeys; Point[] mouse; // Mouse data. Point key; // Key data. boolean keyActive; int gameState; // Game state data. int levelCounter; int endCounter; boolean paused; // Screen colors. Color bgColor = Color.black; Color fgColor = Color.white; Color blockColor = new Color(0, 0, 153); Color fieldColor = new Color(204, 153, 102); Color snakeColor = new Color(0, 153, 0); Color mouseColor = Color.gray; Color keyColor = new Color(204, 102, 102); // Basic shapes. Polygon snakeHead; Polygon snakeBody; Polygon snakeTail; Polygon snakeElb1; Polygon snakeElb2; Polygon mouseBody; Polygon keyShape; // Sound data. boolean loaded = false; boolean sound; AudioClip bonkSound; AudioClip munchSound; AudioClip squeakSound; AudioClip chimeSound; AudioClip advanceSound; // Values for the offscreen image. Dimension offDimension; Image offImage; Graphics offGraphics; // Font data. Font font = new Font("Helvetica", Font.BOLD, 12); FontMetrics fm; int fontWidth; int fontHeight; // Applet information. public String getAppletInfo() { return("Snake Pit, Copyright 1998 by Mike Hall."); } public void init() { Graphics g; int i; // Take credit. System.out.println("Snake Pit, Copyright 1998 by Mike Hall."); // Set font data. g = getGraphics(); g.setFont(font); fm = g.getFontMetrics(); fontWidth = fm.getMaxAdvance(); fontHeight = fm.getHeight(); // Define the playing grid. grid = new int[GRID_WIDTH][GRID_HEIGHT]; // Initialize basic shapes. snakeHead = new Polygon(); snakeHead.addPoint(4, 15); snakeHead.addPoint(3, 14); snakeHead.addPoint(1, 12); snakeHead.addPoint(1, 8); snakeHead.addPoint(3, 6); snakeHead.addPoint(3, 4); snakeHead.addPoint(6, 1); snakeHead.addPoint(9, 1); snakeHead.addPoint(12, 4); snakeHead.addPoint(12, 6); snakeHead.addPoint(14, 8); snakeHead.addPoint(14, 12); snakeHead.addPoint(12, 14); snakeHead.addPoint(11, 15); snakeBody = new Polygon(); snakeBody.addPoint(11, 0); snakeBody.addPoint(11, 15); snakeBody.addPoint(4, 15); snakeBody.addPoint(4, 0); snakeElb1 = new Polygon(); snakeElb1.addPoint(11, 0); snakeElb1.addPoint(11, 4); snakeElb1.addPoint(15, 4); snakeElb1.addPoint(15, 11); snakeElb1.addPoint(7, 11); snakeElb1.addPoint(4, 8); snakeElb1.addPoint(4, 0); snakeElb2 = mirror(snakeElb1); snakeTail = new Polygon(); snakeTail.addPoint(11, 0); snakeTail.addPoint(8, 15); snakeTail.addPoint(7, 15); snakeTail.addPoint(4, 0); mouseBody = new Polygon(); mouseBody.addPoint(8, 1); mouseBody.addPoint(12, 5); mouseBody.addPoint(12, 6); mouseBody.addPoint(10, 7); mouseBody.addPoint(12, 9); mouseBody.addPoint(12, 12); mouseBody.addPoint(10, 14); mouseBody.addPoint(5, 14); mouseBody.addPoint(3, 12); mouseBody.addPoint(3, 9); mouseBody.addPoint(5, 7); mouseBody.addPoint(3, 6); mouseBody.addPoint(3, 5); mouseBody.addPoint(7, 1); keyShape = new Polygon(); keyShape.addPoint(1, 6); keyShape.addPoint(7, 6); keyShape.addPoint(9, 4); keyShape.addPoint(13, 4); keyShape.addPoint(15, 6); keyShape.addPoint(15, 10); keyShape.addPoint(13, 12); keyShape.addPoint(9, 12); keyShape.addPoint(7, 10); keyShape.addPoint(3, 10); keyShape.addPoint(3, 11); keyShape.addPoint(0, 11); keyShape.addPoint(0, 7); // Initialize data. highScore = 0; sound = true; snake = new Point[GRID_WIDTH * GRID_HEIGHT]; for (i = 0; i < snake.length; i++) snake[i] = new Point(-1, -1); mouse = new Point[NUM_MICE]; for (i = 0; i < NUM_MICE; i++) mouse[i] = new Point(-1, -1); key = new Point(-1, -1); lockKeys = false; initGame(); endGame(); for (i = 0; i < NUM_MICE; i++) killMouse(i); gameState = INIT; } public void initGame() { // Initialize game data. score = 0; lives = NUM_LIVES; level = 0; levelTotal = 0; extraLife = EXTRA_LIFE; delay = MAX_DELAY; paused = false; initLevel(); } public void endGame() { gameState = OVER; } public void initLevel() { int i; // Advance level. Once we have gone thru each, start at the beginning and // increase speed. level++; if (level > NUM_LEVELS) { level = 1; if (delay > MIN_DELAY) delay -= DELAY_INCR; } // Level total for display. levelTotal++; // Initialize game data. initBlocks(); initSnake(); for (i = 0; i < NUM_MICE; i++) initMouse(i); miceEaten = 0; miceNeeded = 3 * (NUM_MICE + NUM_LEVELS - level); keyActive = false; gameState = PLAY; } public void endLevel() { // Start counter to pause before changing level. gameState = LEVEL; levelCounter = LEVEL_COUNT; } public void initLife() { // Create a new snake. killSnake(); initSnake(); gameState = PLAY; } public void endLife() { // Start counter to pause before starting a new snake. gameState = END; endCounter = END_COUNT; } public void start() { if (loopThread == null) { loopThread = new Thread(this); loopThread.start(); } if (!loaded && loadThread == null) { loadThread = new Thread(this); loadThread.start(); } } public void stop() { if (loopThread != null) { loopThread.stop(); loopThread = null; } if (loadThread != null) { loadThread.stop(); loadThread = null; } } public void run() { int i; long startTime; // Lower this thread's priority and get the current time. Thread.currentThread().setPriority(Thread.MIN_PRIORITY); startTime = System.currentTimeMillis(); // Run thread for loading sounds. if (!loaded && Thread.currentThread() == loadThread) { loadSounds(); loaded = true; loadThread.stop(); } // This is the main loop. while (Thread.currentThread() == loopThread) { if (!paused) { // Move snake and mice. if (gameState == PLAY) { moveSnake(); for (i = 0; i < NUM_MICE; i++) moveMouse(i); } // Check the score and advance high score if necessary. if (score > highScore) highScore = score; // Add an extra life if score is high enough. if (score >= extraLife) { lives++; extraLife += EXTRA_LIFE; } // See if the snake was killed. If any lives are left, continue with // a new one. if (gameState == END && --endCounter < 0) if (--lives == 0) endGame(); else initLife(); // If enough mice have been eaten, show the key. if (gameState == PLAY && miceEaten == miceNeeded && !keyActive) { if (loaded && sound) chimeSound.play(); initKey(); } // If level was completed, go to the next one when the counter finishes. if (gameState == LEVEL && --levelCounter < 0) initLevel(); } // Update the screen and set the timer for the next loop. repaint(); try { if (gameState == PLAY) startTime += delay; else startTime += MIN_DELAY; Thread.sleep(Math.max(0, startTime - System.currentTimeMillis())); } catch (InterruptedException e) { break; } } } public void loadSounds() { // Load all sound clips by playing and immediately stopping them. try { bonkSound = getAudioClip(new URL(getCodeBase(), "bonk.au")); munchSound = getAudioClip(new URL(getCodeBase(), "munch.au")); squeakSound = getAudioClip(new URL(getCodeBase(), "squeak.au")); chimeSound = getAudioClip(new URL(getCodeBase(), "chime.au")); advanceSound = getAudioClip(new URL(getCodeBase(), "advance.au")); } catch (MalformedURLException e) {} bonkSound.play(); bonkSound.stop(); munchSound.play(); munchSound.stop(); squeakSound.play(); squeakSound.stop(); chimeSound.play(); chimeSound.stop(); advanceSound.play(); advanceSound.stop(); } public boolean keyDown(Event e, int key) { // Check if any cursor keys have been pressed and set direction but don't // allow a reversal of direction. if (!paused && !lockKeys) { if (key == Event.LEFT && lastDirection != RIGHT) direction = LEFT; else if (key == Event.RIGHT && lastDirection != LEFT && lastDirection != NONE) direction = RIGHT; else if (key == Event.UP && lastDirection != DOWN) direction = UP; else if (key == Event.DOWN && lastDirection != UP) direction = DOWN; } // 'M' key: toggle sound. if (key == 109) { if (sound) { bonkSound.stop(); munchSound.stop(); squeakSound.stop(); chimeSound.stop(); advanceSound.stop(); } sound = !sound; } // 'P' key: toggle pause mode. if (key == 112) paused = !paused; // 'S' key: start the game, if not already in progress. if (loaded && key == 115 && (gameState == INIT || gameState == OVER)) initGame(); return true; } public void initBlocks() { int i, j; // Clear the grid. for (i = 0; i < GRID_WIDTH; i++) for (j = 0; j < GRID_HEIGHT; j++) grid[i][j] = EMPTY | NONE | SQUARE; // Add outer walls. for (i = 0; i < GRID_WIDTH; i++) { grid[i][0] = BLOCK | NONE | SQUARE; grid[i][GRID_HEIGHT - 1] = BLOCK | NONE | SQUARE; } for (j = 1; j < GRID_HEIGHT - 1; j++) { grid[0][j] = BLOCK | NONE | SQUARE; grid[GRID_WIDTH - 1][j] = BLOCK | NONE | SQUARE; } // Add inner walls depending on current level. if (level == 2 || level == 4) { i = GRID_WIDTH / 4; for (j = GRID_HEIGHT / 4; j <= GRID_HEIGHT / 2 - 3; j++) { grid[i][j] = BLOCK | NONE | SQUARE; grid[GRID_WIDTH - 1 - i][j] = BLOCK | NONE | SQUARE; grid[i][GRID_HEIGHT - 1 - j] = BLOCK | NONE | SQUARE; grid[GRID_WIDTH - 1 - i][GRID_HEIGHT - 1 - j] = BLOCK | NONE | SQUARE; } j = GRID_HEIGHT / 4; for (i = GRID_WIDTH / 4; i <= GRID_WIDTH / 2 - 3; i++) { grid[i][j] = BLOCK | NONE | SQUARE; grid[GRID_WIDTH - 1 - i][j] = BLOCK | NONE | SQUARE; grid[i][GRID_HEIGHT - 1 - j] = BLOCK | NONE | SQUARE; grid[GRID_WIDTH - 1 - i][GRID_HEIGHT - 1 - j] = BLOCK | NONE | SQUARE; } } if (level == 3) { i = GRID_WIDTH / 2; for (j = 0; j <= 3; j++) { grid[i][j] = BLOCK | NONE | SQUARE; grid[i][GRID_HEIGHT - 1 - j] = BLOCK | NONE | SQUARE; } } if (level == 3 || level == 5) { j = GRID_HEIGHT / 4; for (i = 5; i <= GRID_WIDTH / 4; i++) { grid[i][j] = BLOCK | NONE | SQUARE; grid[GRID_WIDTH - 1 - i][j] = BLOCK | NONE | SQUARE; grid[i][GRID_HEIGHT - 1 - j] = BLOCK | NONE | SQUARE; grid[GRID_WIDTH - 1 - i][GRID_HEIGHT - 1 - j] = BLOCK | NONE | SQUARE; } i = GRID_WIDTH / 4; for (j = GRID_HEIGHT / 4; j <= GRID_HEIGHT / 2; j++) { grid[i][j] = BLOCK | NONE | SQUARE; grid[GRID_WIDTH - 1 - i][j] = BLOCK | NONE | SQUARE; grid[i][GRID_HEIGHT - 1 - j] = BLOCK | NONE | SQUARE; grid[GRID_WIDTH - 1 - i][GRID_HEIGHT - 1 - j] = BLOCK | NONE | SQUARE; } j = GRID_HEIGHT / 2; for (i = 0; i <= GRID_WIDTH / 4 - 5; i++) { grid[i][j] = BLOCK | NONE | SQUARE; grid[GRID_WIDTH - 1 - i][j] = BLOCK | NONE | SQUARE; } } if (level == 4) { i = 4; for (j = GRID_HEIGHT / 2 - 2; j <= GRID_HEIGHT / 2; j++) { grid[i][j] = BLOCK | NONE | SQUARE; grid[GRID_WIDTH - 1 - i][j] = BLOCK | NONE | SQUARE; grid[i][GRID_HEIGHT - 1 - j] = BLOCK | NONE | SQUARE; grid[GRID_WIDTH - 1 - i][GRID_HEIGHT - 1 - j] = BLOCK | NONE | SQUARE; } i = GRID_WIDTH / 2; for (j = 0; j <= 2; j++) { grid[i][j] = BLOCK | NONE | SQUARE; grid[i][GRID_HEIGHT - 1 - j] = BLOCK | NONE | SQUARE; } } if (level == 5) { i = 3 * GRID_WIDTH / 8; for (j = 4; j <= GRID_HEIGHT / 3 - 1; j++) { grid[i][j] = BLOCK | NONE | SQUARE; grid[GRID_WIDTH - 1 - i][j] = BLOCK | NONE | SQUARE; grid[i][GRID_HEIGHT - 1 - j] = BLOCK | NONE | SQUARE; grid[GRID_WIDTH - 1 - i][GRID_HEIGHT - 1 - j] = BLOCK | NONE | SQUARE; } j = GRID_HEIGHT / 3 - 1; for (i = 3 * GRID_WIDTH / 8; i <= GRID_WIDTH / 2; i++) { grid[i][j] = BLOCK | NONE | SQUARE; grid[GRID_WIDTH - 1 - i][j] = BLOCK | NONE | SQUARE; grid[i][GRID_HEIGHT - 1 - j] = BLOCK | NONE | SQUARE; grid[GRID_WIDTH - 1 - i][GRID_HEIGHT - 1 - j] = BLOCK | NONE | SQUARE; } } } public void initSnake() { int i, j; int m, n; // Create a new snake in the center of the grid and reset cell list. i = GRID_WIDTH / 2; j = GRID_HEIGHT / 2; snake[4].x = i - 2; snake[4].y = j; snake[3].x = i - 1; snake[3].y = j; snake[2].x = i - 0; snake[2].y = j; snake[1].x = i + 1; snake[1].y = j; snake[0].x = i + 2; snake[0].y = j; headPtr = 4; tailPtr = 0; grid[i - 2][j] = SNAKE | LEFT | SNAKEHEAD; grid[i - 1][j] = SNAKE | LEFT | SNAKEBODY; grid[i][j] = SNAKE | LEFT | SNAKEBODY; grid[i + 1][j] = SNAKE | LEFT | SNAKEBODY; grid[i + 2][j] = SNAKE | LEFT | SNAKETAIL; // Clear snake direction. direction = NONE; lastDirection = NONE; // Relocate any mice that were overwritten. for (m = 0; m < NUM_MICE; m++) for (n = -2; n <= 2 ; n++) if (mouse[m].x == i + n && mouse[m].y == j) initMouse(m); // Relocate key if overwritten. if (keyActive) for (n = -2; n <= 2 ; n++) if (key.x == i + n && key.y == j) initKey(); } public void moveSnake() { Point pt; int i, j; int k; int c; int d; // Lock cursor keys to prevent the current direction from being changed // until we are done processing it. lockKeys = true; // Move snake's head into the next cell based on current direction. pt = snake[headPtr]; i = pt.x; j = pt.y; switch (direction) { case LEFT: i--; break; case RIGHT: i++; break; case UP: j--; break; case DOWN: j++; break; default: lockKeys = false; return; } // Unlock cursor keys and save direction. lockKeys = false; lastDirection = direction; // Skip if no direction given. if (direction == NONE) return; // Get the type of the new cell. c = grid[i][j] & TYPE_MASK; // Check if we hit a wall or ourselves. if (c == BLOCK || c == SNAKE) { if (loaded && sound) bonkSound.play(); endLife(); return; } // Replace current head with the appropriate body part. d = grid[pt.x][pt.y] & DIR_MASK; if (d == direction) grid[pt.x][pt.y] = SNAKE | d | SNAKEBODY; else if (d == LEFT && direction == UP) grid[pt.x][pt.y] = SNAKE | direction | SNAKEELB1; else if (d == UP && direction == RIGHT) grid[pt.x][pt.y] = SNAKE | direction | SNAKEELB1; else if (d == RIGHT && direction == DOWN) grid[pt.x][pt.y] = SNAKE | direction | SNAKEELB1; else if (d == DOWN && direction == LEFT) grid[pt.x][pt.y] = SNAKE | direction | SNAKEELB1; else if (d == UP && direction == LEFT) grid[pt.x][pt.y] = SNAKE | direction | SNAKEELB2; else if (d == LEFT && direction == DOWN) grid[pt.x][pt.y] = SNAKE | direction | SNAKEELB2; else if (d == DOWN && direction == RIGHT) grid[pt.x][pt.y] = SNAKE | direction | SNAKEELB2; else if (d == RIGHT && direction == UP) grid[pt.x][pt.y] = SNAKE | direction | SNAKEELB2; // Change the new cell to a snake's head and set it in the cell list. grid[i][j] = SNAKE | direction | SNAKEHEAD; if (++headPtr >= snake.length) headPtr = 0; snake[headPtr].x = i; snake[headPtr].y = j; // If we got a mouse, bump the score and create a new one. Otherwise, // move the tail. if (c == MOUSE) { score += MOUSE_POINTS; if (loaded && sound) munchSound.play(); for (k = 0; k < NUM_MICE; k++) if (mouse[k].x == i && mouse[k].y == j) initMouse(k); miceEaten++; } else { pt = snake[tailPtr]; grid[pt.x][pt.y] = EMPTY & NONE & SQUARE; if (++tailPtr >= snake.length) tailPtr = 0; pt = snake[tailPtr]; d = grid[pt.x][pt.y] & DIR_MASK; grid[pt.x][pt.y] = SNAKE | d | SNAKETAIL; } // Check if we got the key. If so, end the current level. if (c == KEY) { score += LEVEL_POINTS; if (loaded && sound) advanceSound.play(); endLevel(); } } public void killSnake() { Point pt; int n; // Remove snake from the grid. n = headPtr + 1; if (n >= snake.length) n = 0; while(tailPtr != n) { pt = snake[tailPtr]; grid[pt.x][pt.y] = EMPTY | NONE | SQUARE; if (++tailPtr >= snake.length) tailPtr = 0; } } public void initMouse(int n) { int i, j; int d; // Find an empty cell. do { i = (int) (Math.random() * GRID_WIDTH); j = (int) (Math.random() * GRID_HEIGHT); } while ((grid[i][j] & TYPE_MASK) != EMPTY); // Get a random direction. d = (int) (Math.random() * 4) + 1; d <<= 8; // Save mouse position. mouse[n].x = i; mouse[n].y = j; // Set the cell with mouse data. grid[i][j] = MOUSE | d | MOUSEBODY; } public void moveMouse(int n) { int i, j; int d; int m; // Skip move at random. if (Math.random() > 0.25) return; // Skip move if mouse is dead. if (mouse[n].x == -1 || mouse[n].y == -1) return; // Toss in a random squeak. if (loaded && sound && Math.random() > 0.975) squeakSound.play(); // Get a random direction. d = (int) (Math.random() * 5); d <<= 8; // Don't allow a reversal of direction. m = grid[mouse[n].x][mouse[n].y] & DIR_MASK; if ((m == LEFT && d == RIGHT) || (m == RIGHT && d == LEFT) || (m == UP && d == DOWN) || (m == DOWN && d == UP)) return; i = mouse[n].x; j = mouse[n].y; switch (d) { case LEFT: i--; break; case RIGHT: i++; break; case UP: j--; break; case DOWN: j++; break; default: return; } // See if the new cell is empty. If not, skip move. if ((grid[i][j] & TYPE_MASK) != EMPTY) return; // Clear mouse from old cell and move to the new one. grid[mouse[n].x][mouse[n].y] = EMPTY | NONE | SQUARE; mouse[n].x = i; mouse[n].y = j; grid[i][j] = MOUSE | d | MOUSEBODY; } public void killMouse(int n) { // Clear mouse from grid and set coordinates to (-1, -1) so we know it is // dead. grid[mouse[n].x][mouse[n].y] = EMPTY | NONE | SQUARE; mouse[n].x = -1; mouse[n].y = -1; } public void initKey() { int i, j; // Find an empty cell. do { i = (int) (Math.random() * GRID_WIDTH); j = (int) (Math.random() * GRID_HEIGHT); } while ((grid[i][j] & TYPE_MASK) != EMPTY); // Save key position. key.x = i; key.y = j; // Set the cell with key data and set the flag to show it's been added. grid[i][j] = KEY | NONE | KEYSHAPE; keyActive = true; } public Polygon translate(Polygon p, int dx, int dy) { Polygon polygon; int i; // Returns the polygon created by translating the given shape. polygon = new Polygon(); for (i = 0; i < p.npoints; i++) polygon.addPoint(p.xpoints[i] + dx, p.ypoints[i] + dy); return polygon; } public Polygon rotate(Polygon p, int d) { Polygon polygon; int i; // Returns the polygon created by rotating the given shape in 90 deg. increments. polygon = new Polygon(); for (i = 0; i < p.npoints; i++) switch (d) { case LEFT: polygon.addPoint(p.ypoints[i], (GRID_SIZE - 1) - p.xpoints[i]); break; case RIGHT: polygon.addPoint((GRID_SIZE - 1) - p.ypoints[i], p.xpoints[i]); break; case DOWN: polygon.addPoint((GRID_SIZE - 1) - p.xpoints[i], (GRID_SIZE - 1) - p.ypoints[i]); break; default: polygon.addPoint(p.xpoints[i], p.ypoints[i]); break; } return polygon; } public Polygon mirror(Polygon p) { Polygon polygon; int i; // Returns the polygon created by mirroring the given shape. polygon = new Polygon(); for (i = 0; i < p.npoints; i++) polygon.addPoint((GRID_SIZE - 1) - p.xpoints[i], p.ypoints[i]); return polygon; } public Color fade(Color s, Color e, double pct) { int r, g, b; // Fade the starting color to the ending color by the given percentage. if (pct < 0.0) return e; if (pct > 1.0) return s; r = e.getRed() + (int) Math.round(pct * (s.getRed() - e.getRed())); g = e.getGreen() + (int) Math.round(pct * (s.getGreen() - e.getGreen())); b = e.getBlue() + (int) Math.round(pct * (s.getBlue() - e.getBlue())); return(new Color(r, g, b)); } public void paint(Graphics g) { update(g); } public void update(Graphics g) { Dimension d = size(); int width, height; int xOff, yOff; int i, j; int m; int n; Polygon p; String s; // Create the offscreen graphics context, if no good one exists. if (offGraphics == null || d.width != offDimension.width || d.height != offDimension.height) { offDimension = d; offImage = createImage(d.width, d.height); offGraphics = offImage.getGraphics(); } // Fill in applet background. offGraphics.setColor(bgColor); offGraphics.fillRect(0, 0, d.width, d.height); // Center game area. width = GRID_WIDTH * GRID_SIZE; height = GRID_HEIGHT * GRID_SIZE; xOff = (d.width - width) / 2; yOff = (d.height - (height + 2 * fontHeight)) / 2; offGraphics.translate(xOff, yOff); // Fill in playing field. if (gameState == LEVEL) offGraphics.setColor(fade(fieldColor, bgColor, (double) levelCounter / (double) LEVEL_COUNT)); else offGraphics.setColor(fieldColor); offGraphics.fillRect(0, 0, GRID_WIDTH * GRID_SIZE, GRID_HEIGHT * GRID_SIZE); // Fill in each grid cell with the appropriate shape. for (i = 0; i < GRID_WIDTH; i++) for (j = 0; j < GRID_HEIGHT; j++) switch (grid[i][j] & TYPE_MASK) { case EMPTY: break; case BLOCK: if (gameState == LEVEL) offGraphics.setColor(fade(blockColor, bgColor, (double) levelCounter / (double) LEVEL_COUNT)); else offGraphics.setColor(blockColor); offGraphics.fillRect(i * GRID_SIZE, j * GRID_SIZE, GRID_SIZE, GRID_SIZE); offGraphics.setColor(bgColor); offGraphics.drawRect(i * GRID_SIZE, j * GRID_SIZE, GRID_SIZE, GRID_SIZE); break; case SNAKE: n = grid[i][j] & SHAPE_MASK; switch (n) { case SNAKEHEAD: p = snakeHead; break; case SNAKEBODY: p = snakeBody; break; case SNAKEELB1: p = snakeElb1; break; case SNAKEELB2: p = snakeElb2; break; case SNAKETAIL: p = snakeTail; break; default: p = snakeHead; break; } p = translate(rotate(p, grid[i][j] & DIR_MASK), i * GRID_SIZE, j * GRID_SIZE); if (gameState == LEVEL) offGraphics.setColor(fade(snakeColor, bgColor, (double) levelCounter / (double) LEVEL_COUNT)); else if (gameState == END || gameState == OVER) offGraphics.setColor(fade(snakeColor, fieldColor, (double) endCounter / (double) END_COUNT)); else offGraphics.setColor(snakeColor); offGraphics.fillPolygon(p); offGraphics.drawPolygon(p); offGraphics.drawLine(p.xpoints[p.npoints - 1], p.ypoints[p.npoints - 1], p.xpoints[0], p.ypoints[0]); if (gameState == END || gameState == OVER) offGraphics.setColor(fade(bgColor, fieldColor, (double) endCounter / (double) END_COUNT)); else offGraphics.setColor(bgColor); if (n == SNAKEHEAD || n == SNAKETAIL) for (m = 0; m < p.npoints - 1; m++) offGraphics.drawLine(p.xpoints[m], p.ypoints[m], p.xpoints[m + 1], p.ypoints[m + 1]); if (n == SNAKEBODY) { offGraphics.drawLine(p.xpoints[0], p.ypoints[0], p.xpoints[1], p.ypoints[1]); offGraphics.drawLine(p.xpoints[2], p.ypoints[2], p.xpoints[3], p.ypoints[3]); } if (n == SNAKEELB1 || n == SNAKEELB2) { offGraphics.drawLine(p.xpoints[0], p.ypoints[0], p.xpoints[1], p.ypoints[1]); offGraphics.drawLine(p.xpoints[1], p.ypoints[1], p.xpoints[2], p.ypoints[2]); offGraphics.drawLine(p.xpoints[3], p.ypoints[3], p.xpoints[4], p.ypoints[4]); offGraphics.drawLine(p.xpoints[4], p.ypoints[4], p.xpoints[5], p.ypoints[5]); offGraphics.drawLine(p.xpoints[5], p.ypoints[5], p.xpoints[6], p.ypoints[6]); } break; case MOUSE: if (gameState == LEVEL) offGraphics.setColor(fade(mouseColor, bgColor, (double) levelCounter / (double) LEVEL_COUNT)); else offGraphics.setColor(mouseColor); p = translate(rotate(mouseBody, grid[i][j] & DIR_MASK), i * GRID_SIZE, j * GRID_SIZE); offGraphics.fillPolygon(p); offGraphics.setColor(bgColor); offGraphics.drawPolygon(p); offGraphics.drawLine(p.xpoints[p.npoints - 1], p.ypoints[p.npoints - 1], p.xpoints[0], p.ypoints[0]); break; case KEY: p = translate(rotate(keyShape, grid[i][j] & DIR_MASK), i * GRID_SIZE, j * GRID_SIZE); offGraphics.setColor(keyColor); offGraphics.fillPolygon(p); offGraphics.setColor(bgColor); offGraphics.drawPolygon(p); offGraphics.drawLine(p.xpoints[p.npoints - 1], p.ypoints[p.npoints - 1], p.xpoints[0], p.ypoints[0]); break; default: break; } // Outline playing field. offGraphics.setColor(fieldColor); offGraphics.drawRect(0, 0, GRID_WIDTH * GRID_SIZE, GRID_HEIGHT * GRID_SIZE - 1); // Display status and messages. offGraphics.setFont(font); offGraphics.setColor(fieldColor); i = height - 1; j = height + 3 * fontHeight / 2; offGraphics.drawRect(0, i, width, 2 * fontHeight); offGraphics.setColor(fgColor); s = "Score: " + score; offGraphics.drawString(s, fontWidth, j); s = "Level: " + levelTotal; offGraphics.drawString(s, (width - fm.stringWidth(s)) / 4, j); s = "Lives: " + lives; offGraphics.drawString(s, 3 * (width - fm.stringWidth(s)) / 4, j); s = "High: " + highScore; offGraphics.drawString(s, width - (fontWidth + fm.stringWidth(s)), j); if (paused) { s = "Paused"; offGraphics.drawString(s, (width - fm.stringWidth(s)) / 2, j); } else if (!sound) { s = "Muted"; offGraphics.drawString(s, (width - fm.stringWidth(s)) / 2, j); } if (gameState == INIT || gameState == OVER) { offGraphics.setColor(bgColor); s = "Snake Pit"; offGraphics.drawString(s, (width - fm.stringWidth(s)) / 2 + 1, height / 3 + fontHeight + 1); s = "Copyright 1998 by Mike Hall"; offGraphics.drawString(s, (width - fm.stringWidth(s)) / 2 + 1, height / 3 + 2 * fontHeight + 1); offGraphics.setColor(fgColor); s = "Snake Pit"; offGraphics.drawString(s, (width - fm.stringWidth(s)) / 2, height / 3 + fontHeight); s = "Copyright 1998 by Mike Hall"; offGraphics.drawString(s, (width - fm.stringWidth(s)) / 2, height / 3 + 2 * fontHeight); offGraphics.setColor(fgColor); if (!loaded) s = "Loading sounds..."; else s = "Game Over - 'S' to Start"; offGraphics.setColor(bgColor); offGraphics.drawString(s, (width - fm.stringWidth(s)) / 2 + 1, 2 * height / 3 + 1); offGraphics.setColor(fgColor); offGraphics.drawString(s, (width - fm.stringWidth(s)) / 2, 2 * height / 3); } if (gameState == LEVEL) { s = "Advancing to Level " + (levelTotal + 1); offGraphics.setColor(bgColor); offGraphics.drawString(s, (width - fm.stringWidth(s)) / 2 + 1, height / 2 + 1); offGraphics.setColor(fgColor); offGraphics.drawString(s, (width - fm.stringWidth(s)) / 2, height / 2); } // Copy the off screen buffer to the screen. offGraphics.translate(-xOff, -yOff); g.drawImage(offImage, 0, 0, this); } }