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