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