diff --git a/.DS_Store b/.DS_Store index 5baa70c..60ff6d3 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/Controller.js b/Controller.js new file mode 100644 index 0000000..b6425f1 --- /dev/null +++ b/Controller.js @@ -0,0 +1,60 @@ +import { showWinningScreen } from './level/WinLose.js'; +import { level1Mice } from './level/Level1.js'; + +const cheeseCount = document.getElementById('cheeseCount'); +const gameProgress = document.getElementById('gameProgress'); +const catCosts = { + chefCat: 50, + singleYarnCat: 100, + doubleYarnCat: 200, + sleepyCat: 150, + iceCat: 150 +}; +let miceKilled = 0; + +/** + * Updates the player's cheese count by n + * + * @param { number } n - The amount of cheese to add (can be negative) + */ +export function updateCheeseCount(n) { + let currCheese = parseInt(cheeseCount.textContent); + currCheese += n; + cheeseCount.textContent = currCheese; +} + +/** + * Enables or disables cat selection buttons based on the current cheese count + * Buttons will be disabled if the player cannot afford the corresponding cat + */ +export function updateCatButtons() { + document.querySelectorAll('.catButton').forEach(button => { + const catType = button.id; + const cost = catCosts[catType]; + + if (parseInt(cheeseCount.textContent) < cost) { + button.disabled = true; + button.style.cursor = 'not-allowed'; + button.style.opacity = '0.5'; + } + else { + button.disabled = false; + button.style.opacity = '1'; + button.style.cursor = 'pointer'; + } + }) +} + +/** + * Updates the game progress bar based on the number of mice killed + * If all mice are killed, the win screen is triggered + */ +export function updateGameProgress() { + miceKilled++; + const percentage = Math.floor((miceKilled / level1Mice.length) * 100); + gameProgress.value = percentage; + + if (percentage >= 100) { + showWinningScreen(); + } +} \ No newline at end of file diff --git a/GameScene.js b/GameScene.js index 6f1db36..41c7187 100644 --- a/GameScene.js +++ b/GameScene.js @@ -1,89 +1,36 @@ import { prototypeFrame, gameFrame } from './constants/Prototype.js'; import { Colors } from './constants/Colors.js'; import { imageAssets, selectedCatType, resetCatType } from './sketch.js'; -import { createCat, SleepyCat, sleepyCats, throwables } from './classes/Cat.js'; -import { createMouse } from './classes/Mouse.js'; -import { RobotVacuum } from './classes/RobotVacuum.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 { showWinningScreen, showLosingScreen } from './level/WinLose.js'; +import { showLosingScreen } from './level/WinLose.js'; +import { updateCatButtons, updateCheeseCount } 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 gameProgress = document.getElementById('gameProgress'); -export const activeCats = []; -export const activeMice = Array.from({ length: 5 }, () => []); -const robotVacuums = []; -let leftBar, rightBar, cheeseFeast; +const menuButton = document.getElementById('menuButton'); + +export let activeCats, activeMice, robotVacuums, cheeses, grid, levelMice; export let gameSprites = []; -export let cheeses = []; -export let grid = Array(5).fill().map(() => Array(9).fill(null)); +export let catGroup, mouseGroup, throwableGroup; +let leftBar, rightBar, cheeseFeast; let startTime; -let levelMice = [...level1Mice]; -export let miceKilled = 0; -export let catGroup, mouseGroup, throwableGroup, cheeseGroup; - -export function calculateCell(mouseX, mouseY) { - let col = floor((mouseX - gameFrame.padding_left) / gameFrame.tileWidth) - let row = floor((mouseY - gameFrame.padding_up) / gameFrame.tileHeight) - - return {row, col}; -} - -function isCellValid(row, col) { - if (row < 0) return false; - if (row >= gameFrame.rows) return false; - if (col < 0) return false; - if (col >= gameFrame.cols) return false; - return true; -} export function GameScene() { this.enter = function() { - select('#upperContainer').show(); select('#endingOverlay').hide(); - select('#menuButton').show(); select('#startButton').hide(); + upperContainer.style.display = 'flex'; + menuButton.style.display = 'flex'; - upperContainer.style.width = width + 'px'; - const gridHeight = gameFrame.rows * gameFrame.tileHeight; - upperContainer.style.height = (gameFrame.height - gridHeight - gameFrame.border) + 'px'; - - controlPanel.style.margin = gameFrame.border + 'px'; - controlPanel.style.height = (gameFrame.height - gridHeight - 3 * gameFrame.border) + 'px'; - - gameSprites = []; // kayanya ga butuh, sama kayak allSprites - - leftBar = createSprite(gameFrame.border / 2, gameFrame.padding_up + gameFrame.tileHeight * 2.5, gameFrame.border, gameFrame.tileHeight * 5); - leftBar.color = Colors.dark_brown; - leftBar.layer = 10; - leftBar.overlaps(allSprites); - gameSprites.push(leftBar); - - rightBar = createSprite(width - gameFrame.border / 2, gameFrame.padding_up + gameFrame.tileHeight * 2.5, gameFrame.border, gameFrame.tileHeight * 5); - rightBar.color = Colors.dark_brown; - rightBar.layer = 10; - rightBar.overlaps(allSprites); - gameSprites.push(rightBar); - - 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) - - for (let row = 0; row < gameFrame.rows; row ++) { - let x = gameFrame.paddingRobot + gameFrame.robotSize / 2; - let y = gameFrame.padding_up + row * gameFrame.tileHeight + gameFrame.tileHeight / 2; - - let vacuum = new RobotVacuum(x, y, row); - - gameSprites.push(vacuum.sprite); - robotVacuums.push(vacuum); - } - - startTime = millis() / 1000; - cheeseCount.textContent = 50; + resetGame(); + drawSideBars(); + drawRobotVacuums(); } this.setup = function() { @@ -100,20 +47,16 @@ export function GameScene() { gameFrame.catRatio = 1.2 * gameFrame.tileWidth/200; - catGroup = new Group(); - mouseGroup = new Group(); - throwableGroup = new Group(); - cheeseGroup = new Group(); + 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.dark_brown); + stroke(Colors.med_brown); strokeWeight(gameFrame.border); - // fix the border radius --> create a ratio for it - rect(gameFrame.border / 2, gameFrame.border / 2, width - gameFrame.border, height - gameFrame.border, 35); + rect(gameFrame.border / 2, gameFrame.border / 2, width - gameFrame.border, height - gameFrame.border, 0.025 * width); updateCatButtons(); drawGrid(); @@ -136,17 +79,13 @@ export function GameScene() { showLosingScreen(); } - sleepyCats.forEach((cat) => { - if (cat.sprite.overlaps(currMouse.sprite)) { + activeCats.forEach((cat) => { + if (cat instanceof SleepyCat && cat.sprite.overlaps(currMouse.sprite)) { cat.awake = true; cat.action(currMouse); } - }) - - activeCats.forEach((cat) => { - if (!(cat instanceof SleepyCat) && cat.sprite.overlaps(currMouse.sprite)) { + else if (cat.sprite.overlaps(currMouse.sprite)) { currMouse.targetCat = cat; - console.log(`seting targetCat to ${currMouse.targetCat}`); } }) @@ -167,10 +106,8 @@ export function GameScene() { } this.exit = function() { - console.log(`i exit gameScene`); - console.log(allSprites); gameSprites.forEach((sprite) => sprite.remove()); - activeCats.forEach((cat) => cat.remove()); // idk if it is needed or not + activeCats.forEach((cat) => cat.remove()); } this.mousePressed = function() { @@ -185,6 +122,7 @@ export function GameScene() { activeCats.splice(index, 1); } grid[row][col] = null; + resetCatType(); } } @@ -203,15 +141,13 @@ export function GameScene() { } for (let i = 0; i < cheeses.length; i++) { - console.log(`there are ${cheeses.length} cheeses`) - // Calculate boundaries + // 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) { - console.log(`cheese ${i} is pressed`) updateCheeseCount(25); cheeses[i].remove(); cheeses.splice(i, 1); @@ -220,84 +156,93 @@ export function GameScene() { } } +} - 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; +/** + * 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'; - let isHovering = ( - mouseX > x && mouseX < x + gameFrame.tileWidth && - mouseY > y && mouseY < y + gameFrame.tileHeight - ); - - if (isHovering && selectedCatType && selectedCatType === 'petCage' && grid[row][col] != null) fill('red'); - else if (isHovering && selectedCatType && selectedCatType != 'petCage' && grid[row][col] == null) fill('red'); - else fill((row + col) % 2 === 0 ? Colors.dark_yellow : Colors.light_yellow); + 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'; - noStroke(); - rect(x, y, gameFrame.tileWidth, gameFrame.tileHeight); + 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]; + + 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 + ); + + 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); } } } -function updateCheeseCount(n) { - let currCheese = int(cheeseCount.textContent); - currCheese += n; - cheeseCount.textContent = currCheese; -} +/** + * Draws the left and right borders and the cheeseFeast loss-detection area + */ +function drawSideBars() { + 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); -function spawnMouse(type, row) { - let x = width; - let y = gameFrame.padding_up + row * gameFrame.tileHeight + gameFrame.tileHeight / 2; - - let newMouse = new createMouse(type, x, y, row); - if (newMouse) { - activeMice[row].push(newMouse); - if (type == 'bossMouse') { - if (row - 1 >= 0) activeMice[row - 1].push(newMouse); - if (row + 1 < gameFrame.rows) activeMice[row + 1].push(newMouse); - } - mouseGroup.add(newMouse.sprite); - gameSprites.push(newMouse.sprite); - } -} + 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); -export function updateProgressBar() { - miceKilled++; - const percentage = Math.floor((miceKilled / level1Mice.length) * 100); - gameProgress.value = percentage; - - if (percentage >= 100) { - showWinningScreen(); - } - console.log(`killed ${miceKilled} out of ${level1Mice.length}, % = ${percentage}`); -} - -const catCosts = { - chefCat: 50, - singleYarnCat: 100, - doubleYarnCat: 200, - sleepyCat: 150, - iceCat: 150 -}; - -function updateCatButtons() { - document.querySelectorAll('.catButton').forEach(button => { - const catType = button.id; - const cost = catCosts[catType]; - - if (parseInt(cheeseCount.textContent) < cost) { - button.disabled = true; - button.style.cursor = 'not-allowed'; - button.style.opacity = '0.5'; - } - else { - button.disabled = false; - button.style.opacity = '1'; - button.style.cursor = 'pointer'; - } - }) + 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) } \ No newline at end of file diff --git a/Helper.js b/Helper.js new file mode 100644 index 0000000..cb952cb --- /dev/null +++ b/Helper.js @@ -0,0 +1,30 @@ +import { gameFrame } from "./constants/Prototype.js"; + +/** + * Calculates the grid cell (row and column) corresponding to the given mouse coordinates + * + * @param { number } mouseX - The X-coordinate of the mouse relative to the canvas + * @param { number } mouseY - The Y-coordinate of the mouse relative to the canvas + * @returns {{row: number, col: number}} An object containing the calculated row and column indices + */ +export function calculateCell(mouseX, mouseY) { + let col = floor((mouseX - gameFrame.padding_left) / gameFrame.tileWidth) + let row = floor((mouseY - gameFrame.padding_up) / gameFrame.tileHeight) + + return {row, col}; +} + +/** + * Checks whether the specified cell coordinates are within the valid bounds the game grid + * + * @param {number} row - The row index of the cell to validate + * @param {number} col - The column index of the cell to validate + * @returns {boolean} True if the cell is within the bounds of the game grid, otherwise, false + */ +export function isCellValid(row, col) { + if (row < 0) return false; + if (row >= gameFrame.rows) return false; + if (col < 0) return false; + if (col >= gameFrame.cols) return false; + return true; +} \ No newline at end of file diff --git a/assets/.DS_Store b/assets/.DS_Store index c1b3277..6b1b512 100644 Binary files a/assets/.DS_Store and b/assets/.DS_Store differ diff --git a/classes/Cat.js b/classes/Cat.js index 14f048a..e1fdc8a 100644 --- a/classes/Cat.js +++ b/classes/Cat.js @@ -1,10 +1,10 @@ import { gameFrame } from '../constants/Prototype.js'; import { catAnimation, imageAssets } from '../sketch.js'; -import { grid, cheeses, activeCats, activeMice, calculateCell, mouseGroup, throwableGroup, gameSprites } from '../GameScene.js'; +import { grid, cheeses, activeCats, activeMice, mouseGroup, throwableGroup, gameSprites } from '../GameScene.js'; import { Yarn, Snowball } from './Throwable.js'; +import { calculateCell } from '../Helper.js'; export const throwables = []; -export const sleepyCats = []; const catAniDesc = { chefCat: { idle: { row: 0, frames: 4, frameSize: [200, 200], frameDelay: 10 }, @@ -31,7 +31,19 @@ const catAniDesc = { } } +/** + * Cat class representing a cat character in the game + */ class Cat { + /** + * Creates an iinstance of a Cat + * + * @param {number} x - The x-coordinate of the cat's position + * @param {number} y - The y-coordinate of the cat's position + * @param {number} cost - The cost of placing the cat + * @param {p5.SpriteSheet} spriteSheet - The sprite sheet for the cat's animations + * @param {Object} ani - Animation details for the cat + */ constructor(x, y, cost, spriteSheet, ani) { // (x, y) is the center of the grid this.width = 1.2 * gameFrame.tileWidth; @@ -57,16 +69,26 @@ class Cat { this.col = col; } + /** + * Switches the cat's animation to 'idle' state + */ switchToIdle() { this.sprite.changeAni('idle'); this.active = false; } + /** + * Switches the cat's animation to 'action' state + */ switchToAction() { this.sprite.changeAni('action'); this.active = true; } + /** + * Called when the cat is attacked by a mouse + * @param {Object} mouse - The mouse attacking the cat + */ attacked(mouse) { this.addExplosion(imageAssets.grayExplosion); this.explosion = undefined; @@ -82,20 +104,30 @@ class Cat { }, 1500); } - changeAni(name) { - this.sprite.changeAni(name); - } - + /** + * Removes the cat from the game + */ remove() { this.sprite.remove(); grid[this.row][this.col] = null; - const index = activeCats.indexOf(this); + let index = activeCats.indexOf(this); if (index !== -1) { activeCats.splice(index, 1); } + + index = gameSprites.indexOf(this); + if (index !== -1) { + gameSprites.splice(index, 1); + } } + /** + * Adds an explosion animation to the cat + * SleepyCat - Red explosion when it overlaps with a mouse + * Other Cats - Gray explosion when it is attacked by a mouse + * @param {p5.SpriteSheet} spriteSheet - The sprite sheet for the explosion + */ addExplosion(spriteSheet) { this.explosion = createSprite(this.x, this.y, this.width, this.width); gameSprites.push(this.explosion); @@ -105,11 +137,12 @@ class Cat { this.explosion.collider = 'none'; this.explosion.addAnis(catAniDesc.explosion); this.explosion.changeAni('action'); - // this.explosion.overlaps(mouseGroup); - // this.explosion.overlaps(catGroup); } } +/** + * Cat that generates cheese periodically for resources + */ class ChefCat extends Cat { constructor(x, y) { super(x, y, 50, catAnimation.chefCat, catAniDesc.chefCat); @@ -118,13 +151,14 @@ class ChefCat extends Cat { } action() { - // Produces 25 cheese every 10 seconds, cheese.png pop in front of the chefCat + // Produces 25 cheese every 10 seconds if (millis() - this.lastProduced > 10000) { const cheese = createSprite(this.x + this.width / 4 + this.offset * this.width / 20, this.y + this.width / 3 + this.offset * this.width / 20); cheese.scale = this.width / 216; cheese.image = imageAssets.cheese; cheese.collider = 'static'; cheese.overlaps(mouseGroup); + cheeses.push(cheese); gameSprites.push(cheese); this.lastProduced = millis(); @@ -133,6 +167,9 @@ class ChefCat extends Cat { } } +/** + * Cat that throws a single yarn at mice every 3 seconds + */ class SingleYarnCat extends Cat { constructor(x, y) { super(x, y, 100, catAnimation.singleYarnCat, catAniDesc.singleYarnCat); @@ -140,7 +177,6 @@ class SingleYarnCat extends Cat { } action() { - // Throw yarn every 3 seconds -> yarn has velocity x of 1 (to the right) if (activeMice[this.row].length > 0) this.switchToAction(); else this.switchToIdle(); @@ -159,6 +195,9 @@ class SingleYarnCat extends Cat { } } +/** + * Cat that throws 2 yarns at mice every 3 seconds + */ class DoubleYarnCat extends Cat { constructor(x, y) { super(x, y, 200, catAnimation.doubleYarnCat, catAniDesc.doubleYarnCat); @@ -166,14 +205,12 @@ class DoubleYarnCat extends Cat { } action() { - // Throw 2 yarns every 3 seconds -> yarn has velocity x of 1 (to the right) - if (activeMice[this.row].length > 0) this.switchToAction(); else this.switchToIdle(); if (this.active && (millis() - this.lastShot > 3000)) { // TODO: check on the offset again - for (let offset of [0, 20]) { + for (let offset of [0, 0.3 * gameFrame.tileWidth]) { let yarnX = this.x + gameFrame.tileWidth / 2 + offset; let yarnY = this.y; @@ -189,6 +226,9 @@ class DoubleYarnCat extends Cat { } } +/** + * Cat that activates when overlapping with a mouse and explodes, damaging the enemy by 150 points + */ export class SleepyCat extends Cat { constructor(x, y) { super(x, y, 150, catAnimation.sleepyCat, catAniDesc.sleepyCat); @@ -200,7 +240,7 @@ export class SleepyCat extends Cat { action(targetMouse) { if (this.awake) { - this.changeAni('action'); + this.switchToAction(); this.addExplosion(imageAssets.redExplosion); this.wakeStart = millis(); this.targetMouse = targetMouse; @@ -224,6 +264,9 @@ export class SleepyCat extends Cat { } } +/** + * Cat that throws snowballs at mice every 3 seconds + */ class IceCat extends Cat { constructor(x, y) { super(x, y, 150, catAnimation.iceCat, catAniDesc.iceCat); @@ -231,8 +274,6 @@ class IceCat extends Cat { } action() { - // Throw snowball every 3 seconds -> snowball has velocity x of 1 (to the right) - if (activeMice[this.row].length > 0) this.switchToAction(); else this.switchToIdle(); @@ -251,6 +292,13 @@ class IceCat extends Cat { } } +/** + * Factory function to create different types of cats + * @param {string} type - The type of cat to create. One of 'chefCat', 'singleYarnCat', 'doubleYarnCat', 'sleepyCat', 'iceCat' + * @param {number} x - The x-coordinate + * @param {number} y - The y-coordinate + * @returns {Cat|undefined} The created cat instance or undefined if type is invalid + */ export function createCat(type, x, y) { switch (type) { case 'chefCat': @@ -262,9 +310,7 @@ export function createCat(type, x, y) { case 'doubleYarnCat': return new DoubleYarnCat(x, y); case 'sleepyCat': - const sleepyCat = new SleepyCat(x, y); - if (sleepyCat) sleepyCats.push(sleepyCat); - return sleepyCat; + return new SleepyCat(x, y); case 'iceCat': return new IceCat(x, y); default: diff --git a/classes/Mouse.js b/classes/Mouse.js index 719d76d..8101b5f 100644 --- a/classes/Mouse.js +++ b/classes/Mouse.js @@ -1,26 +1,42 @@ import { gameFrame } from '../constants/Prototype.js'; import { mouseAnimation } from '../sketch.js'; -import { activeMice, mouseGroup, updateProgressBar } from '../GameScene.js'; +import { activeMice, mouseGroup, gameSprites } from '../GameScene.js'; +import { updateGameProgress } from '../Controller.js'; const mouseAniDesc = { idle: { row: 0, frameSize: [200, 200] }, walk: {row: 1, frames: 6, frameSize: [200, 200], frameDelay: 10 } } +/** + * Mouse class representing a mouse character in the game + */ class Mouse { - constructor(x, y, row, speed, HP, AP, spriteSheet, width) { - this.sprite = createSprite(x, y, width, width); + /** + * Creates an instance of a Mouse + * + * @param {number} row - The row the mouse belongs to + * @param {number} speed - The speed of the mouse's movement + * @param {number} HP - The health points of the mouse + * @param {number} AP - The attack power of the mouse + * @param {object} spriteSheet - The sprite sheet for the mouse animation + * @param {number} size - The size of the mouse sprite + */ + constructor(row, speed, HP, AP, spriteSheet, size) { + const y = gameFrame.padding_up + row * gameFrame.tileHeight + gameFrame.tileHeight / 2; + + this.sprite = createSprite(width, y, size, size); this.sprite.spriteSheet = spriteSheet; - this.sprite.scale = gameFrame.catRatio * width / gameFrame.tileWidth; + this.sprite.scale = gameFrame.catRatio * size / gameFrame.tileWidth; this.sprite.overlaps(mouseGroup) this.sprite.addAnis(mouseAniDesc); this.sprite.changeAni('walk'); this.sprite.layer = 3; this.sprite.vel.x = speed; + this.row = row; this.HP = HP; this.AP = AP; - this.width = width; this.targetCat = undefined; this.lastAttack = 0; this.defaultSpeed = speed; @@ -28,12 +44,16 @@ class Mouse { this.isAlive = true; } + /** + * Removes the mouse from the game and updates the progress + */ remove() { this.sprite.remove(); let index = activeMice[this.row].indexOf(this); if (index != -1) { activeMice[this.row].splice(index, 1); } + if (this.defaultHP = 1000) { if (this.row - 1 >= 0) { index = activeMice[this.row - 1].indexOf(this); @@ -44,17 +64,25 @@ class Mouse { if (index != -1) activeMice[this.row + 1].splice(index, 1); } } + + index = gameSprites.indexOf(this); + if (index !== -1) { + gameSprites.splice(index, 1); + } + if (this.isAlive) { this.isAlive = false; - updateProgressBar(); + updateGameProgress(); } } + /** + * Makes the mouse attack the target cat if one exists + */ attack() { if (this.targetCat != undefined) { this.sprite.vel.x = 0; this.sprite.changeAni('idle'); - console.log(`I have a target Cat, and its HP is ${this.targetCat.HP}`) if (this.lastAttack == 0 || millis() - this.lastAttack > 3000) { this.targetCat.attacked(this); this.lastAttack = millis(); @@ -66,9 +94,12 @@ class Mouse { } } + /** + * Handles when the mouse is attacked and reduces its health + * @param {number} point - The damage taken by the mouse + */ attacked(point) { this.HP -= point; - console.log(`I'm being attacked by ${point} points and my HP is now ${this.HP}`) if (this.HP <= 0) this.remove(); else { this.sprite.opacity = (this.HP / this.defaultHP) * 0.5 + 0.5; @@ -76,41 +107,82 @@ class Mouse { } } +/** + * Basic type of mouse + */ class BasicMouse extends Mouse { constructor(x, y, row) { super(x, y, row, -0.15, 100, 20, mouseAnimation.basicMouse, gameFrame.tileWidth); } } +/** + * Helmet-wearing mouse + * Has a higher HP compared to BasicMouse + */ class HelmetMouse extends Mouse { constructor(x, y, row) { super(x, y, row, -0.15, 150, 20, mouseAnimation.helmetMouse, gameFrame.tileWidth); } } +/** + * Sporty type of mouse + * Has a higher speed compared to Basic Mouse + */ class SportyMouse extends Mouse { constructor(x, y, row) { super(x, y, row, -0.3, 85, 20, mouseAnimation.sportyMouse, gameFrame.tileWidth); } } +/** + * Boss mouse is 3 times bigger than other mice + * Has a higher HP and AP compared to the other types + * Has a slower speed compared to the other types + */ class BossMouse extends Mouse { constructor(x, y, row) { super(x, y, row, -0.05, 1000, 50, mouseAnimation.bossMouse, 3 * gameFrame.tileWidth); } } -export function createMouse(type, x, y, row) { +/** + * Factory function to create different types of cats + * + * @param {string} type - The type of mouse to create + * @param {number} row - The row the mouse belongs to + * @returns @returns {Mouse|undefined} The created mouse instance or undefined if type is invalid + */ +function createMouse(type, row) { switch (type) { case 'basicMouse': - return new BasicMouse(x, y, row); + return new BasicMouse(row); case 'helmetMouse': - return new HelmetMouse(x, y, row); + return new HelmetMouse(row); case 'sportyMouse': - return new SportyMouse(x, y, row); + return new SportyMouse(row); case 'bossMouse': - return new BossMouse(x, y, row); + return new BossMouse(row); default: return undefined; } +} + +/** + * Spawns a mouse of a specified type and adds it to the game + * @param {string} type - The type of mouse to spawn + * @param {number} row - The row to spawn the mouse in + */ +export function spawnMouse(type, row) { + let newMouse = new createMouse(type, row); + if (newMouse) { + activeMice[row].push(newMouse); + if (type == 'bossMouse') { + if (row - 1 >= 0) activeMice[row - 1].push(newMouse); + if (row + 1 < gameFrame.rows) activeMice[row + 1].push(newMouse); + } + mouseGroup.add(newMouse.sprite); + gameSprites.push(newMouse.sprite); + } } \ No newline at end of file diff --git a/classes/RobotVacuum.js b/classes/RobotVacuum.js index 20cc75d..af8adb9 100644 --- a/classes/RobotVacuum.js +++ b/classes/RobotVacuum.js @@ -1,6 +1,6 @@ import { gameFrame } from '../constants/Prototype.js'; import { imageAssets } from '../sketch.js'; -import { activeMice, catGroup, throwableGroup } from '../GameScene.js'; +import { activeMice, catGroup, throwableGroup, gameSprites, robotVacuums } from '../GameScene.js'; export class RobotVacuum { constructor(x, y, row) { @@ -10,10 +10,12 @@ export class RobotVacuum { this.sprite.layer = 2; this.sprite.overlaps(catGroup); this.sprite.overlaps(throwableGroup); + this.activated = false; this.row = row; } + // If activated, kills all the active mice in its row action() { if (!this.activated) { this.activated = true; @@ -29,4 +31,19 @@ export class RobotVacuum { } } +} + +/** + * Draws 1 vacuum robot on each row at the bginning of the game + */ +export function drawRobotVacuums() { + for (let row = 0; row < gameFrame.rows; row ++) { + let x = gameFrame.paddingRobot + gameFrame.robotSize / 2; + let y = gameFrame.padding_up + row * gameFrame.tileHeight + gameFrame.tileHeight / 2; + + let vacuum = new RobotVacuum(x, y, row); + + gameSprites.push(vacuum.sprite); + robotVacuums.push(vacuum); + } } \ No newline at end of file diff --git a/classes/Throwable.js b/classes/Throwable.js index 106c90e..9cc6b58 100644 --- a/classes/Throwable.js +++ b/classes/Throwable.js @@ -11,6 +11,7 @@ export class Throwable { this.sprite.vel.x = 1; this.sprite.rotationSpeed = 1; this.sprite.life = 600; + this.point = point; this.width = width; } @@ -20,12 +21,14 @@ export class Throwable { } } +// Yarn is thrown by singleYarnCat and doubleYarnCat export class Yarn extends Throwable { constructor(x, y) { super(x, y, 15, imageAssets.yarn, gameFrame.tileWidth / 4); } } +// Snowball is thrown by IceCat export class Snowball extends Throwable { constructor(x, y) { super(x, y, 20, imageAssets.snowball, gameFrame.tileWidth / 4); diff --git a/constants/Colors.js b/constants/Colors.js index 58449bb..20ca729 100644 --- a/constants/Colors.js +++ b/constants/Colors.js @@ -1,5 +1,7 @@ export const Colors = { - dark_brown: '#B09472', + dark_brown: '#503E28', + med_brown: '#B09472', + light_brown: '#CAB49A', dark_yellow: '#F7E7BE', light_yellow: '#FDF4E5' } \ No newline at end of file diff --git a/constants/Prototype.js b/constants/Prototype.js index 84ba174..7f131e1 100644 --- a/constants/Prototype.js +++ b/constants/Prototype.js @@ -1,3 +1,4 @@ +// Numbers from the prototype designed on Figma export const prototypeFrame = { border: 10, width: 800, @@ -13,6 +14,7 @@ export const prototypeFrame = { controlPanelGap: 8 } +// Numbers used in the actual gameFrame export const gameFrame = { x: 0, y: 0, diff --git a/css/style.css b/css/style.css index 9b607c7..be03c17 100644 --- a/css/style.css +++ b/css/style.css @@ -2,7 +2,9 @@ --dark-brown: #503E28; --med-brown: #B09472; --light-brown: #CAB49A; + --highlight-brown: #A8845D; --dark-yellow: #F7E7BE; + --med-yellow: #EDDBBD; --light-yellow: #FDF4E5; } @@ -15,11 +17,18 @@ body { flex-direction: column; justify-content: center; align-items: center; + font-family: "Inter", sans-serif; + font-weight: 600; +} + +#title { + color: var(--dark-brown); + font-weight: 600; + font-family: "Fredoka", sans-serif;; } #gameFrame { position: relative; - /* flex-shrink: 0; */ max-width: 800px; width: 60vw; aspect-ratio: 800/569; @@ -44,18 +53,23 @@ body { #endingText { color: var(--dark-yellow); + font-weight: 600; + font-size: x-large; + font-family: "Fredoka", sans-serif; } canvas { display: block; width: 100%; height: 100%; - border-radius: 25px; z-index: 0; + background-color: var(--med-brown); } #upperContainer { position: absolute; + display: flex; + flex-direction: row; align-items: center; background: var(--med-brown); border-radius: 25px 25px 0 0; @@ -63,8 +77,7 @@ canvas { } #controlPanel { - /* background: var(--light-brown); */ - border-radius: 30px 30px 0 0; + width: 73.4%; display: flex; flex-direction: row; align-items: center; @@ -81,8 +94,8 @@ canvas { #cheeseDisplay { background: var(--light-brown); height: 100%; - width: 9.7%; - border-radius: 8px; + width: 13.5%; + border-radius: 12%; display: flex; flex-direction: column; justify-content: center; @@ -93,12 +106,7 @@ canvas { display: flex; flex-direction: row; height: 100%; - width: auto; - aspect-ratio: 330/83; - /* gap: 1px; */ - background: var(--light-brown); - /* padding: 4px 8px; */ - border-radius: 6px; + width: 65% } .catButton { @@ -107,16 +115,25 @@ canvas { flex-direction: column; align-items: center; background: var(--light-brown); - border: 1px solid var(--dark-brown); - border-radius: 4px; + border: 1px solid var(--med-brown); padding: 4px 6px; cursor: pointer; - transition: background 0.2s; + transition: background 0.2s ease; +} + +#chefCat { + border-radius: 12% 0 0 12%; +} + +#iceCat { + border-radius: 0 12% 12% 0; } .catButton span { - font-size: 12px; - color: #333; + margin-top: 5%; + font-size: small; + font-weight: 600; + color: var(--dark-brown); } .catButton:not(:disabled):hover { @@ -124,7 +141,11 @@ canvas { } .catButton.activeButton { - background: red; + background: var(--light-yellow); +} + +.catButton.activeButton:hover { + background: var(--med-yellow); } .divider { @@ -136,6 +157,7 @@ canvas { width: auto; aspect-ratio: 60/83; justify-content: center; + border-radius: 12%; } .catIcon { @@ -156,8 +178,13 @@ canvas { object-fit: contain; } +#cheeseCount { + margin-top: 10%; + font-size: medium; +} + #gameProgressWrapper { - width: 22%; + width: 24%; height: 100%; display: flex; flex-direction: column; @@ -187,7 +214,7 @@ canvas { } #gameProgressLabel { - height: 20%; + font-size: 0.75em; font-weight: 600; font-family: "Inter", sans-serif; margin-top: 4%; @@ -198,7 +225,6 @@ canvas { position: absolute; translate: -50%; width: 100%; - /* z-index: -1; */ } .Button { @@ -213,5 +239,11 @@ canvas { } .Button:hover { - background: #a8845d; + background: var(--highlight-brown); +} + +#menuButton { + display: flex; + flex-direction: row; + gap: 15px; } \ No newline at end of file diff --git a/index.html b/index.html index a3e88d7..44d38d8 100644 --- a/index.html +++ b/index.html @@ -16,7 +16,7 @@ - +
@@ -24,7 +24,7 @@