From 68b7bf8da8a471f6f97a4b05b29b370f3b8f6aee Mon Sep 17 00:00:00 2001 From: Joanthan Bernard Date: Thu, 11 Dec 2014 22:52:55 -0600 Subject: [PATCH] Started on a GUI redesign of Snake. --- resources/css/forSize.mixin.scss | 44 ++++++ snake/snake-redesign.html | 28 ++++ snake/snake.html | 58 +------- snake/snake.js | 65 ++++++--- snake/snake.scss | 228 +++++++++++++++++++++++++++++++ 5 files changed, 350 insertions(+), 73 deletions(-) create mode 100644 resources/css/forSize.mixin.scss create mode 100644 snake/snake-redesign.html create mode 100644 snake/snake.scss 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 @@ + + + + + + + +
+
+
+

SNAKE

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

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; } } +*/