import { prototypeFrame, gameFrame } from './constants/Prototype.js'; import { Colors } from './constants/Colors.js'; import { imageAssets, selectedCatType, resetCatType } from './sketch.js'; import { createCat, SleepyCat, throwables } from './classes/Cat.js'; import { spawnMouse } from './classes/Mouse.js'; import { drawRobotVacuums } from './classes/RobotVacuum.js'; import { level1Mice } from './level/Level1.js'; import { showLosingScreen } from './level/WinLose.js'; import { updateCatButtons, updateCheeseCount, restartGameProgress } from './Controller.js'; import { calculateCell, isCellValid } from './Helper.js'; const gameParent = document.getElementById('gameFrame'); const upperContainer = document.getElementById('upperContainer'); const controlPanel = document.getElementById('controlPanel'); const cheeseCount = document.getElementById('cheeseCount'); const menuButton = document.getElementById('menuButton'); export let activeCats, activeMice, robotVacuums, cheeses, grid, levelMice; export let gameSprites = []; export let catGroup, mouseGroup, throwableGroup; let leftBar, rightBar, cheeseFeast; let startTime; export function GameScene() { this.enter = function() { select('#endingOverlay').hide(); select('#startButton').hide(); upperContainer.style.display = 'flex'; menuButton.style.display = 'flex'; resetGame(); drawSideBars(); drawRobotVacuums(); } this.setup = function() { const {width, height} = gameParent.getBoundingClientRect(); gameFrame.width = width; gameFrame.height = height; const ratio = width / prototypeFrame.width; Object.keys(prototypeFrame).forEach(key => { if (key != 'width' && key != 'height') { gameFrame[key] = ratio * prototypeFrame[key]; } }) gameFrame.catRatio = 1.2 * gameFrame.tileWidth/200; resizeFrame(); } this.draw = function() { clear(); image(imageAssets.gameBackground, 0, gameFrame.padding_up - gameFrame.border, gameFrame.width, gameFrame.height - gameFrame.padding_up + gameFrame.border); noFill(); stroke(Colors.med_brown); strokeWeight(gameFrame.border); rect(gameFrame.border / 2, gameFrame.border / 2, width - gameFrame.border, height - gameFrame.border, 0.025 * width); updateCatButtons(); drawGrid(); // Spawn Mouse at the designated time let currTime = millis() / 1000 - startTime; while (levelMice.length > 0 && levelMice[0].time <= currTime) { const { time, type, row } = levelMice.shift(); spawnMouse(type, row); } // Let each cat perform its action activeCats.forEach((cat) => cat.action()); // Detect collision or overlaps of mice to carry out the proper interaction // Mouse - Cheese Feast: Game Over, the player lose the game // Mouse - SleepyCat: SleepyCat will explode and be removed, Mouse will be attacked // Mouse - other types of cats: the mouse will attack the cat // Mouse - RobotVacuum: RobotVacuum will be activated and removed all activeMice in its row // Mouse - Throwable: Mouse will be attacked by the Throwable (Yarn, Snowball) for (let row = 0; row < gameFrame.rows; row++) { for (let i = 0; i < activeMice[row].length; i++) { const currMouse = activeMice[row][i]; currMouse.attack(); if (cheeseFeast.overlaps(currMouse.sprite)) { showLosingScreen(); } activeCats.forEach((cat) => { if (cat instanceof SleepyCat && cat.sprite.overlaps(currMouse.sprite)) { cat.awake = true; cat.action(currMouse); } else if (cat.sprite.overlaps(currMouse.sprite)) { currMouse.targetCat = cat; } }) robotVacuums.forEach((vacuum) => { if (vacuum.sprite.overlaps(currMouse.sprite)) { vacuum.action(); } }) throwables.forEach((throwable) => { if (throwable.sprite.overlaps(currMouse.sprite)) { currMouse.attacked(throwable); throwable.remove(); } }) } } } // Remove all active sprites this.exit = function() { gameSprites.forEach((sprite) => sprite.remove()); activeCats.forEach((cat) => cat.remove()); } this.mousePressed = function() { const {row, col} = calculateCell(mouseX, mouseY); // Remove an existing cat using the pet cage button if (isCellValid(row, col) && selectedCatType === 'petCage') { const cat = grid[row][col]; if (cat) { cat.remove(); const index = activeCats.indexOf(cat); if (index !== -1) { activeCats.splice(index, 1); } grid[row][col] = null; resetCatType(); } } // Placing a new cat else if (isCellValid(row, col) && grid[row][col] == null && selectedCatType != null) { let x = gameFrame.padding_left + col * gameFrame.tileWidth + gameFrame.tileWidth / 2; let y = gameFrame.padding_up + row * gameFrame.tileHeight + gameFrame.tileHeight / 2; const newCat = createCat(selectedCatType, x, y); if (newCat) { grid[row][col] = newCat; activeCats.push(newCat); catGroup.add(newCat.sprite); gameSprites.push(newCat.sprite); updateCheeseCount(-newCat.cost); resetCatType(); } } // Detecting click on cheese to collect it and update the cheeseCount for (let i = 0; i < cheeses.length; i++) { // Calculate boundaries of the cheese let left = cheeses[i].x - cheeses[i].width / 2; let right = cheeses[i].x + cheeses[i].width / 2; let top = cheeses[i].y - cheeses[i].height / 2; let bottom = cheeses[i].y + cheeses[i].height / 2; if (mouseX >= left && mouseX <= right && mouseY >= top && mouseY <= bottom) { updateCheeseCount(25); cheeses[i].remove(); cheeses.splice(i, 1); break; } } } } /** * Resizes and styles UI containers and game canvas based on screen width */ function resizeFrame() { gameParent.style.borderRadius = (0.03125 * width) + 'px'; canvas.style.borderRadius = (0.03125 * width) + 'px'; const gridHeight = gameFrame.rows * gameFrame.tileHeight; upperContainer.style.width = width + 'px'; upperContainer.style.height = (gameFrame.height - gridHeight - gameFrame.border) + 'px'; upperContainer.style.borderRadius = (0.03125 * width) + 'px' + (0.03125 * width) + 'px 0 0'; controlPanel.style.margin = gameFrame.border + 'px'; controlPanel.style.marginRight = 0; controlPanel.style.height = (gameFrame.height - gridHeight - 3 * gameFrame.border) + 'px'; } /** * Resets all game state variables and reinitializes the game board */ function resetGame() { gameSprites.forEach((sprite) => sprite.remove()); activeCats = []; activeMice = Array.from({ length: 5 }, () => []); gameSprites = []; robotVacuums = []; cheeses = []; grid = Array(5).fill().map(() => Array(9).fill(null)); levelMice = [...level1Mice]; restartGameProgress(); startTime = millis() / 1000; cheeseCount.textContent = 50; catGroup = new Group(); mouseGroup = new Group(); throwableGroup = new Group(); } /** * Draws the tile grid on the game canvas * Applies hover feedback based on selected cat type and grid cell state */ function drawGrid() { for (let row = 0; row < gameFrame.rows; row++) { for (let col = 0; col < gameFrame.cols; col++) { let x = gameFrame.padding_left + col * gameFrame.tileWidth; let y = gameFrame.padding_up + row * gameFrame.tileHeight; let isHovering = ( mouseX > x && mouseX < x + gameFrame.tileWidth && mouseY > y && mouseY < y + gameFrame.tileHeight ); // Highlight the hovered grid if any action is possible with the currently selected button (selectedCatType) if (isHovering && selectedCatType && selectedCatType === 'petCage' && grid[row][col] != null) { fill(Colors.med_brown); } else if (isHovering && selectedCatType && selectedCatType != 'petCage' && grid[row][col] == null) { fill(Colors.med_brown); } else fill((row + col) % 2 === 0 ? Colors.dark_yellow : Colors.light_yellow); noStroke(); rect(x, y, gameFrame.tileWidth, gameFrame.tileHeight); } } } /** * Draws the left and right borders and the cheeseFeast loss-detection area */ function drawSideBars() { // Drawn so that the Mouse and RobotVacuum goes under the border leftBar = createSprite(gameFrame.border / 2, gameFrame.padding_up + gameFrame.tileHeight * 2.5, gameFrame.border, gameFrame.tileHeight * 5); leftBar.color = Colors.med_brown; leftBar.layer = 10; leftBar.overlaps(allSprites); gameSprites.push(leftBar); // Drawn so that the Mouse and RobotVacuum goes under the border rightBar = createSprite(width - gameFrame.border / 2, gameFrame.padding_up + gameFrame.tileHeight * 2.5, gameFrame.border, gameFrame.tileHeight * 5); rightBar.color = Colors.med_brown; rightBar.layer = 10; rightBar.overlaps(allSprites); gameSprites.push(rightBar); // Drawn to detect loss cheeseFeast = createSprite(gameFrame.tileWidth / 4, gameFrame.padding_up + gameFrame.tileHeight * 2.5, gameFrame.tileWidth / 2, gameFrame.tileHeight * 5); cheeseFeast.opacity = 0; cheeseFeast.overlaps(mouseGroup); gameSprites.push(cheeseFeast) }