Skip to content

Instantly share code, notes, and snippets.

@cfreshman
Created May 8, 2024 13:37
Show Gist options
  • Select an option

  • Save cfreshman/715c15667a25849df5b6444db85de77b to your computer and use it in GitHub Desktop.

Select an option

Save cfreshman/715c15667a25849df5b6444db85de77b to your computer and use it in GitHub Desktop.
the most basic web implementation of snake i could come up with. play at https://freshman.dev/snake_ultra_hydra.html (desktop only)
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>snake ultra hydra</title>
<link rel="icon" href="https://i.guim.co.uk/img/media/993cc4a2107b870f78d1228874906ad9646fb204/0_144_2160_1296/master/2160.jpg?width=1200&height=1200&quality=85&auto=format&fit=crop&s=708569290282f1d206da9d72d5c2046c">
<style>body{display:flex;flex-direction:column;margin:.5em;font-family:system-ui}</style>
<style>
#game-canvas {
image-rendering: pixelated;
/* 30rem by default, but shrink to window height w/ .5em margin */
width: min(30rem, calc(100vh - 1em));
height: min(30rem, calc(100vh - 1em));
}
</style>
<script>
// COMMON CODE
// truthy: interpret the value as a boolean
const truthy = x => Boolean(x)
// V2: a two-dimensional vector value
// - add: add to another vector
// - equals: check if this vector equals another vector (e.g. same position)
class V2 {
constructor(x_or_vector, y) {
let x, vector
if (typeof x_or_vector === 'number') {
x = x_or_vector
} else {
vector = x_or_vector
}
if (vector) {
// allow copying another vector
this.x = vector.x
this.y = vector.y
} else {
this.x = x
this.y = y
}
}
add(other) {
return new V2(this.x + other.x, this.y + other.y)
}
equals(other) {
return this.x === other.x && this.y === other.y
}
}
// ListNode: linked list implementation
// store reference to first node to access entire list
class ListNode {
constructor(value, next) {
this.value = value
this.next = next
}
map(func) {
let curr = this
const results = []
while (curr) {
results.push(func(curr.value))
curr = curr.next
}
return results
}
}
</script>
</head>
<body>
<div>
WASD or ARROW KEYS to move. any other key to reset
</div>
<canvas id="game-canvas"></canvas>
<div>
(DESKTOP ONLY)
</div>
<script>
// board size 20x20 pixels
const SIZE = 20
// cardinal directions the snake can move in
const DIRS = {
RIGHT: new V2(1, 0),
UP: new V2(0, -1),
LEFT: new V2(-1, 0),
DOWN: new V2(0, 1),
}
// set the canvas to SIZE width and height
const canvas = document.querySelector('#game-canvas')
canvas.width = canvas.height = SIZE
// get the canvas graphics context for drawing the game pixels
const ctx = canvas.getContext('2d')
// game colors
const COLORS = {
BACKGROUND: '#000000',
SNAKE: '#34eb7d',
APPLE: '#ff0000',
}
// Pixel: a drawable position
// - draw: draw a colored square at this position
// - randomize: move pixel to random position
class Pixel extends V2 {
constructor(x, y, color) {
super(x, y)
this.color = color
}
draw(ctx, color) {
ctx.fillStyle = color
ctx.fillRect(this.x, this.y, 1, 1)
return this
}
randomize() {
this.x = Math.floor(Math.random() * SIZE)
this.y = Math.floor(Math.random() * SIZE)
return this
}
}
// snake behaviors/functions
function snake_move(head, dir, do_grow=false) {
// moving the snake:
// body nodes don't actually change
// a new head is added to the front (in input direction)
// if the snake isn't growing, the last node is removed
const new_head = new ListNode(new Pixel(head.value.x + dir.x, head.value.y + dir.y, head.color), head)
if (!do_grow) {
let curr = head
while (curr.next.next) curr = curr.next
curr.next = undefined
}
return new_head
}
function snake_intersects(head, position) {
// true if any body parts are placed at the position
return head ? head.map(part => part.equals(position)).some(truthy) : false
}
// === GAME STATE ===
// start the snake heading towards the right
let head = new ListNode(new Pixel(SIZE/4, SIZE/2, COLORS.SNAKE))
let direction = DIRS.RIGHT
// how many new segments to grow
let growth = 2
// place the apple at a random position
const apple = new Pixel(0, 0, COLORS.APPLE).randomize()
// is the game over
let gameover = false
// last is used to avoid letting the snake do a 180
let last = direction
// === GAME LOOP ===
function loop() {
if (gameover) return
// we check against 'last' in the input handler to prevent a 180
// 'last' allows us to update direction immediately but always check the new direction against the actual last direction
last = direction
// move the snake
// gameover if snake intersects itself or went out of bounds
head = snake_move(head, direction, growth)
if (growth > 0) growth--
if (snake_intersects(head.next, head.value)
|| head.value.x < 0 || head.value.x > SIZE-1
|| head.value.y < 0 || head.value.y > SIZE-1) {
gameover = true
return
}
// check if the head is at the same position as the apple
// if so, grow the body and move the apple to a new location
if (head.value.equals(apple)) {
growth += 1
do {
apple.randomize()
} while (snake_intersects(head, apple))
}
// draw the scene:
// 1) cover the entire scene with black
// 2) fill each snake segment with green
// 3) fill the apple position with red
ctx.fillStyle = COLORS.BACKGROUND
ctx.fillRect(0, 0, SIZE, SIZE)
head.map(part => part.draw(ctx, COLORS.SNAKE))
apple.draw(ctx, COLORS.APPLE)
}
// === GAME INPUT ===
// listen for pressed keys to re-direct the snake
window.addEventListener('keydown', e => {
switch (e.key) {
case 'w': case 'ArrowUp':
if (last !== DIRS.DOWN) direction = DIRS.UP
break
case 'a': case 'ArrowLeft':
if (last !== DIRS.RIGHT) direction = DIRS.LEFT
break
case 's': case 'ArrowDown':
if (last !== DIRS.UP) direction = DIRS.DOWN
break
case 'd': case 'ArrowRight':
if (last !== DIRS.LEFT) direction = DIRS.RIGHT
break
default:
if (gameover) location.reload()
break
}
})
// run loop() 10 times per second (every 100ms) (snake moves 10 cells/sec)
setInterval(loop, 100)
</script>
</body>
</html>
@guestpostingagency799-lgtm

Copy link
Copy Markdown

Amazing tool that makes creative designs simple and enjoyable for everyone. Minecraft font generator provides unique styles, easy customization, and impressive results that enhance gaming projects, artwork, and personal creations with a professional appearance. It is truly helpful for fans.

@horaihero1

Copy link
Copy Markdown

steermouse has completely transformed how I navigate software solutions on this website. The detailed guides, clean downloads, and responsive user layout make exploring complex utility tools an absolute breeze. I consistently rely on this resource for accurate tech tutorials and seamless navigation, making it a favorite bookmark in my digital workflow.

@horaihero1

Copy link
Copy Markdown

I am truly impressed by how thoroughly flashingtech is discussed on your platform. The article delivers clear, reliable information that helps readers navigate technical setup steps with confidence. Your website continues to be a trusted hub for modern technology guides.

@horaihero1

Copy link
Copy Markdown

As someone who regularly explores advanced software configurations and technical utilities, I am thoroughly impressed by your website's comprehensive coverage of exp soundboard. The depth of information provided alongside seamless navigation makes it a go-to destination for reliable tech insights. Thank you for maintaining such a professional, helpful, and high quality digital resource for users worldwide.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment