Last active
May 4, 2018 13:15
-
-
Save raasoft/0adce6d1d1dba856cf947aba1c1de341 to your computer and use it in GitHub Desktop.
Snake Example
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!-- We are going to use a canvas to render at 60 fps --> | |
<div id="wrapper"> | |
<canvas id="canvas" widht="450" height="450"></canvas> | |
</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* Implementation of https://dart.academy/web-games-with-dart-and-the-html5-canvas/ */ | |
import 'dart:html'; | |
import 'dart:math'; | |
import 'dart:collection'; /* HashMap class */ | |
import 'dart:async'; | |
const int CELL_SIZE = 10; | |
CanvasElement canvas; | |
CanvasRenderingContext2D ctx; | |
Keyboard keyboard = new Keyboard(); | |
void main() { | |
canvas = querySelector('#canvas'); | |
ctx = canvas.getContext('2d'); | |
new Game()..run(); | |
} | |
/* Let's draw something */ | |
void drawCell(Point coords, String color) { | |
ctx..fillStyle = color | |
..strokeStyle = "white"; | |
final int x = coords.x * CELL_SIZE; | |
final int y = coords.y * CELL_SIZE; | |
ctx..fillRect(x, y, CELL_SIZE, CELL_SIZE) | |
..strokeRect(x, y, CELL_SIZE, CELL_SIZE); | |
} | |
void clear() { | |
ctx..fillStyle = "white" | |
..fillRect(0,0,canvas.width, canvas.height); | |
} | |
/* Let's handle keyboard input */ | |
class Keyboard { | |
HashMap<int, num> _keys = new HashMap<int, num>(); | |
Keyboard() { | |
window.onKeyDown.listen( | |
(KeyboardEvent event) { | |
_keys.putIfAbsent(event.keyCode, () => event.timeStamp); | |
}); | |
window.onKeyUp.listen((KeyboardEvent event) { | |
_keys.remove(event.keyCode); | |
}); | |
} | |
bool isPressed(int keyCode) => _keys.containsKey(keyCode); | |
} | |
/* The snake! */ | |
class Snake { | |
// directions | |
static const Point LEFT = const Point(-1, 0); | |
static const Point RIGHT = const Point(1, 0); | |
static const Point UP = const Point(0, -1); | |
static const Point DOWN = const Point(0, 1); | |
static const int START_LENGTH = 6; | |
// coordinates of the body segments | |
List<Point> _body; /* "_" means PRIVATE field */ | |
// current travel direction | |
Point _dir = RIGHT; | |
Snake() { /* This is the constructor of the snake */ | |
/* Generate coordinates for each segment of a new's snake body */ | |
int i = START_LENGTH - 1; | |
/* Generate START_LENGHT elements */ | |
/* Use a ```named constructor``` to generate a list: | |
* takes the number of element and a generator function. | |
* Each time the generator function is called, it returns a new | |
* Point object with x set to the current value of i and y set to 0. | |
* Just after each new Point is created, i is decremented in | |
* preparation for the next iteration. | |
*/ | |
_body = new List<Point>.generate(START_LENGTH, | |
(int index) => new Point(START_LENGTH - 1 - index, 0)); | |
} | |
Point get head => _body.first; | |
/* Private methods */ | |
void _checkInput() { | |
if (keyboard.isPressed(KeyCode.LEFT) && _dir != RIGHT) { | |
_dir = LEFT; | |
} | |
else if (keyboard.isPressed(KeyCode.RIGHT) && _dir != LEFT) { | |
_dir = RIGHT; | |
} | |
else if (keyboard.isPressed(KeyCode.UP) && _dir != DOWN) { | |
_dir = UP; | |
} | |
else if (keyboard.isPressed(KeyCode.DOWN) && _dir != UP) { | |
_dir = DOWN; | |
} | |
} | |
void _move() { | |
//add a new head segment | |
grow(); | |
// remove the tail segment | |
_body.removeLast(); | |
} | |
void _draw() { | |
// starting with the head, draw each body segment | |
for (Point p in _body) { | |
drawCell(p, "green"); | |
} | |
} | |
bool checkForBodyCollision() { | |
for (Point p in _body.skip(1)) { | |
if (p == head) { | |
return true; | |
} | |
} | |
return false; | |
} | |
/* Public methods */ | |
void grow() { | |
//add new head based on direction | |
_body.insert(0, head + _dir); | |
} | |
void update() { | |
_checkInput(); | |
_move(); | |
_draw(); | |
} | |
} | |
class Game { | |
// smaller numbers make the game run faster | |
static const num GAME_SPEED = 33; | |
num _lastTimeStamp = 0; | |
// a few convenience variables to simplify calculations | |
int _rightEdgeX; | |
int _bottomEdgeY; | |
Snake _snake; | |
Point _food; | |
Game() { | |
_rightEdgeX = canvas.width ~/ CELL_SIZE; | |
_bottomEdgeY = canvas.height ~/ CELL_SIZE; | |
init(); | |
} | |
void init() { | |
_snake = new Snake(); | |
_food = _randomPoint(); | |
} | |
Point _randomPoint() { | |
Random random = new Random(); | |
return new Point(random.nextInt(_rightEdgeX), | |
random.nextInt(_bottomEdgeY)); | |
} | |
void _checkForCollisions() { | |
// check for collision with food | |
if (_snake.head == _food) { | |
_snake.grow(); | |
_food = _randomPoint(); | |
} | |
// check death conditions | |
if (_snake.head.x <= -1 || | |
_snake.head.x >= _rightEdgeX || | |
_snake.head.y <= -1 || | |
_snake.head.y >= _bottomEdgeY || | |
_snake.checkForBodyCollision()) { | |
init(); | |
} | |
} | |
/* Dart uses Futures | |
* (sometimes called promises in other languages) | |
* to help manage and alleviate some of the pain in | |
* using callback functions. Because you imported dart:html, | |
* you have access to the browser's window object, but | |
* instead of calling requestAnimationFrame() and passing | |
* it a callback, you access the animationFrame getter, | |
* which returns a Future instance. | |
* | |
* Dart's await syntax is used to wait for the animationFrame | |
* Future to complete, which happens as soon as the browser's | |
* next redraw cycle begins. The completed Future will resolve | |
* to a numeric delta value representing the number of | |
* milliseconds elapsed since your app started. This number | |
* is then passed to the Game class's soon-to-be-written | |
* update() function. | |
* | |
* Game's update() will be called by the system as soon as | |
* it's ready to redraw the screen. When update() has finished | |
* its business, it will call run() again to request another | |
* frame, creating your game's main loop. | |
*/ | |
Future run() async { | |
update(await window.animationFrame); | |
} | |
void update(num delta) { | |
final num diff = delta - _lastTimeStamp; | |
if (diff > GAME_SPEED) { | |
_lastTimeStamp = delta; | |
clear(); | |
drawCell(_food, "blue"); | |
_snake.update(); | |
_checkForCollisions(); | |
} | |
// keep looping | |
run(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* Max out the size of the page so it's easy to center the canvas */ | |
html, body { | |
widh: 100%; | |
height: 100%; | |
margin: 0; | |
padding: 0; | |
} | |
/* Wrapper div target of all formatting */ | |
#wrapper { | |
width: 450px; | |
margin: auto; | |
border: solid thin black; | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment