From 379526b65da01d5862f4bfd44a478d437d847e03 Mon Sep 17 00:00:00 2001 From: Jonathan Bernard Date: Tue, 9 Dec 2014 00:00:25 -0600 Subject: [PATCH] Updated snake with a level editor. --- .gitignore | 1 + snake/snake.html | 70 ++++++++++---- snake/snake.js | 247 ++++++++++++++++++++++++++++++++++++----------- 3 files changed, 242 insertions(+), 76 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..45d62d8 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.sw? diff --git a/snake/snake.html b/snake/snake.html index f3d01c7..5d89a4b 100644 --- a/snake/snake.html +++ b/snake/snake.html @@ -2,36 +2,72 @@ +

Snake

- - - - - - - +
+ +
+ +
+ +
+ Game + Editor + +
+
+
+ +
+
+ + +
+
+ Arrow keys change direction.
+ Pause key pauses the game.
+ Enter restarts the game.
+
-
- +
+ diff --git a/snake/snake.js b/snake/snake.js index c928488..6a88dfd 100644 --- a/snake/snake.js +++ b/snake/snake.js @@ -2,88 +2,154 @@ var S = window.Snake = {}; + var SPACE = 0; var WALL = 1; var SNAKE = 2; var FOOD = 3; var ROWS = S.ROWS = 50; var COLS = S.COLS = 50; var board; + var startBoard; + var tileWidth, tileHeight; + var body = new Array(); - var headCur = coord2idx(Math.floor((ROWS - 1) / 2), Math.floor((COLS - 1) / 2)); + var headCur; var direction = 1; - var g = S.graphicsContext = null; - var canvas = S.canvas = null; - var cellWidth, cellHeight; var foodEaten = false; + + var g; + var canvas; + + var editor = false; + var editorControls; + var editorDataTextarea; + var cursorIdx; + var cursorBlock; + var dead = true; + var pause = false; var skipTicks; var nextGameTick; + var cmdQueue = new Array(); + var handers = new Array(); function coord2idx(row, col) { return (COLS * row) + col; } function idx2row(idx) { return Math.floor(idx/COLS); } function idx2col(idx) { return idx % COLS; } - function initialize(options) { //givenCanvas, keyContext, fps) { + function initialize(options) { - if (!options.canvas) { - alert("Missing the canvas element."); - return false } - - canvas = options.canvas; + // Store our own copy of the canvas and get a 2D graphics context. + if (options.canvas) canvas = options.canvas; + else canvas = document.getElementsByTagName("canvas")[0]; g = S.graphicsContext = canvas.getContext("2d"); + // Remove any existing handlers + canvas.removeEventListener('keydown', handleGameKey); + canvas.removeEventListener('keydown', handleEditorKey); + canvas.removeEventListener('click', handleEditorClick); + + // Read in the board dimensions. if (options.rows) S.ROWS = ROWS = options.rows; if (options.cols) S.COLS = COLS = options.cols; - board = new Array(ROWS * COLS); + if (options.board) startBoard = options.board; + else { + // Wipe the board and draw the walls. + startBoard = new Array(ROWS * COLS); + var i; + for (i = 0; i < startBoard.length; i++ ) { + if (Math.floor(i / COLS) == 0 || (i % COLS) == 0) startBoard[i] = WALL + else if (Math.floor(i / COLS) == (ROWS - 1) || + (i % COLS) == (COLS - 1)) startBoard[i] = WALL + else startBoard[i] = SPACE; } } - cellWidth = canvas.width / COLS; - cellHeight = canvas.height / ROWS; - - if (options.keyContext) options.keyContext.onkeydown = handleKey; - else canvas.onkeydown = handleKey; + // Figure out how big each game tile is. + tileWidth = canvas.width / COLS; + tileHeight = canvas.height / ROWS; + // Mark the player as dead. This is primarily for the case where a + // player re-initializes the board during a game in progress. It + // causes any scheduled logic to quit without affecting out game state. + dead = true; + + // Check to see if they want to use the editor or the game. + editor = Boolean(options.editor); + g.fillStyle = "white"; g.fillRect(0, 0, canvas.width, canvas.height); - - g.font = "24px sans-serif"; - g.fillStyle = "black"; - g.fillText("Press Enter to begin.", 50, Math.floor(canvas.height/2), canvas.width - 100); + + if (!editor) { + canvas.addEventListener('keydown', handleGameKey); - if (options.fps) skipTicks = Math.ceil(1000 / options.fps); - else skipTicks = 150; } + g.font = "24px sans-serif"; + g.fillStyle = "black"; + g.fillText("Press Enter to begin.", 50, Math.floor(canvas.height/2), canvas.width - 100); - function startGame(canvas, keyContext) { + if (options.fps) skipTicks = Math.ceil(1000 / options.fps); + else skipTicks = 150; } + + else { + canvas.addEventListener('keydown', handleEditorKey); + canvas.addEventListener('click', handleEditorClick); + + editorControls = document.getElementById("editorControls") + editorDataTextarea = document.querySelector("#editorControls textarea"); + + startEditor(); } + + canvas.focus(); } + + + function startGame() { while (body.length > 0) body.pop(); while (cmdQueue.length > 0) cmdQueue.pop(); headCur = coord2idx(Math.floor((ROWS - 1) / 2), Math.floor((COLS - 1) / 2)); direction = 1; - dead = false; + dead = pause = false; nextGameTick = (new Date).getTime() + skipTicks; body.push(headCur); - // Wipe the board and draw the walls. - var i; - for (i = 0; i < board.length; i++ ) { - if (Math.floor(i / COLS) == 0 || (i % COLS) == 0) board[i] = 'W'; - else if (Math.floor(i / COLS) == (ROWS - 1) || - (i % COLS) == (COLS - 1)) board[i] = 'W'; - else board[i] = ''; } - - // Write the initial body segment onto the board. - board[headCur] = 'S'; + // Copy over a fresh board. + board = new Array(startBoard.length); + for (var i = 0; i < board.length; i++) board[i] = startBoard[i]; // Create a new food item and add it to the board. - board[newFoodLocation()] = 'F'; + board[newFoodLocation()] = FOOD; + + // Write the initial body segment onto the board. + board[headCur] = SNAKE; // Draw the board - for (i = 0; i < board.length; i++) { paintIdx(i); } + paintBoard(); gameLoop(); } + function startEditor() { + + board = new Array(ROWS * COLS); + + cursorBlock = SPACE; + cursorIdx = headCur = coord2idx( + Math.floor((ROWS - 1) / 2), Math.floor((COLS - 1) / 2)); + + // Wipe the board and draw the walls. + var i; + for (i = 0; i < board.length; i++ ) { + if (Math.floor(i / COLS) == 0 || (i % COLS) == 0) board[i] = WALL + else if (Math.floor(i / COLS) == (ROWS - 1) || + (i % COLS) == (COLS - 1)) board[i] = WALL + else board[i] = SPACE; } + + board[headCur] = SNAKE; + + document.getElementById("editorControls").style.display = "inline-block"; + paintBoard(); } + function gameLoop() { - if (dead) return // break the callback loop + // Break the game loop if the player died or paused. + if (dead || pause) return; if ((new Date).getTime() > nextGameTick) { updateAndDraw(); @@ -114,35 +180,35 @@ // 3. First check to see if we've eaten food (since it will affect // collision detection for the body). This is also where growth // happens via not moving the tail. - if (board[headCur] == 'F') foodEaten = true; + if (board[headCur] == FOOD) foodEaten = true; // 4. Remove the tail. if (!foodEaten) { var tailIdx = body.shift(); // Value on the board doesn't matter, just not W,S, or F - board[tailIdx] = ''; + board[tailIdx] = SPACE paintIdx(tailIdx); } // 5. Detect wall and snake collisions - if (board[headCur] == 'W' || board[headCur] == 'S') { + if (board[headCur] == WALL || board[headCur] == SNAKE) { die(); return } // 6. Move the head body.push(headCur); - board[headCur] = 'S'; + board[headCur] = SNAKE; paintIdx(headCur); // 7. Create more food if needed. if (foodEaten) { var foodLoc = newFoodLocation(); - board[foodLoc] = 'F'; + board[foodLoc] = FOOD; paintIdx(foodLoc); } } function newFoodLocation() { var emptySpaces = new Array(); for (var i = 0; i < board.length; i++) - if (board[i] != 'W' && board[i] != 'S') + if (board[i] != WALL && board[i] != SNAKE) emptySpaces.push(i); return emptySpaces[Math.floor(Math.random() * emptySpaces.length)] } @@ -157,42 +223,105 @@ g.fillStyle = "red"; g.fillText("Press Enter to retry.", 50, Math.floor(canvas.height/2), canvas.width - 100); } - function handleKey(ke) { + function handleGameKey(ke) { var key = ke.keyCode ? ke.keyCode : ke.which; + var dontCare = false; // Enter, start a new game. - if (key == 13) startGame(); + if (key == 13) { + dead = true; + requestAnimationFrame(startGame); } + + // Pause, pause the game. + else if (key == 19) { + if (pause == true) { + pause = false; + nextGameTick = (new Date).getTime(); + gameLoop(); } + else pause = true; } // Queue directions in the command queue - else if ((key > 36) && (key < 41)) cmdQueue.push(key); } + else if ((key > 36) && (key < 41)) cmdQueue.push(key); + + else dontCare = true; + + if (!dontCare) ke.preventDefault(); + return false; } + + function handleEditorKey(ke) { + var key = ke.keyCode ? ke.keyCode : ke.which; + var dontCare = false; + + switch (key) { + // Enter: place a tile. + case 13: board[cursorIdx] = cursorBlock; break; + + case 37: cursorIdx -= 1; break; // Left + case 38: cursorIdx -= COLS; break; // Up + case 39: cursorIdx += 1; break; // Right + case 40: cursorIdx += COLS; break; // Down + + case 49: case 97: cursorBlock = SPACE; break; // 1 + case 50: case 98: cursorBlock = WALL; break; // 2 + case 51: case 99: cursorBlock = SNAKE; break; // 3 + case 52: case 100: cursorBlock = FOOD; break; // 4 + + default: dontCare = true; + } + + emitEditorLevelData(); + paintBoard(); + g.strokeStyle = "solid 4px gray"; + g.strokeRect( + (cursorIdx % COLS) * tileWidth, + Math.floor(cursorIdx / COLS) * tileHeight, + tileWidth, tileHeight); + + if (!dontCare) ke.preventDefault(); } + + function handleEditorClick(ke) { + + } + + function emitEditorLevelData() { + editorDataTextarea.value = JSON.stringify({ + board: board, + rows: ROWS, + cols: COLS, + fps: Math.ceil(1000 / skipTicks)}); } + + function loadEditorLevelData() { } + + function paintBoard() { + for (i = 0; i < board.length; i++) { paintIdx(i); } } function paintCoord(row, col) { var idx = coord2idx(row, col); - if (board[idx] == 'W') g.fillStyle = "black"; - else if (board[idx] == 'F') g.fillStyle = "yellow"; - else if (board[idx] == 'S') g.fillStyle = "blue"; + if (board[idx] == WALL) g.fillStyle = "black"; + else if (board[idx] == FOOD) g.fillStyle = "green"; + else if (board[idx] == SNAKE) g.fillStyle = "blue"; else g.fillStyle = "white"; - g.fillRect(col, row, cellWidth, cellHeight); } + g.fillRect(col, row, tileWidth, tileHeight); } function paintIdxColor(idx, color) { g.fillStyle = color; g.fillRect( - (idx % COLS) * cellWidth, - Math.floor(idx / COLS) * cellHeight, - cellWidth, cellHeight); } + (idx % COLS) * tileWidth, + Math.floor(idx / COLS) * tileHeight, + tileWidth, tileHeight); } function paintIdx(idx) { - if (board[idx] == 'W') g.fillStyle = "black"; - else if (board[idx] == 'F') g.fillStyle = "yellow"; - else if (board[idx] == 'S') g.fillStyle = "blue"; + if (board[idx] == WALL) g.fillStyle = "black"; + else if (board[idx] == FOOD) g.fillStyle = "green"; + else if (board[idx] == SNAKE) g.fillStyle = "blue"; else g.fillStyle = "white"; g.fillRect( - (idx % COLS) * cellWidth, - Math.floor(idx / COLS) * cellHeight, - cellWidth, cellHeight); } + (idx % COLS) * tileWidth, + Math.floor(idx / COLS) * tileHeight, + tileWidth, tileHeight); } S.initialize = initialize;