Snake
I was bored on an airplane a while ago, and I just wanted to do something mindless. I decided that playing Snake was a little too mindless, but writing Snake (and then playing it) was just the ticket.
Here’s the game up front. Press g
to start, use w
/a
/s
/d
to move, and
press the j
and k
keys to adjust the game speed. I found that bumping up the
difficulty a bit (by pressing k
a few times) makes the game a lot more fun.
The code was written in about 45 minutes on a plane, and it has some hallmarks
of that: constants scattered throughout the code, both Snake
and CanvasView
tracking the canvas size, etc. Nonetheless, it passes the main test I have for
it: that I find it fun.
class CanvasView {
constructor (el, width, height, pxSize=5) {
this._canvas = el;
this._canvas.width = width;
this._canvas.height = height;
this._ctx = this._canvas.getContext('2d');
this._ctx.fillStyle = 'black';
this._pxSize = pxSize;
}
pxSize() {
return this._pxSize;
}
drawPixel(x, y) {
this._ctx.fillRect(x * this._pxSize, y * this._pxSize, this._pxSize, this._pxSize);
}
clearPixel(x, y) {
this._ctx.clearRect(x * this._pxSize, y * this._pxSize, this._pxSize, this._pxSize);
}
clear() {
this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height);
}
}
Direction = {
"UP" : 0,
"RIGHT": 1,
"DOWN": 2,
"LEFT": 3
}
class Snake {
constructor () {
this._height = 101;
this._width = 101;
this._canvas = new CanvasView(document.querySelector('canvas'), 505, 505);
this.running = false;
this.reset();
}
reset() {
this._x = 50;
this._y = 50;
this._direction = Direction.RIGHT
this._points = []; /* from tail to head */
this._length = 10;
this._interval = 150;
this.setTreat();
}
static toPoint(x, y) {
return {"x": x, "y": y}
}
isAtPoint(x, y) {
return this._points.filter(function(pt) { return pt.x == x && pt.y == y }).length > 0
}
step() {
if (this._direction == Direction.UP) this._y -= 1
if (this._direction == Direction.RIGHT) this._x += 1
if (this._direction == Direction.DOWN) this._y += 1
if (this._direction == Direction.LEFT) this._x -= 1
if (this._x < 0 || this._x >= this._width || this._y < 0 || this._y >= this._height) { this.die(); }
if (this.isAtPoint(this._x, this._y)) { this.die(); }
if (this.isAtPoint(this._treat.x, this._treat.y)) { this.eatTreat(); }
this._canvas.drawPixel(this._x, this._y)
this._points.push(Snake.toPoint(this._x, this._y))
if (this._points.length > this._length) {
let pt = this._points.shift()
this._canvas.clearPixel(pt.x, pt.y)
}
}
setTreat() {
this._treat = Snake.toPoint(Math.floor(Math.random() * 101), Math.floor(Math.random() * 101));
this._canvas.drawPixel(this._treat.x, this._treat.y);
}
eatTreat() {
this._length = Math.floor(this._length * 1.5);
this.setTreat();
}
handleKeyPress(e) {
if (this.running) {
if (e.key == "w") this.turn(Direction.UP)
if (e.key == "d") this.turn(Direction.RIGHT)
if (e.key == "s") this.turn(Direction.DOWN)
if (e.key == "a") this.turn(Direction.LEFT)
if (e.key == "j") this.changeSpeed(1.75)
if (e.key == "k") this.changeSpeed(0.75)
}
else {
if (e.key == "g") this.start()
}
}
turn(d) {
if (d == Direction.UP && this._direction == Direction.DOWN) this.die()
if (d == Direction.RIGHT && this._direction == Direction.LEFT) this.die()
if (d == Direction.DOWN && this._direction == Direction.UP) this.die()
if (d == Direction.LEFT && this._direction == Direction.RIGHT) this.die()
this._direction = d;
}
changeSpeed(multFactor) {
snake._interval *= multFactor;
clearInterval(this._step_timer);
snake._step_timer = setInterval(function() {snake.step()}, this._interval)
}
die() {
clearInterval(this._step_timer);
this.running = false;
this._canvas.clear();
this.reset();
}
start() {
snake._step_timer = setInterval(function() {snake.step()}, this._interval)
this.running = true
}
}
snake = new Snake();
window.onkeypress = function(e) {snake.handleKeyPress(e)}