Created
August 26, 2023 03:43
-
-
Save shakkurcwb/1050875b9bd4e46a78df740b1a9e6ef5 to your computer and use it in GitHub Desktop.
Predator vs Prey Game
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<title>Predator and Prey</title> | |
<style> | |
</style> | |
</head> | |
<body> | |
<canvas id="gameCanvas" width="400" height="400"></canvas> | |
<script> | |
let canvas = document.getElementById("gameCanvas"); | |
let ctx = canvas.getContext("2d"); | |
const CREATURE_SIZE = 10; | |
const AVAILABLE_WIDTH = canvas.width - CREATURE_SIZE; | |
const AVAILABLE_HEIGHT = canvas.height - CREATURE_SIZE; | |
class Creature { | |
constructor(type, x, y, color) { | |
this.type = type; | |
this.x = x; | |
this.y = y; | |
this.color = color; | |
} | |
draw() { | |
// ctx.fillStyle = this.color; | |
// ctx.fillRect(this.x, this.y, CREATURE_SIZE, CREATURE_SIZE); | |
ctx.beginPath(); | |
ctx.arc(this.x, this.y, CREATURE_SIZE, 0, 2 * Math.PI); | |
ctx.fillStyle = this.color; | |
ctx.fill(); | |
ctx.strokeStyle = 'white'; | |
ctx.stroke(); | |
ctx.closePath(); | |
} | |
move(x, y) { | |
this.x = x; | |
this.y = y; | |
this.afterMove(); | |
} | |
afterMove() { | |
// this.teleport(); | |
this.bounce(); | |
} | |
teleport() { | |
// when colliding with walls, respawn on the other side | |
if (this.x < 0) this.x = AVAILABLE_WIDTH; | |
if (this.x > AVAILABLE_WIDTH) this.x = 0; | |
if (this.y < 0) this.y = AVAILABLE_HEIGHT; | |
if (this.y > AVAILABLE_HEIGHT) this.y = 0; | |
} | |
bounce() { | |
// when colliding with walls, bounce back | |
if (this.x < 0) this.x = 0; | |
if (this.x > AVAILABLE_WIDTH) this.x = AVAILABLE_WIDTH; | |
if (this.y < 0) this.y = 0; | |
if (this.y > AVAILABLE_HEIGHT) this.y = AVAILABLE_HEIGHT; | |
} | |
} | |
const PREDATOR = "predator"; | |
const PREY = "prey"; | |
class Predator extends Creature { | |
static type = PREDATOR; | |
static color = "red"; | |
constructor(x, y) { | |
super(Predator.type, x, y, Predator.color); | |
} | |
hunt() { | |
let x = this.x; | |
let y = this.y; | |
const { | |
closestPrey, | |
closestDistance | |
} = this.lookAround(); | |
if (!closestPrey) { | |
// roam | |
let movement = Helper.getRandomMovement(); | |
this.move(x + movement.x, y + movement.y); | |
return; | |
} | |
// run away | |
let movement = Helper.getMovementTowards(this.x, this.y, closestPrey.x, closestPrey.y); | |
this.move(x + movement.x, y + movement.y); | |
if (closestDistance < 10) { | |
this.clone(); | |
} | |
} | |
lookAround() { | |
let closestPrey = null; | |
let closestDistance = null; | |
for (let i = 0; i < creatures.length; i++) { | |
if (creatures[i].type == Predator.type) continue; | |
let distance = Math.sqrt( | |
Math.pow(this.x - creatures[i].x, 2) + Math.pow(this.y - creatures[i].y, 2) | |
); | |
if (!closestDistance) { | |
closestDistance = distance; | |
} | |
if (!closestPrey) { | |
closestPrey = creatures[i]; | |
} | |
if (distance < closestDistance) { | |
closestDistance = distance; | |
closestPrey = creatures[i]; | |
} | |
} | |
return { | |
closestPrey, | |
closestDistance | |
}; | |
} | |
clone() { | |
const { | |
x, | |
y | |
} = Helper.getRandomPosition(); | |
const predator = new Predator(x, y); | |
creatures.push(predator); | |
} | |
} | |
class Prey extends Creature { | |
static type = PREY; | |
static color = "green"; | |
constructor(x, y) { | |
super(Prey.type, x, y, Prey.color); | |
} | |
survive() { | |
let x = this.x; | |
let y = this.y; | |
const { | |
closestPredator, | |
closestDistance | |
} = this.lookAround(); | |
if (!closestPredator) { | |
// roam | |
let movement = Helper.getRandomMovement(); | |
this.move(x + movement.x, y + movement.y); | |
return; | |
} | |
if (closestDistance > 100) { | |
// roam | |
let movement = Helper.getRandomMovement(); | |
this.move(x + movement.x, y + movement.y); | |
} else { | |
// run away | |
let movement = Helper.getMovementAway(this.x, this.y, closestPredator.x, closestPredator.y); | |
this.move(x + movement.x, y + movement.y); | |
} | |
if (closestDistance < 10) { | |
this.die(); | |
} | |
} | |
lookAround() { | |
let closestPredator = null; | |
let closestDistance = null; | |
for (let i = 0; i < creatures.length; i++) { | |
if (creatures[i].type == Prey.type) continue; | |
let distance = Math.sqrt( | |
Math.pow(this.x - creatures[i].x, 2) + Math.pow(this.y - creatures[i].y,2) | |
); | |
if (!closestDistance) { | |
closestDistance = distance; | |
} | |
if (!closestPredator) { | |
closestPredator = creatures[i]; | |
} | |
if (distance < closestDistance) { | |
closestDistance = distance; | |
closestPredator = creatures[i]; | |
} | |
} | |
return { | |
closestPredator, | |
closestDistance | |
}; | |
} | |
die() { | |
for (let i = 0; i < creatures.length; i++) { | |
if (creatures[i].x == this.x && creatures[i].y == this.y) { | |
creatures.splice(i, 1); | |
} | |
} | |
} | |
} | |
class Map { | |
constructor(width = 400, height = 400) { | |
this.width = width; | |
this.height = height; | |
} | |
clear() { | |
ctx.clearRect(0, 0, this.width, this.height); | |
} | |
draw() { | |
ctx.fillStyle = "black"; | |
ctx.fillRect(0, 0, this.width, this.height); | |
} | |
} | |
class Helper { | |
static getRandomPosition() { | |
const x = Math.floor(Math.random() * AVAILABLE_WIDTH); | |
const y = Math.floor(Math.random() * AVAILABLE_HEIGHT); | |
return { | |
x, | |
y | |
}; | |
} | |
static getRandomMovement() { | |
const random = Math.floor(Math.random() * 4); | |
if (random == 0) return { | |
x: 1, | |
y: 0 | |
}; | |
if (random == 1) return { | |
x: -1, | |
y: 0 | |
}; | |
if (random == 2) return { | |
x: 0, | |
y: 1 | |
}; | |
if (random == 3) return { | |
x: 0, | |
y: -1 | |
}; | |
return { | |
x: 0, | |
y: 0 | |
}; | |
} | |
static getDistance(x1, y1, x2, y2) { | |
return Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2)); | |
} | |
static getMovementTowards(x1, y1, x2, y2) { | |
let x = x1; | |
let y = y1; | |
if (x1 < x2) x += 1; | |
if (x1 > x2) x -= 1; | |
if (y1 < y2) y += 1; | |
if (y1 > y2) y -= 1; | |
return { | |
x, | |
y | |
}; | |
} | |
static getMovementAway(x1, y1, x2, y2) { | |
let x = x1; | |
let y = y1; | |
if (x1 < x2) x -= 1; | |
if (x1 > x2) x += 1; | |
if (y1 < y2) y -= 1; | |
if (y1 > y2) y += 1; | |
return { | |
x, | |
y | |
}; | |
} | |
} | |
class CreatureCollision { | |
constructor(prey, predator) { | |
this.prey = prey; | |
this.predator = predator; | |
} | |
check() { | |
if (this.predator.x == this.prey.x && this.predator.y == this.prey.y) { | |
return true; | |
} | |
return false; | |
} | |
} | |
const MAX_PREDATORS = 4; | |
const MAX_PREYS = 4; | |
const creatures = []; | |
for (let i = 0; i < MAX_PREDATORS; i++) { | |
const { | |
x, | |
y | |
} = Helper.getRandomPosition(); | |
const predator = new Predator(x, y); | |
creatures.push(predator); | |
} | |
for (let i = 0; i < MAX_PREYS; i++) { | |
const { | |
x, | |
y | |
} = Helper.getRandomPosition(); | |
const prey = new Prey(x, y); | |
creatures.push(prey); | |
} | |
let map = new Map(canvas.width, canvas.height); | |
function draw() { | |
map.clear(); | |
map.draw(); | |
for (let i = 0; i < creatures.length; i++) { | |
creatures[i].draw(); | |
} | |
} | |
function update() { | |
for (let i = 0; i < creatures.length; i++) { | |
if (creatures[i].type == PREDATOR) { | |
creatures[i].hunt(); | |
} | |
if (creatures[i].type == PREY) { | |
creatures[i].survive(); | |
} | |
} | |
draw(); | |
} | |
let frame = null; | |
function gameLoop() { | |
console.log('game loop'); | |
try { | |
update(); | |
} catch (e) { | |
console.log('error update', e); | |
stopGameLoop(); | |
return; | |
} | |
frame = requestAnimationFrame(gameLoop); | |
console.log('frame', frame); | |
} | |
function stopGameLoop() { | |
console.log('stop game loop'); | |
cancelAnimationFrame(frame); | |
} | |
gameLoop(); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment