Skip to content

Instantly share code, notes, and snippets.

@melwyn95
Last active March 8, 2020 15:10
Show Gist options
  • Save melwyn95/e2452350eb90adfc45bbffeb18537c84 to your computer and use it in GitHub Desktop.
Save melwyn95/e2452350eb90adfc45bbffeb18537c84 to your computer and use it in GitHub Desktop.
Snake Pub Sub
<!DOCTYPE html>
<html>
<head>
<title>Snake Pub Sub</title>
</head>
<body>
<canvas id="canvas" height="500" width="500">
</canvas>
<script src='main.js'></script>
</body>
</html>
const HEIGTH = 500
const WIDTH = 500
const SIZE = 10
const FPS = 12
function Stream() {
const subscribers = []
return {
publish(event) {
subscribers.forEach(sub => sub && sub(event))
},
subscribe(fn) {
fn && subscribers.push(fn)
}
}
}
function CanvasRenderer(ctx) {
return {
render(cells, color, width = SIZE, height = SIZE) {
const fillStyle = ctx.fillStyle
ctx.fillStyle = color
cells.forEach(([x, y]) => {
ctx.fillRect(x, y, width, height)
})
ctx.fillStyle = fillStyle
}
}
}
function initKeyBoardListeners(stream) {
const opposites = {
"up": "down",
"down": "up",
"left": "right",
"right": "left"
}
function direction({ key }) {
switch (key) {
case "ArrowRight": return "right"
case "ArrowLeft": return "left"
case "ArrowUp": return "up"
case "ArrowDown": return "down"
default: return undefined
}
}
function validateDirection(current, next) {
if (opposites[current] === next) {
stream.publish({ type: "stopEmitter" })
return false
}
return true
}
document.addEventListener("keydown", function (event) {
// Only for debugging
if (event.key === " ") return stream.publish({ type: "stopEmitter" })
const dir = direction(event)
validateDirection(snake.direction(), dir)
&& stream.publish({ type: "moveSnake", payload: { direction: dir } })
})
}
function FrameEmitter(stream) {
let interval;
return {
start() {
interval = setInterval(() => {
stream.publish({ type: "moveSnake" })
stream.publish({ type: "renderSnake" })
}, parseInt(1000 / FPS))
},
stop({ type }) {
if (type === "stopEmitter") {
clearInterval(interval)
}
}
}
}
function Snake() {
const snake = [[0, 0]]
let __direction__ = "right";
return {
get() {
return snake
},
head() {
return snake[0];
},
tail() {
return snake.slice(1)
},
last() {
return snake[snake.length - 1]
},
direction() {
return __direction__
},
__nextHead__(direction) {
const [hx, hy] = this.head()
switch (direction || __direction__) {
case "right": return [hx + SIZE, hy ]
case "left": return [hx - SIZE, hy ]
case "up": return [hx , hy - SIZE]
case "down": return [hx , hy + SIZE]
}
},
__nextTail__() {
return snake.slice(0, snake.length - 1)
},
canMove() {
const [hx, hy] = this.__nextHead__()
return !this.dead([hx, hy])
&& (hx >= 0 && hx <= WIDTH)
&& (hy >= 0 && hy <= HEIGTH)
},
move({ type, payload: { direction } = {} }) {
if (type === "moveSnake") {
const head = this.__nextHead__(direction)
snake.unshift(head)
snake.pop()
direction && (__direction__ = direction)
}
},
grow({ type, payload: head }) {
if (type === "growSnake") {
head && snake.unshift(head)
}
},
check([x, y] = []) {
return snake.some(([sx, sy]) => sx === x && sy === y)
},
dead([hx, hy]) {
console.log(snake, [hx, hy], this.__nextTail__())
return this.__nextTail__().some(([tx, ty]) => tx === hx && ty === hy)
},
}
}
function Food() {
let food;
return {
get() {
return food
},
generate() {
const x = parseInt((Math.random() * (WIDTH)) / SIZE) * SIZE
const y = parseInt((Math.random() * (HEIGTH)) / SIZE) * SIZE
food = [x, y]
return food
}
}
}
const stream = new Stream()
stream.subscribe(console.log)
initKeyBoardListeners(stream)
const canvas = document.getElementById("canvas")
const ctx = canvas.getContext("2d")
const frameEmitter = FrameEmitter(stream)
frameEmitter.start()
stream.subscribe(frameEmitter.stop)
const canvasRenderer = new CanvasRenderer(ctx)
canvasRenderer.render([[0, 0]], "black", HEIGTH, WIDTH)
const snake = new Snake()
const food = new Food()
stream.subscribe(({ type }) => {
if (type === "renderFood") {
const f = food.generate()
canvasRenderer.render([f], "orange")
}
})
stream.publish({ type: "renderFood" })
stream.subscribe(({ type }) => {
if (type === "renderSnake") {
canvasRenderer.render(snake.get(), "white")
}
})
stream.subscribe(snake.grow)
stream.publish({ type: "renderSnake" })
stream.subscribe(({ type }) => {
if (type === "moveSnake") {
if (!snake.canMove()) {
stream.publish({ type: "stopEmitter" })
}
const f = food.get()
if (snake.check(f)) {
stream.publish({ type: "growSnake", payload: f })
stream.publish({ type: "renderFood" })
}
canvasRenderer.render([snake.last()], "black")
}
})
stream.subscribe(snake.move.bind(snake))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment