final int GAME_SPLASHSCREEN = 0; final int GAME_PLAYING = 1; final int GAME_OVER = 2; final int DIR_NONE = 0; final int DIR_UP = 1; final int DIR_DOWN = 2; final int DIR_LEFT = 4; final int DIR_RIGHT = 8; final int BOOSTER_FOOD = -1; final int BOOSTER_COFFEE = -2; final int BOOSTER_BROCCOLI = -4; final int SNAKE_HEAD = -8; final int SNAKE_BODY = -16; final color COL_SNAKE_BODY = #99ccff; final color COL_SNAKE_HEAD = #5588ff; final color COL_FOOD = #cf8a00; final color COL_BROCCOLI = #00b215; final color COL_COFFEE = #eac29a; final int BROCCOLI_DELAY_MIN = 500; final int BROCCOLI_DELAY_MAX = 2500; int gameState = GAME_SPLASHSCREEN; int cellSize = 16; int xOffset = 0; int yOffset = 0; int[] grid; int rows = 0; int columns = 0; int snakeHeadX = 0; int snakeHeadY = 0; int snakeDirection = DIR_NONE; int lastMove = 0; int currentHighScore = 0; int moveDelay = 100; int lastBroccoliMove = 0; int broccoliMoveDelay = 2500; int broccoliX = 0; int broccoliY = 0; String highScoreText = ""; String gameOverReason = ""; PFont headingFont; PFont textFont; PFont smallFont; void setup() { size(500, 500); headingFont = createFont("Liberation Sans Bold", 32); textFont = createFont("Liberation Sans", 16); smallFont = createFont("Liberation Sans", 12); xOffset = (width % cellSize) / 2; yOffset = (height % cellSize) / 2; ellipseMode(CENTER); } void draw() { switch (gameState) { case GAME_SPLASHSCREEN: background(#000000); fill(#FFFFFF); textAlign(CENTER, CENTER); textFont(headingFont); text("Snake", width / 2, height / 4); textFont(textFont); text("A single player game\n\nPress any key or click anywhere to start playing\n\n© 2022 Christian Weber", width / 2, height / 2); noStroke(); textAlign(CENTER, TOP); textFont(smallFont); fill(#FFFFFF); text("The snake\n(big head!)", width / 2, 3 * height / 4 - 20); drawCell(SNAKE_BODY, width / 2, 3 * height / 4 - 30); drawCell(SNAKE_BODY, width / 2 + cellSize, 3 * height / 4 - 30); drawCell(SNAKE_HEAD, width / 2 - cellSize, 3 * height / 4 - 30); fill(#FFFFFF); text("Food\n(+1 length)", width / 4, 3 * height / 4 + 55); drawCell(BOOSTER_FOOD, width / 4, 3 * height / 4 + 45); fill(#FFFFFF); text("Coffee\n(+3 length, +1 speed)", width / 2, 3 * height / 4 + 55); drawCell(BOOSTER_COFFEE, width / 2, 3 * height / 4 + 45); fill(#FFFFFF); text("Broccoli (Poison)\n(-3 length, -1 speed)\nBeware! Jumps around!", 3 * width / 4, 3 * height / 4 + 55); drawCell(BOOSTER_BROCCOLI, 3 * width / 4, 3 * height / 4 + 45); break; case GAME_PLAYING: background(#000000); drawGrid(); if (millis() - lastMove > moveDelay) { moveSnake(); lastMove = millis(); } if (millis() - lastBroccoliMove > broccoliMoveDelay) { randomBooster(BOOSTER_BROCCOLI); broccoliMoveDelay = (int)random(250, 5000); lastBroccoliMove = millis(); } break; case GAME_OVER: background(#000000); textAlign(CENTER, CENTER); drawGrid(); fill(#77000000); noStroke(); rectMode(CORNER); rect(xOffset, yOffset, columns * cellSize, rows * cellSize); fill(#FFFFFF); textFont(headingFont); text("Game Over", width / 2, height / 4); textFont(textFont); text(gameOverReason + "\n\nYou scored " + currentHighScore + " points.\n\nPress space or enter or click anywhere to play again." + highScoreText, width / 2, height / 2); break; } } void drawCell(int booster, int x, int y) { noStroke(); int radius = 4 * cellSize / 5; switch (booster) { case SNAKE_HEAD: fill(COL_SNAKE_HEAD); break; case SNAKE_BODY: fill(COL_SNAKE_BODY); radius = 3 * cellSize / 5; break; case BOOSTER_COFFEE: fill(COL_COFFEE); break; case BOOSTER_FOOD: fill(COL_FOOD); rectMode(CENTER); rect(x, y, radius, radius); return; case BOOSTER_BROCCOLI: fill(COL_BROCCOLI); radius = 2 * cellSize / 5; triangle(x - radius, y + radius, x + radius, y + radius, x, y - radius); return; } ellipse(x, y, radius, radius); } void drawGrid() { if (grid == null || grid.length <= 0) return; for (int x = 0; x < columns; x++) { for (int y = 0; y < rows; y++) { int cell = grid[x * columns + y]; int xPos = xOffset + x * cellSize + cellSize / 2; int yPos = yOffset + y * cellSize + cellSize / 2; if (cell > 0) { if (x == snakeHeadX && y == snakeHeadY) { drawCell(SNAKE_HEAD, xPos, yPos); } else { drawCell(SNAKE_BODY, xPos, yPos); } } else if (cell == BOOSTER_FOOD || cell == BOOSTER_COFFEE || cell == BOOSTER_BROCCOLI) { drawCell(cell, xPos, yPos); } } } stroke(#222222); for (int x = 0; x <= columns; x++) { line(xOffset + x * cellSize, yOffset, xOffset + x * cellSize, height - yOffset); } for (int y = 0; y <= rows; y++) { line(xOffset, yOffset + y * cellSize, width - xOffset, yOffset + y * cellSize); } } void startGame() { gameState = GAME_PLAYING; moveDelay = 100; columns = width / cellSize; rows = height / cellSize; grid = new int[columns * rows]; snakeHeadX = (int)random(columns / 3) + columns / 3; snakeHeadY = (int)random(rows / 3) + rows / 3; grid[snakeHeadX * columns + snakeHeadY] = 2; switch ((int)random(4) + 1) { case 1: grid[snakeHeadX * columns + (snakeHeadY + 1)] = 1; setDirection(DIR_UP); break; case 2: grid[snakeHeadX * columns + (snakeHeadY - 1)] = 1; setDirection(DIR_DOWN); break; case 3: grid[(snakeHeadX + 1) * columns + snakeHeadY] = 1; setDirection(DIR_LEFT); break; default: case 4: grid[(snakeHeadX - 1) * columns + snakeHeadY] = 1; setDirection(DIR_RIGHT); break; } for (int i = 0; i < 5; i++) { randomBooster(BOOSTER_FOOD); } randomBooster(BOOSTER_BROCCOLI); randomBooster(BOOSTER_COFFEE); } void randomBooster(int booster) { int x = (int)random(columns); int y = (int)random(rows); if (booster == BOOSTER_BROCCOLI) { grid[broccoliX * columns + broccoliY] = 0; } grid[x * columns + y] = booster; if (booster == BOOSTER_BROCCOLI) { broccoliX = x; broccoliY = y; } } void setDirection(int direction) { if (direction == DIR_UP || direction == DIR_DOWN || direction == DIR_LEFT || direction == DIR_RIGHT) { snakeDirection = direction; } } void moveSnake() { int head = grid[snakeHeadX * columns + snakeHeadY]; for (int x = 0; x < columns; x++) { for (int y = 0; y < rows; y++) { if (grid[x * columns + y] > 0) { grid[x * columns + y]--; } } } int newHeadIdx = -1; switch (snakeDirection) { case DIR_UP: if (snakeHeadY - 1 < 0) { endGame(head, "You hit the wall."); return; } newHeadIdx = snakeHeadX * columns + (snakeHeadY - 1); snakeHeadY--; break; case DIR_DOWN: if (snakeHeadY + 1 >= rows) { endGame(head, "You hit the wall."); return; } newHeadIdx = snakeHeadX * columns + (snakeHeadY + 1); snakeHeadY++; break; case DIR_LEFT: if (snakeHeadX - 1 < 0) { endGame(head, "You hit the wall."); return; } newHeadIdx = (snakeHeadX - 1) * columns + snakeHeadY; snakeHeadX--; break; case DIR_RIGHT: if (snakeHeadX + 1 >= columns) { endGame(head, "You hit the wall."); return; } newHeadIdx = (snakeHeadX + 1) * columns + snakeHeadY; snakeHeadX++; break; } if (grid[newHeadIdx] > 0) { endGame(head, "You tried to eat yourself. Creep."); return; } else if (grid[newHeadIdx] == BOOSTER_FOOD) { head++; randomBooster(BOOSTER_FOOD); } else if (grid[newHeadIdx] == BOOSTER_COFFEE) { moveDelay *= 0.9; head += 3; randomBooster(BOOSTER_COFFEE); } else if (grid[newHeadIdx] == BOOSTER_BROCCOLI) { moveDelay *= 1.1; head -= 3; randomBooster(BOOSTER_BROCCOLI); } if (head < 1) { endGame(0, "You ate too much broccoli."); return; } grid[newHeadIdx] = head; } void endGame(int score, String reason) { gameState = GAME_OVER; currentHighScore = score; gameOverReason = reason; StringList highScores = new StringList(); if (dataFile("highscores.txt").isFile()) { highScores.append(loadStrings("data/highscores.txt")); } String date = nf(year(), 4) + "-" + nf(month(), 2) + "-" + nf(day(), 2) + " " + nf(hour(), 2) + ":" + nf(minute(), 2) + ":" + nf(second(), 2); highScores.append(date + " / " + score); saveStrings("data/highscores.txt", highScores.array()); if (highScores.size() > 0) { int maxScore = 0; StringList dates = new StringList(); for (String s : highScores) { int parsedScore = parseInt(s.substring(22).trim()); if (maxScore < parsedScore) { maxScore = parsedScore; dates.clear(); } if (maxScore == parsedScore) { dates.append(s.substring(0, 20).trim()); } } dates.sort(); highScoreText = "\n\nHighScore: " + maxScore + " points, scored " + dates.size() + " times,\nfirst on " + dates.get(0) + ",\nlast on " + dates.get(dates.size() - 1) + "."; } } void keyPressed() { switch (gameState) { case GAME_SPLASHSCREEN: startGame(); break; case GAME_OVER: if (key == ' ' || keyCode == ENTER) { startGame(); } break; case GAME_PLAYING: if (key == 'n') { startGame(); } else if (keyCode == UP) { setDirection(DIR_UP); } else if (keyCode == DOWN) { setDirection(DIR_DOWN); } else if (keyCode == LEFT) { setDirection(DIR_LEFT); } else if (keyCode == RIGHT) { setDirection(DIR_RIGHT); } break; } } void mouseClicked() { switch (gameState) { case GAME_SPLASHSCREEN: case GAME_OVER: startGame(); break; case GAME_PLAYING: break; } }