Skip to content

Instantly share code, notes, and snippets.

@raasoft
Last active May 4, 2018 13:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save raasoft/0adce6d1d1dba856cf947aba1c1de341 to your computer and use it in GitHub Desktop.
Save raasoft/0adce6d1d1dba856cf947aba1c1de341 to your computer and use it in GitHub Desktop.
Snake Example
<!-- We are going to use a canvas to render at 60 fps -->
<div id="wrapper">
<canvas id="canvas" widht="450" height="450"></canvas>
</div>
/* 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();
}
}
/* 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