diff --git a/resources/css/forSize.mixin.scss b/resources/css/forSize.mixin.scss
new file mode 100644
index 0000000..93efcc0
--- /dev/null
+++ b/resources/css/forSize.mixin.scss
@@ -0,0 +1,44 @@
+$smallScreen: 640px;
+$wideScreen: 1200px;
+$ultraWideScreen: 1600px;
+
+/** ### forSize
+ * This mixin allows us to apply some rules selectively based on the screen
+ * size. There are three primary sizes: `small`, `medium`, and `large`, which
+ * are mutually exclusive. Additionally there are two additional sizes:
+ * `notSmall` and `ultraLarge`. `notSmall`, as the name implies matches any
+ * value which is not the small screen size, so it overlaps with medium,
+ * large, and ultraLarge. `ultraLarge` defines a wider minimum screen size
+ * than large, but neither large nor ultraLarge specify maximum widths,
+ * so ultraLarge is a strict subset of large. A screen large enough to match
+ * ultraLarge will also match large (compare with medium and large: matching
+ * medium means it will not match large, and vice versa). */
+@mixin forSize($size) {
+
+ @if $size == small {
+ @media screen and (max-width: $smallScreen) { @content; } }
+ @else if $size == notSmall {
+ @media screen and (min-width: $smallScreen + 1) { @content; } }
+ @else if $size == medium {
+ @media screen and (min-width: $smallScreen + 1) and (max-width: $wideScreen - 1) { @content; } }
+ @else if $size == large {
+ @media screen and (min-width: $wideScreen) { @content; } }
+ @else if $size == ultraLarge {
+ @media screen and (min-width: $ultraWideScreen) { @content; } }
+}
+
+
+@mixin forAspect($aspect) {
+
+ @if $aspect == tall {
+ @media screen and (max-aspect-ratio: 1/2) { @content; } }
+ @else if $aspect == portrait {
+ @media screen and (min-aspect-ratio: 11/20) and (max-aspect-ratio: 5/6) { @content; } }
+ @else if $aspect == even {
+ @media screen and (min-aspect-ratio: 11/12) and (max-aspect-ratio: 6/5) { @content; } }
+ @else if $aspect == landscape {
+ @media screen and (min-aspect-ratio: 12/11) and (max-aspect-ratio: 31/18) { @content; } }
+ @else if $aspect == wide {
+ @media screen and (min-aspect-ratio: 16/9) { @content; } }
+
+}
diff --git a/snake/snake-redesign.html b/snake/snake-redesign.html
new file mode 100644
index 0000000..bf1d9d2
--- /dev/null
+++ b/snake/snake-redesign.html
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ Play SNAKE! You are the BLUE
+ snake. Eat the GREEN food to
+ grow longer! But be careful! Don't run into the walls or
+ yourself!
+
+
+
diff --git a/snake/snake.html b/snake/snake.html
index db6c5de..b28b729 100644
--- a/snake/snake.html
+++ b/snake/snake.html
@@ -4,64 +4,8 @@
Snake
+
-
diff --git a/snake/snake.js b/snake/snake.js
index 6a88dfd..7a7ffc8 100644
--- a/snake/snake.js
+++ b/snake/snake.js
@@ -2,30 +2,41 @@
var S = window.Snake = {};
+ // True constants
var SPACE = 0; var WALL = 1; var SNAKE = 2; var FOOD = 3;
+
+ // Fake constants (constant after board initialization).
var ROWS = S.ROWS = 50;
var COLS = S.COLS = 50;
- var board;
- var startBoard;
var tileWidth, tileHeight;
+ // The current game board.
+ var board;
+
+ // The starting game board.
+ var startBoard;
+
var body = new Array();
var headCur;
var direction = 1;
var foodEaten = false;
- var g;
+ // HTML Elements
var canvas;
+ var g;
- var editor = false;
var editorControls;
var editorDataTextarea;
var cursorIdx;
var cursorBlock;
+ // Flags
+ var editor = false;
var dead = true;
var pause = false;
+
+ // Time-management
var skipTicks;
var nextGameTick;
@@ -75,9 +86,11 @@
// Check to see if they want to use the editor or the game.
editor = Boolean(options.editor);
+ // Clear the canvas.
g.fillStyle = "white";
g.fillRect(0, 0, canvas.width, canvas.height);
+ // They want the game
if (!editor) {
canvas.addEventListener('keydown', handleGameKey);
@@ -88,6 +101,7 @@
if (options.fps) skipTicks = Math.ceil(1000 / options.fps);
else skipTicks = 150; }
+ // They want the editor
else {
canvas.addEventListener('keydown', handleEditorKey);
canvas.addEventListener('click', handleEditorClick);
@@ -158,9 +172,10 @@
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.
+ // together. The snake moves one step per animation frame. We do limit the
+ // animation frame to a given fps. This works because there are no other
+ // elements being animated other than the snake and no partial animation
+ // states.
function updateAndDraw() {
if (!g) return false;
@@ -185,10 +200,8 @@
// 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] = SPACE
paintIdx(tailIdx); }
-
// 5. Detect wall and snake collisions
if (board[headCur] == WALL || board[headCur] == SNAKE) {
@@ -205,12 +218,14 @@
board[foodLoc] = FOOD;
paintIdx(foodLoc); } }
+ // Find a new location for a piece of food.
function newFoodLocation() {
+ // Find all the spaces on the board that are not occupied
var emptySpaces = new Array();
for (var i = 0; i < board.length; i++)
- if (board[i] != WALL && board[i] != SNAKE)
- emptySpaces.push(i);
+ if (board[i] == SPACE) emptySpaces.push(i);
+ // Choose one of those spaces at random.
return emptySpaces[Math.floor(Math.random() * emptySpaces.length)] }
function die() {
@@ -225,6 +240,9 @@
function handleGameKey(ke) {
var key = ke.keyCode ? ke.keyCode : ke.which;
+ // We will only intercept key events which we care about. This makes it
+ // safe to attach the handler to the window or anywhere else without
+ // worrying about the game becoming a black-hole for events.
var dontCare = false;
// Enter, start a new game.
@@ -232,7 +250,7 @@
dead = true;
requestAnimationFrame(startGame); }
- // Pause, pause the game.
+ // Pause, pause or unpause the game.
else if (key == 19) {
if (pause == true) {
pause = false;
@@ -243,24 +261,34 @@
// Queue directions in the command queue
else if ((key > 36) && (key < 41)) cmdQueue.push(key);
+ // Anything else we don't care about.
else dontCare = true;
+ // If we *do* care about this key, consume it and prevent it from
+ // bubbling up.
if (!dontCare) ke.preventDefault();
return false; }
+ // Handle input for the game editor. The editor is entirely input driven.
+ // All of it's actions and logic are in response to user input. There is no
+ // "game" loop and most of the editor's logic happens in this function.
function handleEditorKey(ke) {
var key = ke.keyCode ? ke.keyCode : ke.which;
+
+ // Again, we will pass along input we are not interested in.
var dontCare = false;
switch (key) {
// Enter: place a tile.
case 13: board[cursorIdx] = cursorBlock; break;
+ // Direction keys: move the cursor.
case 37: cursorIdx -= 1; break; // Left
case 38: cursorIdx -= COLS; break; // Up
case 39: cursorIdx += 1; break; // Right
case 40: cursorIdx += COLS; break; // Down
+ // Number keys: select a tile type.
case 49: case 97: cursorBlock = SPACE; break; // 1
case 50: case 98: cursorBlock = WALL; break; // 2
case 51: case 99: cursorBlock = SNAKE; break; // 3
@@ -269,20 +297,27 @@
default: dontCare = true;
}
+ // Our board may have changed, let's spit out the data.
emitEditorLevelData();
+
+ // Redraw the board.
paintBoard();
+
+ // Draw the current cursor position.
g.strokeStyle = "solid 4px gray";
g.strokeRect(
(cursorIdx % COLS) * tileWidth,
Math.floor(cursorIdx / COLS) * tileHeight,
tileWidth, tileHeight);
+ // Consume events we care about and prevent them from bubbling up.
if (!dontCare) ke.preventDefault(); }
function handleEditorClick(ke) {
}
+ // Write out the editor level data as a JSON string to the textarea.
function emitEditorLevelData() {
editorDataTextarea.value = JSON.stringify({
board: board,
@@ -290,8 +325,10 @@
cols: COLS,
fps: Math.ceil(1000 / skipTicks)}); }
+ // Load the editor data as a JSON string from the textarea.
function loadEditorLevelData() { }
+ // Draw the entire board.
function paintBoard() {
for (i = 0; i < board.length; i++) { paintIdx(i); } }
@@ -325,8 +362,4 @@
S.initialize = initialize;
- // Exports for debugging
- S.board = board;
- S.body = body;
- S.updateAndDraw = updateAndDraw;
})();
diff --git a/snake/snake.scss b/snake/snake.scss
new file mode 100644
index 0000000..a7c3ced
--- /dev/null
+++ b/snake/snake.scss
@@ -0,0 +1,228 @@
+/* Pallete: http://www.colourlovers.com/palette/1832769/100_Following_Me
+ * A70407 - Red
+ * F4A612 - Orange
+ * FFFB51 - Soft Yellow
+ * 1E4119 - Green
+ * 0B0C38 - Blue */
+
+//$fg: #333;
+$fg: #FFF;
+$out: #A70407;
+$in: #0B0C38;
+$accent1: #F4A612;
+$accent2: #FFFB51;
+$accent3: #1E4119;
+
+@import "../resources/css/forSize.mixin.scss";
+
+* { margin: 0;
+ padding: 0;
+ box-sizing: border-box; }
+
+html {
+ background-color: $out;
+ font-family: "Press Start 2P";
+}
+
+.corner {
+ border-style: solid;
+ position: absolute;
+
+ height: 1rem;
+ width: 1rem;
+
+ &:nth-child(1) {
+ top: -0.5rem; left: -0.5rem;
+ border-width: 0.5rem 0 0 0.5rem; }
+
+ &:nth-child(2) {
+ top: -0.5rem; right: -0.5rem;
+ border-width: 0.5rem 0.5rem 0 0; }
+
+ &:nth-child(3) {
+ bottom: -0.5rem; right: -0.5rem;
+ border-width: 0 0.5rem 0.5rem 0; }
+
+ &:nth-child(4) {
+ bottom: -0.5rem; left: -0.5rem;
+ border-width: 0 0 0.5rem 0.5rem; } }
+
+body {
+ border: solid 0.5rem $accent1;
+ background-color: $accent2;
+ padding: 0.5rem;
+ position: relative;
+
+ & > .corner {
+ width: 1.5rem;
+ height: 1.5rem;
+ background-color: $accent1;
+ border-color: $out; }
+
+ #container {
+ border: solid 0.5rem $accent3;
+ background-color: $in;
+ color: $fg;
+
+ position: relative;
+
+ & > .corner {
+ background-color: $accent3;
+ border-color: $accent2; }
+
+ header {
+ text-align: center;
+ & > h1 { padding-top: 0.5rem; } } } }
+
+button {
+ background-color: $out;
+ border-color: $accent2 $accent1 $accent1 $accent2;
+ border-style: solid;
+ border-width: 0.25rem;
+ color: $fg;
+ display: block;
+ font-family: inherit;
+ padding: 0.1rem;
+ position: relative;
+
+ .corner {
+ border: none;
+ background-color: $in;
+
+ height: 0.25rem;
+ width: 0.25rem;
+
+ &:nth-child(1) {
+ top: -0.25rem; left: -0.25rem;
+ border-width: 0.25rem 0 0 0.25rem; }
+
+ &:nth-child(2) {
+ top: -0.25rem; right: -0.25rem;
+ border-width: 0.25rem 0.25rem 0 0; }
+
+ &:nth-child(3) {
+ bottom: -0.25rem; right: -0.25rem;
+ border-width: 0 0.25rem 0.25rem 0; }
+
+ &:nth-child(4) {
+ bottom: -0.25rem; left: -0.25rem;
+ border-width: 0 0 0.25rem 0.25rem; } } }
+
+@include forAspect(wide) {
+ body {
+ width: 73rem;
+ height: 37rem;
+ margin-top: 4rem;
+
+ #container {
+ height: 35rem;
+ width: 71rem;
+
+ section { margin: 0.5rem; }
+
+ section#description, section#levelSelect, section#controls {
+ width: 18rem; } } }
+}
+
+@include forAspect(landscape) {
+ body {
+ width: 73rem;
+ height: 37rem;
+ margin-top: 4rem;
+
+ #container {
+ height: 35rem;
+ width: 71rem; } }
+}
+
+@include forSize(ultraLarge) {
+ html { font-size: 125%; } }
+
+@include forSize(medium) {
+ html { font-size: 55%; } }
+
+@include forSize(notSmall) {
+ body {
+ margin-left: auto;
+ margin-right: auto; }
+}
+
+/* @include forSize(medium) {
+ html { font-size: 80%; }
+ body {
+ height: 36rem;
+ width: 62rem;
+ margin-top: 2rem;
+
+ #container {
+ height: 34rem;
+ width: 60rem; } } }
+
+@include forSize(large) {
+ body {
+ height: 36rem;
+ width: 62rem;
+ margin-top: 2rem;
+
+ #container {
+ height: 34rem;
+ width: 60rem; } } }
+
+@include forSize(ultraLarge) {
+ body {
+ height: 42rem;
+ width: 72rem;
+ margin-top: 4rem;
+
+ #container {
+ height: 40rem;
+ width: 70rem; } } }
+
+*/
+/*
+html {
+ background-color: white;
+ color: #354650;
+
+ font-family: sans-serif;
+}
+
+body {
+ padding: 1em;
+}
+
+header {
+ background-color: #1E4119;
+ color: #FFFB51;
+ padding: 0.5rem 1rem;
+ font-family: "Exo 2";
+ font-weight: 900;
+
+ position: absolute;
+ left: 0; right: 0; top: 0;
+}
+
+header > h1 { font-size: 300%; }
+
+canvas { border: solid thin gray; }
+
+section {
+ display: inline-block;
+ margin: 0.5rem;
+ position: relative; }
+
+section:first-of-type { margin-top: 6rem; }
+
+label {
+ display: inline-block;
+ width: 12rem; }
+
+input[type=button] {
+ position: absolute;
+ right: 0; }
+
+#editorControls { display: none; }
+
+@media (max-width: 600px) {
+ header { text-align: center; } }
+*/