Skip to content

Instantly share code, notes, and snippets.

@tiagofrancafernandes
Created October 23, 2023 16: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 tiagofrancafernandes/a8cf7cd2f7751b9159a5cb36e6a392ed to your computer and use it in GitHub Desktop.
Save tiagofrancafernandes/a8cf7cd2f7751b9159a5cb36e6a392ed to your computer and use it in GitHub Desktop.
Snake game
<!DOCTYPE html>
<html>
<head>
<title>
Snake Game
</title>
<meta name="author" content="sirb alin cristian"/>
<meta name="description" content="snake game project"/>
<meta name="keywords" content="snake, game, project"/>
<meta name="viewport" content="width=device-width, initial-scale= 1.0"/>
<meta charset="utf-8"/>
<link rel="stylesheet" type="text/css" href="./src/style.css"/>
</head>
<body>
<canvas height="512" width="512" id="gameCanvas"></canvas>
<script type="text/javascript" src="./src/main.js"/></script>
</body>
</html>
console.log("Snake game project");
alert("Welcome to snake game!");
let cnv = document.getElementById('gameCanvas'),
ctx = cnv.getContext('2d'),
segmentLength = 10,
startingSegments = 20,
spawn = { x: 0, y:cnv.height/2 },
snakeSpeed = 5,
maxApples = 5,
appleLife = 500,
segmentsPerApple = 3,
snakeWidth = 5,
appleWidth = 5,
cursorSize = 10,
snakeColor = [ 100, 255, 100, 1],
appleColor = [ 0, 255, 0, 1],
cursorColor = [ 255, 255, 255, 1],
cursorColorMod = [ 0,-255,-255, 0],
targetColor = [ 0, 0, 255, 1],
targetColorMod = [ 255, 0,-255, 0],
scoreColor = [ 255, 255, 255, 1],
cursorSpin = 0.075,
snake,
cursor,
target,
apples,
score,
gameState,
deathMeans;
function distance(p1,p2){
let dx = p2.x-p1.x;
let dy = p2.y-p1.y;
return Math.sqrt(dx*dx + dy*dy);
}
function lineIntersect(p0_x, p0_y, p1_x, p1_y, p2_x, p2_y, p3_x, p3_y) {
let s1_x = p1_x - p0_x,
s1_y = p1_y - p0_y,
s2_x = p3_x - p2_x,
s2_y = p3_y - p2_y,
s = (-s1_y * (p0_x - p2_x) + s1_x * (p0_y - p2_y)) / (-s2_x * s1_y + s1_x * s2_y),
t = ( s2_x * (p0_y - p2_y) - s2_y * (p0_x - p2_x)) / (-s2_x * s1_y + s1_x * s2_y);
if (s >= 0 && s <= 1 && t >= 0 && t <= 1) {
return true;
}
return false;
}
function SGM(angle, x, y) {
this.x = x || 0;
this.y = y || 0;
this.angle = angle || 0;
this.parent = null;
};
SGM.prototype.endX = function() {
return this.x + Math.cos(this.angle) * segmentLength;
};
SGM.prototype.endY = function() {
return this.y + Math.sin(this.angle) * segmentLength;
};
SGM.prototype.pointAt = function(x, y) {
let dx = x - this.x,
dy = y - this.y;
this.angle = Math.atan2(dy, dx);
};
SGM.prototype.target = function(x, y) {
this.targetX = x;
this.targetY = y;
this.arrived = false;
this.totalDist = distance({x:this.endX(), y: this.endY()}, {x: this.targetX, y: this.targetY});
this.currentDist = parseInt(this.totalDist);
};
SGM.prototype.gotoTarget = function() {
if(!this.arrived) {
if(this.targetX > this.x + segmentLength || this.targetX < this.x - segmentLength || this.targetY > this.y + segmentLength || this.targetY < this.y - segmentLength) {
this.pointAt(this.targetX, this.targetY);
}
else {
this.arrived = true;
}
this.currentDist = distance({x:this.endX(), y: this.endY()}, {x: this.targetX, y: this.targetY});
}
this.x += (this.endX() - this.x) / snakeSpeed;
this.y += (this.endY() - this.y) / snakeSpeed;
this.parent.drag(this.x, this.y);
};
SGM.prototype.drag = function(x, y) {
this.pointAt(x, y);
this.x = x - Math.cos(this.angle) * segmentLength;
this.y = y - Math.sin(this.angle) * segmentLength;
if(this.parent) {
this.parent.drag(this.x, this.y);
}
};
SGM.prototype.render = function(context) {
context.lineTo(this.endX(), this.endY());
};
function IKR(x, y) {
this.ix = x || 0;
this.iy = y || 0;
this.sgms = [];
this.lastArm = null;
};
IKR.prototype.addSeg = function(angle) {
let arm = new SGM(angle);
if(this.lastArm !== null) {
arm.x = this.lastArm.endX();
arm.y = this.lastArm.endY();
arm.parent = this.lastArm;
}
else {
arm.x = this.ix;
arm.y = this.iy;
}
this.sgms.push(arm);
this.lastArm = arm;
};
IKR.prototype.grow = function() {
let tail = this.sgms[0],
arm = new SGM(tail.angle);
arm.x = tail.x - Math.cos(tail.angle) * segmentLength;
arm.y = tail.y - Math.sin(tail.angle) * segmentLength;
tail.parent = arm;
this.sgms.unshift(arm);
}
IKR.prototype.drag = function(x, y) {
this.lastArm.drag(x, y);
};
function CUR(x,y) {
this.x = x;
this.y = y;
this.rotation = 0;
};
CUR.prototype.render = function(context) {
context.save();
context.translate(this.x, this.y);
context.rotate(this.rotation);
context.beginPath();
context.moveTo(0, -cursorSize);
context.lineTo(0, -cursorSize/2);
context.moveTo(0, cursorSize/2);
context.lineTo(0, cursorSize);
context.moveTo(-cursorSize, 0);
context.lineTo(-cursorSize/2, 0);
context.moveTo(cursorSize/2, 0);
context.lineTo(cursorSize, 0);
context.stroke();
context.restore();
this.rotation = (this.rotation + cursorSpin) % 360;
};
function Apple(x, y) {
this.x = x;
this.y = y;
this.life = appleLife;
this.rotation = 0;
}
Apple.prototype.update = function() {
this.life--;
};
Apple.prototype.render = function(context) {
context.beginPath();
context.arc(this.x, this.y, appleWidth, 0, Math.PI*2);
context.fill();
if(gameState !== 'dead') {
context.save();
context.fillStyle = 'white';
context.font = '8px sans-serif';
context.fillText(this.life, this.x+10, this.y+10);
context.restore();
CUR.prototype.render.call(this, context);
}
};
function init() {
snake = new IKR(spawn.x, spawn.y);
cursor = new CUR(-20, -20);
target = new CUR(spawn.x + segmentLength * (startingSegments + 5), spawn.y);
apples = [];
score = 0;
for(let i = 0; i < startingSegments; i++) {
snake.addSeg();
}
snake.lastArm.target(target.x, target.y);
gameState = 'play';
}
init();
cnv.addEventListener('mousemove', function(e) {
switch(gameState) {
case 'play':
cursor.x = e.offsetX;
cursor.y = e.offsetY;
break;
}
});
cnv.addEventListener('mousedown', function(e) {
switch(gameState) {
case 'play':
target.x = e.offsetX;
target.y = e.offsetY;
snake.lastArm.target(target.x, target.y);
break;
case 'dead':
init();
break;
}
});
function badPlacement(apple) {
for(let s = 0; s < snake.sgms.length; s++) {
let seg = snake.sgms[s];
if(Math.min(distance(apple, {x:seg.endX(), y:seg.endY()}), distance(apple, {x:seg.x,y:seg.y})) < appleWidth*2) {
return true;
}
}
return false;
}
function addScoreSegments() {
for(let i = 0; i < segmentsPerApple; i++) {
snake.grow();
}
}
function update() {
if(gameState !== 'dead') {
snake.lastArm.gotoTarget();
if(snake.lastArm.endX() > cnv.width - 2 || snake.lastArm.endX() < 2 || snake.lastArm.endY() > cnv.height - 2 || snake.lastArm.endY() < 2) {
gameState = 'dead';
deathMeans = 'You hit the wall...';
return;
}
for(let s = 0; s < snake.sgms.length-2; s++) {
let seg = snake.sgms[s];
if(lineIntersect(snake.lastArm.x, snake.lastArm.y, snake.lastArm.endX(), snake.lastArm.endY(), seg.x, seg.y, seg.endX(), seg.endY())) {
gameState = 'dead';
deathMeans = 'You bit yourself!';
return;
}
for(let a in apples) {
let apple = apples[a];
if(Math.min(distance(apple, {x:seg.endX(), y:seg.endY()}), distance(apple, {x:seg.x,y:seg.y})) < appleWidth*2) {
score += Math.round(apple.life/2); // half score if absorbed by the tail
apples.splice(a, 1);
addScoreSegments();
}
}
}
for(let a in apples) {
let apple = apples[a];
apple.update();
if(apple.life <= 0) {
apples.splice(a,1);
continue;
}
if(distance(apple,{x:snake.lastArm.endX(),y:snake.lastArm.endY()}) < appleWidth*2) {
score += apple.life;
apples.splice(a,1);
addScoreSegments();
}
}
if(apples.length < maxApples && Math.random()<.1) {
let offset = appleWidth*10,
apple = new Apple(
offset/2+Math.floor(Math.random()*(cnv.width-offset)),
offset/2+Math.floor(Math.random()*(cnv.height-offset))
);
while(badPlacement(apple)) {
apple.x = offset/2+Math.floor(Math.random()*(cnv.width-offset));
apple.y = offset/2+Math.floor(Math.random()*(cnv.height-offset));
}
apples.push(apple);
}
}
}
function drawTarget(targetModFactor) {
if(!snake.lastArm.arrived) {
ctx.strokeStyle = 'rgba('+
(targetColor[0] + targetColorMod[0]*targetModFactor).toFixed(0) + ',' +
(targetColor[1] + targetColorMod[1]*targetModFactor).toFixed(0) + ',' +
(targetColor[2] + targetColorMod[2]*targetModFactor).toFixed(0) + ',' +
(targetColor[3] + targetColorMod[3]*targetModFactor).toFixed(0)
+')';
ctx.lineWidth = 1;
target.render(ctx);
}
}
function drawSnake() {
ctx.beginPath();
ctx.strokeStyle = ctx.fillStyle = 'rgba('+ snakeColor[0] +','+ snakeColor[1] +','+ snakeColor[2] +','+ snakeColor[3]+')';
ctx.lineWidth = snakeWidth;
ctx.moveTo(snake.sgms[0].x, snake.sgms[0].y);
for(let s in snake.sgms) {
snake.sgms[s].render(ctx);
}
ctx.stroke();
ctx.beginPath();
ctx.arc(snake.lastArm.endX(), snake.lastArm.endY(), appleWidth*.75, 0, Math.PI*2);
ctx.fill();
}
function drawCursor(targetModFactor) {
ctx.strokeStyle = 'rgba('+
(cursorColor[0] + cursorColorMod[0]*targetModFactor).toFixed(0) + ',' +
(cursorColor[1] + cursorColorMod[1]*targetModFactor).toFixed(0) + ',' +
(cursorColor[2] + cursorColorMod[2]*targetModFactor).toFixed(0) + ',' +
(cursorColor[3] + cursorColorMod[3]*targetModFactor).toFixed(0)
ctx.lineWidth = 1;
cursor.render(ctx);
}
function drawApples() {
ctx.fillStyle = 'rgba('+
appleColor[0] +','+
appleColor[1] +','+
appleColor[2] +','+
appleColor[3]
+')';
for(let a in apples) {
let apple = apples[a],
appleTargetMod = 1 - apple.life / appleLife;
ctx.strokeStyle = 'rgba('+
(cursorColor[0] + cursorColorMod[0]*appleTargetMod).toFixed(0) + ',' +
(cursorColor[1] + cursorColorMod[1]*appleTargetMod).toFixed(0) + ',' +
(cursorColor[2] + cursorColorMod[2]*appleTargetMod).toFixed(0) + ',' +
(cursorColor[3] + cursorColorMod[3]*appleTargetMod).toFixed(0)
+')';
ctx.lineWidth = 1;
apple.render(ctx);
}
}
function render() {
let targetModFactor = 1 - snake.lastArm.currentDist / snake.lastArm.totalDist;
switch(gameState) {
case 'play':
ctx.clearRect(0, 0, cnv.width, cnv.height);
drawTarget(targetModFactor);
drawSnake();
drawApples();
drawCursor(targetModFactor);
ctx.fillStyle = 'rgba('+
scoreColor[0] +','+
scoreColor[1] +','+
scoreColor[2] +','+
scoreColor[3]
+')';
ctx.textAlign = 'left';
ctx.textBaseline = 'top';
ctx.fillText('Score: '+score, 10, 10);
break;
case 'dead':
ctx.clearRect(0, 0, cnv.width, cnv.height);
drawSnake();
drawApples();
ctx.fillStyle = 'rgba(255,0,0,0.5)';
ctx.fillRect(100, 100, cnv.width - 200, cnv.height - 200);
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillStyle = ctx.strokeStyle = 'white';
ctx.font = 'bold 30px sans-serif'
ctx.fillText('DEAD', cnv.width/2, cnv.height/2-70);
ctx.font = 'bold 25px sans-serif'
ctx.fillText(deathMeans, cnv.width/2, cnv.height/2-30);
ctx.font = '20px sans-serif';
ctx.fillText('Score:', cnv.width/2, cnv.height/2+15);
ctx.font = 'bold 60px sans-serif';
ctx.fillText(score, cnv.width/2, cnv.height/2+60);
ctx.font = 'lighter 10px sans-serif';
ctx.lineWidth = 1;
ctx.strokeRect(103, 103, cnv.width - 206, cnv.height - 206);
break;
}
}
function animate() {
update();
render();
requestAnimationFrame(animate);
}
snake.lastArm.target(target.x, target.y);
animate();
* {
box-sizing: border-box;
}
html, body {
margin: 0;
height: 100%;
background-color: dodgerblue;
}
@media only screen and(width:auto) {
body {
height: auto;
}
}
.clearfix {
content: "";
clear: both;
display: flex;
}
body {
display: -webkit-box;
display: flex;
-webkit-box-pack: center; justify-content: center;
-webkit-box-align: center; align-items: center;
}
canvas {
background: lightgray;
font-size: 2vw;
box-shadow: 1em 1em 0.5em rgba(0, 0, 0, 0.25);
outline: 1px solid white;
outline-offset: -2px;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment