451 lines
11 KiB
JavaScript
451 lines
11 KiB
JavaScript
export function createSketch(p, onGameData) {
|
|
//cell setting
|
|
let cellSize = 20;
|
|
|
|
let snake = [
|
|
{x: 10, y: 5},
|
|
{x: 9, y: 5},
|
|
{x: 8, y: 5},
|
|
{x: 7, y: 5},
|
|
{x: 6, y: 5},
|
|
{x: 5, y: 5},
|
|
{x: 4, y: 5},
|
|
{x: 3, y: 5}
|
|
]; // initial length = 8
|
|
|
|
let headImg, bodyImg, cornerImg, tailImg, obstacleImg, heartImg, gemstoneImg, sunImg;
|
|
|
|
//load images
|
|
p.preload = () => {
|
|
headImg = p.loadImage("/assets/segments/head.png");
|
|
bodyImg = p.loadImage("/assets/segments/body.png");
|
|
cornerImg = p.loadImage("/assets/segments/corner.png");
|
|
tailImg = p.loadImage("/assets/segments/tail.png");
|
|
obstacleImg = p.loadImage("/assets/obstacle.png");
|
|
heartImg = p.loadImage("/assets/items/heart.png");
|
|
gemstoneImg = p.loadImage("/assets/items/gemstone.png");
|
|
sunImg = p.loadImage("/assets/items/sun.png");
|
|
};
|
|
|
|
//game state variables
|
|
let life = 3;
|
|
let score = 0;
|
|
let speed = 8;
|
|
let obstacles = [];
|
|
let obstacleInterval = 100;
|
|
let walls = [];
|
|
let items = [];
|
|
let growNext = false;
|
|
let dirHead = { x: 1, y: 0 };
|
|
let dirTail = { x: -1, y: 0 };
|
|
let gameState = "play";
|
|
|
|
|
|
//game data
|
|
function updateGameData() {
|
|
if (typeof onGameData == "function") {
|
|
onGameData({life, score, state: gameState});
|
|
}
|
|
}
|
|
|
|
//rotation direction
|
|
function rotationByDir(dir) {
|
|
if (dir.x === 1 && dir.y === 0) return 0;
|
|
if (dir.x === 0 && dir.y === 1) return p.HALF_PI;
|
|
if (dir.x === -1 && dir.y === 0) return p.PI;
|
|
if (dir.x === 0 && dir.y === -1) return -p.HALF_PI;
|
|
return 0;
|
|
}
|
|
|
|
//make obstacle
|
|
function makeObstacle() {
|
|
// moving head or tail? which direction?
|
|
let base;
|
|
let dir;
|
|
|
|
if (dirHead.x !==0 || dirHead.y !== 0) {
|
|
base = snake[0];
|
|
dir = dirHead;
|
|
}
|
|
else if (dirTail.x !== 0 || dirTail.y !== 0){
|
|
base = snake[snake.length - 1];
|
|
dir = dirTail;
|
|
}
|
|
else {
|
|
return;
|
|
}
|
|
|
|
//gap between the snake and an obstacle
|
|
const gap = p.floor(p.random(3, 7));
|
|
let newX = base.x + dir.x * gap + p.floor(p.random(-1, 2));
|
|
let newY = base.y + dir.y * gap + p.floor(p.random(-1, 2));
|
|
|
|
|
|
//is in canvas? (clamp)
|
|
newX = p.constrain(newX, 0, p.floor(p.width / cellSize) - 1);
|
|
newY = p.constrain(newY, 0, p.floor(p.height / cellSize) - 1);
|
|
|
|
//overlap check
|
|
const overlap = [...snake, ...walls, ...items].some(seg => seg.x == newX && seg.y == newY);
|
|
if (overlap) return;
|
|
|
|
obstacles = [{x: newX, y: newY, hit: null, time: p.frameCount}];
|
|
}
|
|
|
|
//make items
|
|
function makeItem() {
|
|
const cols = p.width / cellSize;
|
|
const rows = p.height / cellSize;
|
|
|
|
let newX = p.floor(p.random(cols));
|
|
let newY = p.floor(p.random(rows));
|
|
|
|
const overlap = [...snake, ...walls, ...obstacles].some(seg => seg.x == newX && seg.y == newY);
|
|
if (overlap) return;
|
|
|
|
const types = ["heart", "gemstone", "sun"];
|
|
const type = types[p.floor(p.random(types.length))];
|
|
|
|
items.push({x: newX, y: newY, type});
|
|
}
|
|
|
|
p.setup = () => {
|
|
p.createCanvas(1000, 520);
|
|
p.imageMode(p.CENTER);
|
|
|
|
const cols = p.width / cellSize;
|
|
const rows = p.height / cellSize;
|
|
|
|
for (let i = 0; i < cols; i += 1) {
|
|
walls.push({x:i, y:0});
|
|
walls.push({x:i, y: rows - 1});
|
|
}
|
|
for (let i = 0; i < rows; i += 1) {
|
|
walls.push({x:0, y:i});
|
|
walls.push({x:cols - 1, y: i});
|
|
}
|
|
|
|
makeItem();
|
|
};
|
|
|
|
p.draw = () => {
|
|
//screen change conditions
|
|
if (gameState === "over" || gameState === "clear") {
|
|
updateGameData();
|
|
p.noLoop();
|
|
return;
|
|
}
|
|
|
|
p.background(0);
|
|
|
|
updateGameData();
|
|
|
|
let grow = growNext;
|
|
growNext = false;
|
|
|
|
const head = snake[0];
|
|
const tail = snake[snake.length - 1];
|
|
|
|
//default movement
|
|
if (gameState !== "play") return;
|
|
|
|
if (score >= 90) {
|
|
speed = 3;
|
|
}
|
|
else if (score >= 70){
|
|
speed = 5;
|
|
}
|
|
else {
|
|
speed = 7;
|
|
}
|
|
|
|
if (snake.length < 2) return;
|
|
|
|
if (p.frameCount % speed == 0) {
|
|
//new head & tail
|
|
const head = snake[0];
|
|
const tail = snake[snake.length - 1];
|
|
const newHead = {
|
|
x: head.x + dirHead.x,
|
|
y: head.y + dirHead.y
|
|
};
|
|
const newTail = {
|
|
x: tail.x + dirTail.x,
|
|
y: tail.y + dirTail.y
|
|
};
|
|
|
|
if(dirHead.x !== 0 || dirHead.y !== 0) {
|
|
snake.unshift(newHead);
|
|
|
|
items = items.filter(item => {
|
|
const hit = (head.x == item.x && head.y == item.y);
|
|
|
|
if (hit) {
|
|
if (item.type == "heart") {
|
|
score += 5;
|
|
growNext += 1;
|
|
}
|
|
else if (item.type == "gemstone") {
|
|
score += 10;
|
|
growNext += 2;
|
|
}
|
|
else if (item.type == "sun") {
|
|
score += 15;
|
|
growNext += 3;
|
|
}
|
|
makeItem();
|
|
return false;
|
|
}
|
|
return true;
|
|
});
|
|
|
|
if(growNext > 0) {
|
|
growNext -= 1;
|
|
}
|
|
else {
|
|
snake.pop();
|
|
}
|
|
}
|
|
}
|
|
|
|
//collision test
|
|
if (snake.length >= 2) {
|
|
const updatedHead = snake[0];
|
|
const updatedTail = snake[snake.length - 1];
|
|
if (updatedHead.x == updatedTail.x && updatedHead.y == updatedTail.y) {
|
|
gameState = "over";
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
////GET ITEMS////
|
|
|
|
//items rendering
|
|
items.forEach(i => {
|
|
const px = i.x * cellSize + cellSize / 2;
|
|
const py = i.y * cellSize + cellSize / 2;
|
|
|
|
if (i.type == "heart") {
|
|
p.image(heartImg, px, py, cellSize, cellSize);
|
|
}
|
|
else if (i.type == "gemstone") {
|
|
p.image(gemstoneImg, px, py, cellSize, cellSize);
|
|
}
|
|
if (i.type == "sun") {
|
|
p.image(sunImg, px, py, cellSize, cellSize);
|
|
}
|
|
})
|
|
|
|
if (items.length == 0) {
|
|
makeItem();
|
|
}
|
|
|
|
////SNAKE MOVEMENT////
|
|
|
|
//snake rendering
|
|
for (let i = 0; i < snake.length; i+=1) {
|
|
const seg = snake[i];
|
|
const px = seg.x * cellSize + cellSize/2;
|
|
const py = seg.y * cellSize + cellSize/2;
|
|
|
|
p.push();
|
|
p.translate(px, py);
|
|
|
|
//rotate segments
|
|
if (i == 0) {
|
|
//head
|
|
const angle = rotationByDir({
|
|
x: seg.x - snake[1].x,
|
|
y: seg.y - snake[1].y
|
|
});
|
|
p.rotate(angle);
|
|
p.image(headImg, 0, 0, cellSize, cellSize);
|
|
}
|
|
else if (i == snake.length - 1) {
|
|
//tail
|
|
const angle = rotationByDir({
|
|
x: snake[i - 1].x - seg.x,
|
|
y: snake[i - 1].y - seg.y
|
|
});
|
|
p.rotate(angle);
|
|
p.image(tailImg, 0, 0, cellSize, cellSize);
|
|
}
|
|
else {
|
|
//body & corner
|
|
const prev = snake[i - 1];
|
|
const next = snake[i + 1];
|
|
|
|
const dx1 = seg.x - prev.x;
|
|
const dy1 = seg.y - prev.y;
|
|
const dx2 = next.x - seg.x;
|
|
const dy2 = next.y - seg.y;
|
|
|
|
const isCorner = dx1 !== dx2 || dy1 !== dy2;
|
|
|
|
if (isCorner) {
|
|
//corner
|
|
let angle = 0;
|
|
|
|
if (dx1 == -1 && dy1 == 0 && dx2 == 0 && dy2 == 1) {
|
|
angle = 3 * p.HALF_PI;
|
|
} //←↓
|
|
else if (dx1 == 0 && dy1 == 1 && dx2 == 1 && dy2 == 0) {
|
|
angle = p.PI;
|
|
} //↓→
|
|
else if (dx1 == 1 && dy1 == 0 && dx2 == 0 && dy2 == -1) {
|
|
angle = p.HALF_PI;
|
|
} //→↑
|
|
else if (dx1 == 0 && dy1 == -1 && dx2 == -1 && dy2 == 0) {
|
|
angle = 0;
|
|
} //↑←
|
|
else if (dx1 == 0 && dy1 == 1 && dx2 == -1 && dy2 == 0) {
|
|
angle = p.HALF_PI;
|
|
} //↓←
|
|
else if (dx1 == 1 && dy1 == 0 && dx2 == 0 && dy2 == 1) {
|
|
angle = 0;
|
|
} //→↓
|
|
else if (dx1 == 0 && dy1 == -1 && dx2 == 1 && dy2 == 0) {
|
|
angle = -p.HALF_PI;
|
|
} //↑→
|
|
else if (dx1 == -1 && dy1 == 0 && dx2 == 0 && dy2 == -1) {
|
|
angle = p.PI;
|
|
} //←↑
|
|
|
|
p.rotate(angle);
|
|
p.image(cornerImg, 0, 0, cellSize, cellSize);
|
|
}
|
|
else {
|
|
//body
|
|
const angle = dx1 == 0 ? p.HALF_PI : 0;
|
|
p.rotate(angle);
|
|
p.image(bodyImg, 0, 0, cellSize, cellSize);
|
|
}
|
|
}
|
|
|
|
p.pop();
|
|
}
|
|
|
|
////COLLISION WITH OBSTACLE////
|
|
|
|
//show obstacle
|
|
if (p.frameCount % obstacleInterval == 0) {
|
|
const o = obstacles[0];
|
|
|
|
const shouldMake = !o || o.hit !== null || (o.time !== undefined && p.frameCount - o.time > 120);
|
|
|
|
if (shouldMake) {
|
|
obstacles = [];
|
|
makeObstacle();
|
|
}
|
|
}
|
|
|
|
//collision
|
|
for (let i = 0; i < obstacles.length; i += 1) {
|
|
const o = obstacles[i];
|
|
if ((head.x == o.x && head.y == o.y)
|
|
|| tail.x == o.x && tail.y == o.y) {
|
|
if (o.hit == null) {
|
|
life -= 1;
|
|
o.hit = p.frameCount;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
//game over condition
|
|
if (life == 0) {
|
|
gameState = "over";
|
|
return;
|
|
}
|
|
|
|
//obstacles rendering
|
|
obstacles.forEach(o => {
|
|
const px = o.x * cellSize + cellSize / 2;
|
|
const py = o.y * cellSize + cellSize / 2;
|
|
|
|
if (o.hit == null) {
|
|
p.image(obstacleImg, px, py, cellSize, cellSize);
|
|
}
|
|
else {
|
|
const count = p.frameCount - o.hit;
|
|
if (count < 32) {
|
|
if (count % 4 == 0) {
|
|
p.image(obstacleImg, px, py, cellSize, cellSize);
|
|
}
|
|
}
|
|
else {
|
|
const index = obstacles.indexOf(o);
|
|
if (index !== -1) obstacles.splice(index, 1);
|
|
}
|
|
}
|
|
});
|
|
|
|
////COLLISION WITH WALL////
|
|
|
|
//wall rendering
|
|
walls.forEach (w => {
|
|
const px = w.x * cellSize + cellSize / 2;
|
|
const py = w.y * cellSize + cellSize / 2;
|
|
p.image(obstacleImg, px, py, cellSize, cellSize);
|
|
});
|
|
|
|
//collision
|
|
for (let i = 0; i < walls.length; i += 1) {
|
|
const w = walls[i];
|
|
if ((head.x == w.x && head.y == w.y) || (tail.x == w.x && tail.y == w.y)) {
|
|
gameState = "over";
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (score >= 100) {
|
|
gameState = "clear";
|
|
return;
|
|
}
|
|
|
|
|
|
|
|
};//draw end
|
|
|
|
//operating snake
|
|
p.keyPressed = () => {
|
|
const key = p.key.toLowerCase();
|
|
|
|
if (p.keyCode === p.LEFT_ARROW && !(dirHead.x === 1 && dirHead.y === 0)) {
|
|
dirHead = {x: -1, y: 0};
|
|
}
|
|
if (p.keyCode === p.RIGHT_ARROW && !(dirHead.x === -1 && dirHead.y === 0)) {
|
|
dirHead = {x: 1, y: 0};
|
|
}
|
|
if (p.keyCode === p.UP_ARROW && !(dirHead.x === 0 && dirHead.y === 1)) {
|
|
dirHead = {x: 0, y: -1};
|
|
}
|
|
if (p.keyCode === p.DOWN_ARROW && !(dirHead.x === 0 && dirHead.y === -1)) {
|
|
dirHead = {x: 0, y: 1};
|
|
}
|
|
};
|
|
|
|
function handleKeyDown(e) {
|
|
const key = e.key.toLowerCase();
|
|
|
|
if (key === 'a' && !(dirTail.x === 1 && dirTail.y === 0)) {
|
|
dirTail = {x: -1, y: 0};
|
|
}
|
|
if (key === 'd' && !(dirTail.x === -1 && dirTail.y === 0)) {
|
|
dirTail = {x: 1, y: 0};
|
|
}
|
|
if (key === 'w' && !(dirTail.x === 0 && dirTail.y === 1)) {
|
|
dirTail = {x: 0, y: -1};
|
|
}
|
|
if (key === 's' && !(dirTail.x === 0 && dirTail.y === -1)) {
|
|
dirTail = {x: 0, y: 1};
|
|
}
|
|
}
|
|
|
|
window.addEventListener('keydown', handleKeyDown);
|
|
}
|
|
|
|
|
|
|