Day 1: Snake
This commit is contained in:
commit
59981daf60
37
snake/snake.html
Normal file
37
snake/snake.html
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<script src="snake.js" type="application/javascript"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Snake</h1>
|
||||||
|
<label>Rows</label>
|
||||||
|
<input id=rows type=text placeholder="Rows" value=20>
|
||||||
|
<label>Columns</label>
|
||||||
|
<input id=cols type=text placeholder="Columns" value=20>
|
||||||
|
<label>Speed (higher = faster)</label>
|
||||||
|
<input id=fps type=text placeholder="Speed (higher = faster)" value=10>
|
||||||
|
<input id=reset type=button value="Apply">
|
||||||
|
|
||||||
|
<br/>
|
||||||
|
<canvas id=gameCanvas width=600 height=600></canvas>
|
||||||
|
|
||||||
|
<script type="application/javascript">
|
||||||
|
var canvas = document.getElementById("gameCanvas");
|
||||||
|
var resetButton = document.getElementById("reset");
|
||||||
|
|
||||||
|
Snake.initialize({ canvas: canvas,
|
||||||
|
keyContext: window,
|
||||||
|
rows: parseInt(document.getElementById("rows").value),
|
||||||
|
cols: parseInt(document.getElementById("cols").value),
|
||||||
|
fps: parseInt(document.getElementById("fps").value)});
|
||||||
|
|
||||||
|
resetButton.onclick = function() {
|
||||||
|
Snake.initialize({ canvas: canvas,
|
||||||
|
keyContext: window,
|
||||||
|
rows: parseInt(document.getElementById("rows").value),
|
||||||
|
cols: parseInt(document.getElementById("cols").value),
|
||||||
|
fps: parseInt(document.getElementById("fps").value)}); };
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
203
snake/snake.js
Normal file
203
snake/snake.js
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
(function() {
|
||||||
|
|
||||||
|
var S = window.Snake = {};
|
||||||
|
|
||||||
|
var ROWS = S.ROWS = 50;
|
||||||
|
var COLS = S.COLS = 50;
|
||||||
|
|
||||||
|
var board;
|
||||||
|
var body = new Array();
|
||||||
|
var headCur = coord2idx(Math.floor((ROWS - 1) / 2), Math.floor((COLS - 1) / 2));
|
||||||
|
var direction = 1;
|
||||||
|
var g = S.graphicsContext = null;
|
||||||
|
var canvas = S.canvas = null;
|
||||||
|
var cellWidth, cellHeight;
|
||||||
|
var foodEaten = false;
|
||||||
|
var dead = true;
|
||||||
|
var skipTicks;
|
||||||
|
var nextGameTick;
|
||||||
|
var cmdQueue = 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) {
|
||||||
|
|
||||||
|
if (!options.canvas) {
|
||||||
|
alert("Missing the canvas element.");
|
||||||
|
return false }
|
||||||
|
|
||||||
|
canvas = options.canvas;
|
||||||
|
g = S.graphicsContext = canvas.getContext("2d");
|
||||||
|
|
||||||
|
if (options.rows) S.ROWS = ROWS = options.rows;
|
||||||
|
if (options.cols) S.COLS = COLS = options.cols;
|
||||||
|
|
||||||
|
board = new Array(ROWS * COLS);
|
||||||
|
|
||||||
|
cellWidth = canvas.width / COLS;
|
||||||
|
cellHeight = canvas.height / ROWS;
|
||||||
|
|
||||||
|
if (options.keyContext) options.keyContext.onkeydown = handleKey;
|
||||||
|
else canvas.onkeydown = handleKey;
|
||||||
|
|
||||||
|
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 (options.fps) skipTicks = Math.ceil(1000 / options.fps);
|
||||||
|
else skipTicks = 150; }
|
||||||
|
|
||||||
|
function startGame(canvas, keyContext) {
|
||||||
|
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;
|
||||||
|
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';
|
||||||
|
|
||||||
|
// Create a new food item and add it to the board.
|
||||||
|
board[newFoodLocation()] = 'F';
|
||||||
|
|
||||||
|
// Draw the board
|
||||||
|
for (i = 0; i < board.length; i++) { paintIdx(i); }
|
||||||
|
|
||||||
|
gameLoop(); }
|
||||||
|
|
||||||
|
function gameLoop() {
|
||||||
|
if (dead) return // break the callback loop
|
||||||
|
|
||||||
|
if ((new Date).getTime() > nextGameTick) {
|
||||||
|
updateAndDraw();
|
||||||
|
nextGameTick += skipTicks; }
|
||||||
|
|
||||||
|
requestAnimationFrame(gameLoop); }
|
||||||
|
|
||||||
|
// For this game I want the game logic and the refresh rate to be tied
|
||||||
|
// together. The snake moves on step per animation frame. We do limit the
|
||||||
|
// animation frame to 1fps. This works because there are no other elements
|
||||||
|
// being animated other than the snake.
|
||||||
|
function updateAndDraw() {
|
||||||
|
if (!g) return false;
|
||||||
|
|
||||||
|
foodEaten = false;
|
||||||
|
|
||||||
|
// 1. Read up to one command from the user.
|
||||||
|
if (cmdQueue.length > 0) {
|
||||||
|
var cmd = cmdQueue.shift();
|
||||||
|
if (cmd == 37) direction = -1; // Left
|
||||||
|
else if (cmd == 38) direction = -COLS; // Up
|
||||||
|
else if (cmd == 39) direction = 1; // Right
|
||||||
|
else if (cmd == 40) direction = COLS; /* Down */ }
|
||||||
|
|
||||||
|
// 2. Find the head's next spot.
|
||||||
|
headCur = headCur + direction;
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
// 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] = '';
|
||||||
|
paintIdx(tailIdx); }
|
||||||
|
|
||||||
|
|
||||||
|
// 5. Detect wall and snake collisions
|
||||||
|
if (board[headCur] == 'W' || board[headCur] == 'S') {
|
||||||
|
die(); return }
|
||||||
|
|
||||||
|
// 6. Move the head
|
||||||
|
body.push(headCur);
|
||||||
|
board[headCur] = 'S';
|
||||||
|
paintIdx(headCur);
|
||||||
|
|
||||||
|
// 7. Create more food if needed.
|
||||||
|
if (foodEaten) {
|
||||||
|
var foodLoc = newFoodLocation();
|
||||||
|
board[foodLoc] = 'F';
|
||||||
|
paintIdx(foodLoc); } }
|
||||||
|
|
||||||
|
function newFoodLocation() {
|
||||||
|
var emptySpaces = new Array();
|
||||||
|
for (var i = 0; i < board.length; i++)
|
||||||
|
if (board[i] != 'W' && board[i] != 'S')
|
||||||
|
emptySpaces.push(i);
|
||||||
|
|
||||||
|
return emptySpaces[Math.floor(Math.random() * emptySpaces.length)] }
|
||||||
|
|
||||||
|
function die() {
|
||||||
|
paintIdxColor(headCur, "red");
|
||||||
|
dead = true;
|
||||||
|
//g.fillStyle = "white";
|
||||||
|
//g.fillRect(0, 0, canvas.width, canvas.height);
|
||||||
|
|
||||||
|
g.font = "24px sans-serif";
|
||||||
|
g.fillStyle = "red";
|
||||||
|
g.fillText("Press Enter to retry.", 50, Math.floor(canvas.height/2), canvas.width - 100); }
|
||||||
|
|
||||||
|
function handleKey(ke) {
|
||||||
|
var key = ke.keyCode ? ke.keyCode : ke.which;
|
||||||
|
|
||||||
|
// Enter, start a new game.
|
||||||
|
if (key == 13) startGame();
|
||||||
|
|
||||||
|
// Queue directions in the command queue
|
||||||
|
else if ((key > 36) && (key < 41)) cmdQueue.push(key); }
|
||||||
|
|
||||||
|
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";
|
||||||
|
else g.fillStyle = "white";
|
||||||
|
|
||||||
|
g.fillRect(col, row, cellWidth, cellHeight); }
|
||||||
|
|
||||||
|
function paintIdxColor(idx, color) {
|
||||||
|
g.fillStyle = color;
|
||||||
|
|
||||||
|
g.fillRect(
|
||||||
|
(idx % COLS) * cellWidth,
|
||||||
|
Math.floor(idx / COLS) * cellHeight,
|
||||||
|
cellWidth, cellHeight); }
|
||||||
|
|
||||||
|
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";
|
||||||
|
else g.fillStyle = "white";
|
||||||
|
|
||||||
|
g.fillRect(
|
||||||
|
(idx % COLS) * cellWidth,
|
||||||
|
Math.floor(idx / COLS) * cellHeight,
|
||||||
|
cellWidth, cellHeight); }
|
||||||
|
|
||||||
|
S.initialize = initialize;
|
||||||
|
|
||||||
|
// Exports for debugging
|
||||||
|
S.board = board;
|
||||||
|
S.body = body;
|
||||||
|
S.updateAndDraw = updateAndDraw;
|
||||||
|
})();
|
Loading…
x
Reference in New Issue
Block a user