individual_project/sveltep5play/src/sketch.js
2025-05-11 20:58:33 +09:00

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);
}