Compare commits
10 Commits
c4aeab6ec5
...
2cf34b04be
Author | SHA1 | Date | |
---|---|---|---|
![]() |
2cf34b04be | ||
![]() |
9826e8f115 | ||
![]() |
839b9fbb4c | ||
![]() |
1f19216529 | ||
![]() |
d8ad66f6a9 | ||
![]() |
ca8adb23c0 | ||
![]() |
2ca032f930 | ||
![]() |
05d1136461 | ||
![]() |
cb2b42497a | ||
![]() |
ad4ec1d64b |
8
.idea/.gitignore
vendored
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
9
.idea/cats-vs-mice.iml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="JAVA_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||||
|
<exclude-output />
|
||||||
|
<content url="file://$MODULE_DIR$" />
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
6
.idea/misc.xml
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_23" default="true" project-jdk-name="openjdk-23" project-jdk-type="JavaSDK">
|
||||||
|
<output url="file://$PROJECT_DIR$/out" />
|
||||||
|
</component>
|
||||||
|
</project>
|
8
.idea/modules.xml
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/cats-vs-mice.iml" filepath="$PROJECT_DIR$/.idea/cats-vs-mice.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
6
.idea/vcs.xml
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
235
Cat.js
|
@ -1,235 +0,0 @@
|
||||||
import { gameFrame } from './prototype.js';
|
|
||||||
import { catAnimation, imageAssets } from './sketch.js';
|
|
||||||
import { grid, cheeses, activeMice, calculateCell, mouseGroup, throwableGroup } from './GameScene.js';
|
|
||||||
import { Yarn, Snowball } from './Throwable.js';
|
|
||||||
|
|
||||||
export const throwables = [];
|
|
||||||
export const catAniDesc = {
|
|
||||||
chefCat: {
|
|
||||||
idle: { row: 0, frames: 4, frameSize: [200, 200], frameDelay: 10 },
|
|
||||||
action: { row: 0, frames: 4, frameSize: [200, 200], frameDelay: 10 }
|
|
||||||
},
|
|
||||||
singleYarnCat: {
|
|
||||||
idle: { row: 0, frameSize: [200, 200] },
|
|
||||||
action: {row: 1, frames: 8, frameSize: [200, 200], frameDelay: 22 }
|
|
||||||
},
|
|
||||||
doubleYarnCat: {
|
|
||||||
idle: { row: 0, frameSize: [200, 200] },
|
|
||||||
action: {row: 1, frames: 8, frameSize: [200, 200], frameDelay: 22 }
|
|
||||||
},
|
|
||||||
sleepyCat: {
|
|
||||||
idle: {row: 0, frames: 6, frameSize: [200, 200], frameDelay: 20 },
|
|
||||||
action: {row: 1, frames: 9, frameSize: [200, 200], frameDelay: 10 }
|
|
||||||
},
|
|
||||||
iceCat: {
|
|
||||||
idle: { row: 0, frameSize: [200, 200] },
|
|
||||||
action: {row: 1, frames: 8, frameSize: [200, 200], frameDelay: 22 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Cat {
|
|
||||||
constructor(x, y, cost, spriteSheet, ani, width) {
|
|
||||||
// (x, y) is the center of the grid
|
|
||||||
this.sprite = createSprite(x, y, width, width);
|
|
||||||
this.sprite.spriteSheet = spriteSheet;
|
|
||||||
this.sprite.addAnis(ani);
|
|
||||||
this.sprite.collider = 'static';
|
|
||||||
this.sprite.collides(mouseGroup);
|
|
||||||
this.sprite.overlaps(throwableGroup);
|
|
||||||
this.sprite.layer = 1;
|
|
||||||
this.sprite.changeAni('idle');
|
|
||||||
this.active = false;
|
|
||||||
|
|
||||||
this.x = x;
|
|
||||||
this.y = y;
|
|
||||||
this.cost = cost;
|
|
||||||
this.ani = ani;
|
|
||||||
this.width = width;
|
|
||||||
this.HP = 200;
|
|
||||||
|
|
||||||
const { row, col } = calculateCell(x, y);
|
|
||||||
this.row = row;
|
|
||||||
this.col = col;
|
|
||||||
}
|
|
||||||
|
|
||||||
switchToIdle() {
|
|
||||||
this.sprite.changeAni('idle');
|
|
||||||
this.active = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
switchToAction() {
|
|
||||||
this.sprite.changeAni('action');
|
|
||||||
this.active = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
action() {
|
|
||||||
// TODO:
|
|
||||||
// - ChefCat: produces cheese every 10 seconds -> cheese.png pop up on top of the ChefCat every 10 seconds
|
|
||||||
// - SingleYarnCat: throw a yarn every 3 seconds -> a sprite of yarn.png shows up on the right of the cat with velocity of 1 to the right,
|
|
||||||
// delete the sprite of yarn when yarn hit a mouse or get out of the gameFrame
|
|
||||||
// - DoubleYarnCat: similar to SingleYarnCat but throw 2 yarns every 3 seconds, the yarns are visibly detached from each other
|
|
||||||
// - SleepyCat: stay idle until collide with a mouse, when colliding, change ani into 'wake_up' and then remove the sleepyCat sprite
|
|
||||||
// - IceCat: throw a snowball every 3 seconds from its mouth, the snowball is a sprite with image snowball.png
|
|
||||||
// delete the snowball sprite when it hit a mouse or get out of gameFrame
|
|
||||||
}
|
|
||||||
|
|
||||||
update() {
|
|
||||||
// update ani
|
|
||||||
clear();
|
|
||||||
if (kb.presses('a')) {
|
|
||||||
console.log(`a is pressed`)
|
|
||||||
this.sprite.changeAni('action');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
attacked(mouse) {
|
|
||||||
this.HP = max(0, this.HP - mouse.AP);
|
|
||||||
// if HP = 0, remove sprite
|
|
||||||
}
|
|
||||||
|
|
||||||
// draw() {
|
|
||||||
// drawSprite(this.sprite);
|
|
||||||
// // animation(this.ani, this.x, this.y, 0, this.width, this.width);
|
|
||||||
// }
|
|
||||||
|
|
||||||
collide() {
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
changeAni(name) {
|
|
||||||
this.sprite.changeAni(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
remove() {
|
|
||||||
this.sprite.remove();
|
|
||||||
grid[this.row][this.col] = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ChefCat extends Cat {
|
|
||||||
constructor(x, y) {
|
|
||||||
super(x, y, 50, catAnimation.chefCat, catAniDesc.chefCat, 100);
|
|
||||||
this.lastProduced = millis();
|
|
||||||
}
|
|
||||||
|
|
||||||
action() {
|
|
||||||
// Produces 25 cheese every 10 seconds, cheese.png pop in front of the chefCat
|
|
||||||
if (millis() - this.lastProduced > 10000) {
|
|
||||||
console.log(`produces Cheese!`)
|
|
||||||
const cheese = createSprite(this.x + this.width / 5, this.y + this.width / 5);
|
|
||||||
cheese.scale = this.width / 300;
|
|
||||||
cheese.image = imageAssets.cheese;
|
|
||||||
cheese.mouseActive = true;
|
|
||||||
cheeses.push(cheese);
|
|
||||||
this.lastProduced = millis();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class SingleYarnCat extends Cat {
|
|
||||||
constructor(x, y) {
|
|
||||||
super(x, y, 100, catAnimation.singleYarnCat, catAniDesc.singleYarnCat, 100);
|
|
||||||
this.lastShot = millis();
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
|
||||||
|
|
||||||
if (this.active && (millis() - this.lastShot > 3000)) {
|
|
||||||
let yarnX = this.x + gameFrame.tileWidth / 2;
|
|
||||||
let yarnY = this.y;
|
|
||||||
|
|
||||||
const yarn = new Yarn(yarnX, yarnY);
|
|
||||||
if (yarn) {
|
|
||||||
throwables.push(yarn);
|
|
||||||
throwableGroup.add(yarn.sprite);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.lastShot = millis();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class DoubleYarnCat extends Cat {
|
|
||||||
constructor(x, y) {
|
|
||||||
super(x, y, 200, catAnimation.doubleYarnCat, catAniDesc.doubleYarnCat, 100);
|
|
||||||
this.lastShot = millis();
|
|
||||||
}
|
|
||||||
|
|
||||||
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]) {
|
|
||||||
let yarnX = this.x + gameFrame.tileWidth / 2 + offset;
|
|
||||||
let yarnY = this.y;
|
|
||||||
|
|
||||||
const yarn = new Yarn(yarnX, yarnY);
|
|
||||||
if (yarn) {
|
|
||||||
throwables.push(yarn);
|
|
||||||
throwableGroup.add(yarn.sprite);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.lastShot = millis();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class SleepyCat extends Cat {
|
|
||||||
constructor(x, y) {
|
|
||||||
super(x, y, 150, catAnimation.sleepyCat, catAniDesc.sleepyCat, 100);
|
|
||||||
this.awake = false;
|
|
||||||
this.wakeStart = undefined;
|
|
||||||
this.targetMouse = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
action(targetMouse) {
|
|
||||||
if (this.awake) {
|
|
||||||
this.changeAni('action');
|
|
||||||
this.wakeStart = millis();
|
|
||||||
this.targetMouse = targetMouse;
|
|
||||||
this.awake = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.wakeStart != undefined) {
|
|
||||||
if (millis() - this.wakeStart > 900) {
|
|
||||||
if (this.targetMouse) this.targetMouse.remove();
|
|
||||||
}
|
|
||||||
if (millis() - this.wakeStart > 1480) this.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class IceCat extends Cat {
|
|
||||||
constructor(x, y) {
|
|
||||||
super(x, y, 150, catAnimation.iceCat, catAniDesc.iceCat, 100);
|
|
||||||
this.lastShot = millis();
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
|
||||||
|
|
||||||
if (this.active && (millis() - this.lastShot > 3000)) {
|
|
||||||
const snowballX = this.x + gameFrame.tileWidth / 2;
|
|
||||||
const snowballY = this.y;
|
|
||||||
|
|
||||||
const snowball = new Snowball(snowballX, snowballY)
|
|
||||||
if (snowball) {
|
|
||||||
throwables.push(snowball);
|
|
||||||
throwableGroup.add(snowball.sprite);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.lastShot = millis();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
262
GameScene.js
|
@ -1,262 +0,0 @@
|
||||||
import { prototypeFrame, gameFrame } from './prototype.js';
|
|
||||||
import { imageAssets, selectedCatType, resetCatType } from './sketch.js';
|
|
||||||
import { ChefCat, SingleYarnCat, DoubleYarnCat, SleepyCat, IceCat, throwables } from './Cat.js';
|
|
||||||
import { BasicMouse, HelmetMouse } from './Mouse.js';
|
|
||||||
import { level1Mice } from './level/level1.js';
|
|
||||||
import { RobotVacuum } from './RobotVacuum.js';
|
|
||||||
|
|
||||||
const gameParent = document.getElementById('gameFrame');
|
|
||||||
const upperContainer = document.getElementById('upperContainer');
|
|
||||||
const controlPanel = document.getElementById('controlPanel');
|
|
||||||
const cheeseCount = document.getElementById('cheeseCount');
|
|
||||||
const activeCats = [];
|
|
||||||
export const activeMice = Array.from({ length: 5 }, () => []);
|
|
||||||
let robotVacuums = [];
|
|
||||||
let gameSprites = [];
|
|
||||||
let sleepyCats = [];
|
|
||||||
export let cheeses = [];
|
|
||||||
export let grid = Array(5).fill().map(() => Array(9).fill(null));
|
|
||||||
let startTime;
|
|
||||||
let levelMice = [...level1Mice];
|
|
||||||
export let catGroup, mouseGroup, throwableGroup;
|
|
||||||
|
|
||||||
function createCat(type, x, y) {
|
|
||||||
switch (type) {
|
|
||||||
case 'chefCat':
|
|
||||||
let cat = new ChefCat(x, y);
|
|
||||||
cat.action();
|
|
||||||
return cat;
|
|
||||||
case 'singleYarnCat':
|
|
||||||
return new SingleYarnCat(x, y);
|
|
||||||
case 'doubleYarnCat':
|
|
||||||
return new DoubleYarnCat(x, y);
|
|
||||||
case 'sleepyCat':
|
|
||||||
return new SleepyCat(x, y);
|
|
||||||
case 'iceCat':
|
|
||||||
return new IceCat(x, y);
|
|
||||||
default:
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function createMouse(type, x, y, row) {
|
|
||||||
switch (type) {
|
|
||||||
case 'basicMouse':
|
|
||||||
return new BasicMouse(x, y, row);
|
|
||||||
case 'helmetMouse':
|
|
||||||
return new HelmetMouse(x, y, row);
|
|
||||||
default:
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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('#menuButton').show();
|
|
||||||
select('#startButton').hide();
|
|
||||||
|
|
||||||
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
|
|
||||||
robotVacuums = [];
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
catGroup = new Group();
|
|
||||||
mouseGroup = new Group();
|
|
||||||
throwableGroup = new Group();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.draw = function() {
|
|
||||||
clear();
|
|
||||||
image(imageAssets.gameBackground, 0, gameFrame.padding_up - gameFrame.border, gameFrame.width, gameFrame.height - gameFrame.padding_up + gameFrame.border);
|
|
||||||
noFill();
|
|
||||||
stroke('#B09472');
|
|
||||||
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);
|
|
||||||
drawGrid();
|
|
||||||
|
|
||||||
let currTime = millis() / 1000 - startTime;
|
|
||||||
|
|
||||||
while (levelMice.length > 0 && levelMice[0].time <= currTime) {
|
|
||||||
const { time, type, row } = levelMice.shift();
|
|
||||||
spawnMouse(type, row);
|
|
||||||
}
|
|
||||||
|
|
||||||
activeCats.forEach((cat) => cat.action());
|
|
||||||
for (let row = 0; row < gameFrame.rows; row++) {
|
|
||||||
for (let i = 0; i < activeMice[row].length; i++) {
|
|
||||||
const currMouse = activeMice[row][i];
|
|
||||||
sleepyCats.forEach((cat) => {
|
|
||||||
if (cat.sprite.overlaps(currMouse.sprite)) {
|
|
||||||
cat.awake = true;
|
|
||||||
cat.action(currMouse);
|
|
||||||
activeMice[row].splice(i, 1);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
robotVacuums.forEach((vacuum) => {
|
|
||||||
if (vacuum.sprite.overlaps(currMouse.sprite)) {
|
|
||||||
vacuum.action();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
throwables.forEach((throwable) => {
|
|
||||||
if (throwable.sprite.overlaps(currMouse.sprite)) {
|
|
||||||
currMouse.attacked(throwable.point);
|
|
||||||
throwable.remove();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.exit = function() {
|
|
||||||
console.log(`i exit gameScene`);
|
|
||||||
gameSprites.forEach((sprite) => sprite.remove());
|
|
||||||
activeCats.forEach((cat) => cat.remove()); // idk if it is needed or not
|
|
||||||
}
|
|
||||||
|
|
||||||
this.mousePressed = function() {
|
|
||||||
const {row, col} = calculateCell(mouseX, mouseY);
|
|
||||||
|
|
||||||
if (isCellValid(row, col) && selectedCatType === 'petCage') {
|
|
||||||
const cat = grid[row][col];
|
|
||||||
if (cat) {
|
|
||||||
cat.remove();
|
|
||||||
// TODO: need to remove from activeCats too
|
|
||||||
const index = activeCats.indexOf(cat);
|
|
||||||
if (index !== -1) {
|
|
||||||
activeCats.splice(index, 1);
|
|
||||||
}
|
|
||||||
grid[row][col] = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (isCellValid(row, col) && 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) {
|
|
||||||
newCat.sprite.scale = gameFrame.catRatio;
|
|
||||||
grid[row][col] = newCat;
|
|
||||||
activeCats.push(newCat);
|
|
||||||
catGroup.add(newCat.sprite);
|
|
||||||
gameSprites.push(newCat.sprite); // Is this redundant? kedouble2
|
|
||||||
if (newCat instanceof SleepyCat) sleepyCats.push(newCat);
|
|
||||||
resetCatType();
|
|
||||||
}
|
|
||||||
// grid[row][col] = selectedCatType;
|
|
||||||
// console.log(`after click, grid[${row}][${col}] = ${grid[row][col]}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = 0; i < cheeses.length; i++) {
|
|
||||||
console.log(`there are ${cheeses.length} cheeses`)
|
|
||||||
// Calculate boundaries
|
|
||||||
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);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
|
||||||
fill('red');
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
fill((row + col) % 2 === 0 ? '#F7E7BE' : '#FDF4E5');
|
|
||||||
}
|
|
||||||
noStroke();
|
|
||||||
rect(x, y, gameFrame.tileWidth, gameFrame.tileHeight);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateCheeseCount(n) {
|
|
||||||
console.log(`update cheeseCount by ${n}`)
|
|
||||||
let currCheese = int(cheeseCount.textContent);
|
|
||||||
currCheese += n;
|
|
||||||
cheeseCount.textContent = currCheese;
|
|
||||||
// TODO: handle if n is negative and currCheese < -n somewhere else
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
|
||||||
newMouse.sprite.scale = gameFrame.catRatio;
|
|
||||||
activeMice[row].push(newMouse);
|
|
||||||
mouseGroup.add(newMouse.sprite);
|
|
||||||
gameSprites.push(newMouse.sprite); // Is this redundant? kedouble2 sama allSprites
|
|
||||||
}
|
|
||||||
}
|
|
42
Mouse.js
|
@ -1,42 +0,0 @@
|
||||||
import { gameFrame } from './prototype.js';
|
|
||||||
import { imageAssets } from './sketch.js';
|
|
||||||
import { activeMice } from './GameScene.js';
|
|
||||||
|
|
||||||
export class Mouse {
|
|
||||||
constructor(x, y, row, speed, HP, AP, img, width) {
|
|
||||||
this.sprite = createSprite(x, y, width, width);
|
|
||||||
this.sprite.image = img;
|
|
||||||
this.sprite.layer = 3;
|
|
||||||
this.sprite.velocity.x = speed;
|
|
||||||
this.row = row;
|
|
||||||
this.HP = HP;
|
|
||||||
this.AP = AP;
|
|
||||||
this.width = width;
|
|
||||||
}
|
|
||||||
|
|
||||||
remove() {
|
|
||||||
this.sprite.remove();
|
|
||||||
const index = activeMice[this.row].indexOf(this);
|
|
||||||
if (index != -1) {
|
|
||||||
activeMice[this.row].splice(index, 1);
|
|
||||||
}
|
|
||||||
// console.log(`there are now ${activeMice[this.row].length} mice in row ${this.row}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
attacked(point) {
|
|
||||||
this.HP -= point;
|
|
||||||
if (this.HP <= 0) this.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class BasicMouse extends Mouse {
|
|
||||||
constructor(x, y, row) {
|
|
||||||
super(x, y, row, -0.25, 100, 20, imageAssets.mouse, gameFrame.tileWidth);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class HelmetMouse extends Mouse {
|
|
||||||
constructor(x, y, row) {
|
|
||||||
super(x, y, row, -0.25, 150, 20, imageAssets.mouse, gameFrame.tileWidth);
|
|
||||||
}
|
|
||||||
}
|
|
189
README.md
|
@ -1,4 +1,189 @@
|
||||||
# Cats vs. Mice
|
# 🐱 Cats vs. Mice 🐭
|
||||||
|
|
||||||
|
## 👤 Student Information
|
||||||
Name : Adelia Putri
|
Name : Adelia Putri
|
||||||
Student ID : 20210782
|
Student ID : 20210782
|
||||||
Email : adelia@kaist.ac.kr
|
Email : adelia@kaist.ac.kr
|
||||||
|
Git Repository: [Cats vs. Mice Repository](https://github.com/adeliptr/Cats-vs-Mice)
|
||||||
|
Demo Video: [Cats vs. Mice Demo Video](https://youtu.be/wxM7zTX4wvc)
|
||||||
|
|
||||||
|
## 📚 Table of Content
|
||||||
|
- [🐱 Cats vs. Mice 🐭](#-cats-vs-mice-)
|
||||||
|
- [👤 Student Information](#-student-information)
|
||||||
|
- [📚 Table of Content](#-table-of-content)
|
||||||
|
- [🎮 Game Description](#-game-description)
|
||||||
|
- [🖼️ Game Interface](#️-game-interface)
|
||||||
|
- [🕹️ Game Mechanics](#️-game-mechanics)
|
||||||
|
- [💸 Currency](#-currency)
|
||||||
|
- [👾 Characters](#-characters)
|
||||||
|
- [1. Cats 🐈](#1-cats-)
|
||||||
|
- [2. Mice 🐁](#2-mice-)
|
||||||
|
- [🔗 Interactions List](#-interactions-list)
|
||||||
|
- [🗂️ Code Organization](#️-code-organization)
|
||||||
|
- [💻 Tech Stack](#-tech-stack)
|
||||||
|
- [🧩 How Components Interact](#-how-components-interact)
|
||||||
|
- [💡 Design Patterns Used](#-design-patterns-used)
|
||||||
|
- [🏭 Factory Method Pattern](#-factory-method-pattern)
|
||||||
|
- [♟️ Strategy Pattern](#️-strategy-pattern)
|
||||||
|
- [⭐️ Special Features](#️-special-features)
|
||||||
|
- [⚠️ Limitation](#️-limitation)
|
||||||
|
- [🙌 Acknowledgement](#-acknowledgement)
|
||||||
|
|
||||||
|
|
||||||
|
## 🎮 Game Description
|
||||||
|
**Cats vs. Mice** is a tower defense-style strategy game inspired by [*Plants vs. Zombies*](https://en.wikipedia.org/wiki/Plants_vs._Zombies), redesigned with a cute kitchen theme. Players place cats on a tiled kitchen floor to stop waves of invading mice who are after the **Cheese Feast**, a table full of cheesy dishes at the leftmost side of the screen.
|
||||||
|
|
||||||
|
> 🎯 **Objective**:
|
||||||
|
> The player’s goal is to protect the **Cheese Feast** by defending against waves of incoming mice
|
||||||
|
> To do this, the player can place different types of **[cats](#1-cats-)** with special abilities to attack the **[mice](#2-mice-)**
|
||||||
|
|
||||||
|
> 🏆 **Winning Condition**:
|
||||||
|
> Successfully stops all the mice from reaching the **Cheese Feast**
|
||||||
|
> The progress can be seen on the progress bar at the top right of the screen
|
||||||
|
|
||||||
|
> ❌ **Losing Condition**:
|
||||||
|
> A mouse reaches the **Cheese Feast**
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<img src="assets/README/cats_vs_mice.png" height="450">
|
||||||
|
</p>
|
||||||
|
|
||||||
|
### 🖼️ Game Interface
|
||||||
|
- **Background**: A 5x9 tiled kitchen floor with a checkered pattern
|
||||||
|
- **Cat Panel**: A row of clickable cat icons showing the price of each cat
|
||||||
|
- **Cheese Feast**: A table full of cheese dishes on the left side of the screen
|
||||||
|
- **Mice Entrance**: Mice appear from a hole on the floor on the right side of the screen
|
||||||
|
- **Defense Line**: Each row has a *robot vacuum* at the far left that activates once a mouse approaches it, sweeping away all mice in that row
|
||||||
|
- **Cheese** (currency): Drops from `🧑🍳 Chef Cat` periodically
|
||||||
|
- **Progress Bar**: Showing the progress of the game, if the progress bar is full, the player wins the game
|
||||||
|
|
||||||
|
## 🕹️ Game Mechanics
|
||||||
|
### 💸 Currency
|
||||||
|
- The currency of the game is `🧀 Cheese` (replacing the sun in *Plants vs. Zombies*)
|
||||||
|
- Players can collect cheese produced by `🧑🍳 Chef Cat`
|
||||||
|
- `🧀 Cheese` is used to buy different types of **[cats](#1-cats-)**, which can be placed on the 5x9 kitchen grid
|
||||||
|
|
||||||
|
### 👾 Characters
|
||||||
|
#### 1. Cats 🐈
|
||||||
|
- Objective: Protect the **Cheese Feast** by attacking the mice, slowing down the mice, or generating cheese. Each cat has a different ability and price
|
||||||
|
- Cats can only be placed in empty tiles
|
||||||
|
- Cats can be removed from the tiles by using the `Cage Button`
|
||||||
|
- Cats' HP is shown through its opacity, the lower the HP, the lower the opacity
|
||||||
|
|
||||||
|
**Cat Roles**
|
||||||
|
| Icon | Cat Type | Ability | Cheese Cost |
|
||||||
|
|------|----------|---------|-------------|
|
||||||
|
| <img src="assets/characters/single_yarn_cat.gif" width="75" height="75"> | Single Yarn Cat | Shoots 1 yarn ball every 3 seconds | 100 |
|
||||||
|
| <img src="assets/characters/double_yarn_cat.gif" width="75" height="75"> | Double Yarn Cat | Shoots 2 yarn balls every 3 seconds (double damage) | 200 |
|
||||||
|
| <img src="assets/characters/sleepy_cat.gif" width="75" height="75"> | Sleepy Cat | Sleeps until touched, then instantly attacks mouse that touched it | 150 |
|
||||||
|
| <img src="assets/characters/chef_cat.gif" width="75" height="75"> | Chef Cat | Produces 25 cheeses every 10 seconds | 50 |
|
||||||
|
| <img src="assets/characters/ice_cat.gif" width="75" height="75"> | Ice Cat | Slows down mice by throwing snowballs and attacks them | 150 |
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
- `🧶 Yarn` : reduces a mouse's HP by 20 points
|
||||||
|
- `❄️ Snowball` : reduces a mouse's HP by 8 points + speed reduction (-0.02 units/frame)
|
||||||
|
|
||||||
|
#### 2. Mice 🐁
|
||||||
|
- Objective: Mice enter from the right side and move left toward the **Cheese Feast**
|
||||||
|
- Mice will move forward in a straight line unless blocked by a cat
|
||||||
|
- If a mouse reaches a cat, it will attack the cat, which is indicated by a smoke puff
|
||||||
|
|
||||||
|
**Mouse Roles**
|
||||||
|
|
||||||
|
| Visual | Mouse Type | Description | Appearance |
|
||||||
|
|------|--------------|-------------|------------|
|
||||||
|
| <img src="assets/characters/basic_mouse.gif" width="75" height="75"> | Basic Mouse | Walks slowly (0.15), standard HP (100) | Early Game |
|
||||||
|
| <img src="assets/characters/helmet_mouse.gif" width="75" height="75"> | Helmet Mouse | Wears a helmet, high HP (150) | Mid Game |
|
||||||
|
| <img src="assets/characters/sporty_mouse.gif" width="75" height="75"> | Sporty Mouse | Moves quickly (2x Basic Mouse), low HP (80) | Mid Game |
|
||||||
|
| <img src="assets/characters/boss_mouse.gif" width="75" height="75"> | Boss Mouse | Very slow (3x Basic Mouse), very high HP (1000) | Late Game |
|
||||||
|
|
||||||
|
### 🔗 Interactions List
|
||||||
|
In the game, players can do the following actions:
|
||||||
|
| **Action**| **Trigger** | **Result**|
|
||||||
|
|----------------|-------------|-----------|
|
||||||
|
| Cheese pick up | Mouse click on cheese icon | Increase cheese count |
|
||||||
|
| Place cat | Mouse click on UI + tile | Deduct cheese, place cat |
|
||||||
|
| Remove cat | Mouse click on UI + cat | Remove cat from the tile |
|
||||||
|
|
||||||
|
In the game, these interactions between entities can happen:
|
||||||
|
| **Interaction**| **Trigger** | **Result**|
|
||||||
|
|----------------|-------------|-----------|
|
||||||
|
| Yarn / Snowball hits mouse | Collision | Mouse takes damage |
|
||||||
|
| Mouse reaches cat | Collision | Mouse attacks cat |
|
||||||
|
| Mouse reaches robot vacuum | Collision | Robot vacuum move to the right and remove all mice in the rows |
|
||||||
|
| Mouse reaches Cheese Feast | Collision | Trigger loss state |
|
||||||
|
| All mice defeated | Mouse count check | Trigger win state |
|
||||||
|
|
||||||
|
## 🗂️ Code Organization
|
||||||
|
|
||||||
|
### 💻 Tech Stack
|
||||||
|
This project is developed using JavaScript, HTML, and CSS for structuring and styling the user interface
|
||||||
|
|
||||||
|
Libraries used in the project:
|
||||||
|
- `p5.js`: for drawing and managing canvas-based graphics
|
||||||
|
- `p5.play`: for sprite and animation handling in the game environment
|
||||||
|
- `p5.scenemanager`: for managing multiple game scenes (start page and actual game page)
|
||||||
|
|
||||||
|
### 🧩 How Components Interact
|
||||||
|
This game is built using `Object-Oriented Programming (OOP)` principles to create reusable code. The overall structure of the code is as follows:
|
||||||
|
|
||||||
|
```
|
||||||
|
├── README.md
|
||||||
|
├── assets/ // all game assets
|
||||||
|
├── css/ // game styling
|
||||||
|
├── index.html // entry point of the game
|
||||||
|
├── js/ // external libraries
|
||||||
|
├── sketch.js // main game script
|
||||||
|
└── src // core game source code
|
||||||
|
├── classes // class definitions for game entities
|
||||||
|
│ ├── Cat.js
|
||||||
|
│ ├── Mouse.js
|
||||||
|
│ ├── RobotVacuum.js
|
||||||
|
├── constants
|
||||||
|
│ ├── Colors.js // centralized color palette
|
||||||
|
│ └── Prototype.js // dimensions from Figma prototype
|
||||||
|
├── level
|
||||||
|
│ ├── Level1.js // list of mice for level 1
|
||||||
|
│ └── WinLose.js // win/lose overlay
|
||||||
|
├── logic
|
||||||
|
│ ├── Controller.js // UI update functions
|
||||||
|
│ └── Helper.js // general utility functions
|
||||||
|
└── scenes // game scenes for screen manager
|
||||||
|
├── GameScene.js
|
||||||
|
└── StartScene.js
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
In this project, `GameScene.js` acts as the central coordinator of the gameplay. Below is the significance of each components:
|
||||||
|
- **Game Loop** (`GameScene.js`): checks for interactions between entities and calls the appropriate methods
|
||||||
|
- **Entity Classes** (`src/classes/`): each entity manages its own state and behavior (action(), attack(), etc.), which will be called by `GameScene.js`
|
||||||
|
- **UI Sync** (`Controller.js`): updates DOM (cheese count, progress bar, etc.) based on internal game state
|
||||||
|
|
||||||
|
The UML for the classes is as follows:
|
||||||
|
<p align="center">
|
||||||
|
<img src="assets/README/classes_uml.png" height="450">
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
### 💡 Design Patterns Used
|
||||||
|
#### 🏭 Factory Method Pattern
|
||||||
|
- Implemented in the `createCat(type, x, y)` and `createMouse(type, row)` functions
|
||||||
|
- These functions abstract away the creation logic and allows easy type extension
|
||||||
|
|
||||||
|
#### ♟️ Strategy Pattern
|
||||||
|
- Each `🐱 Cat` subclasses overrides the action() method to implement different attack behaviors (e.g., throwing yarn, exploding)
|
||||||
|
|
||||||
|
## ⭐️ Special Features
|
||||||
|
- **Opacity-based HP indicator**: the cats and mice's opacity lowers as their HP decreases
|
||||||
|
- **Robot Vacuum**: Activated when a mouse touches it and will defeat all mice in its row upon activation
|
||||||
|
|
||||||
|
## ⚠️ Limitation
|
||||||
|
The current version still has these limitations:
|
||||||
|
- When a `💤 SleepyCat` overlaps with 2 mice, it only attacks the first mouse that touches it instead of both
|
||||||
|
- When 2 mice perfectly overlap with each other, one of them won't be detected by other entities like `RobotVacuum`
|
||||||
|
|
||||||
|
## 🙌 Acknowledgement
|
||||||
|
- I learned how to use p5.play from the tutorial at [Learn p5play](https://p5play.org/learn/index.html)
|
||||||
|
- I found solutions to coding issues on [Stack Overflow](https://stackoverflow.com/questions)
|
||||||
|
- I referred to [W3Schools CSS Reference](https://www.w3schools.com/cssref/index.php) for styling help
|
||||||
|
- Some visual assets were sourced from [Flaticon](https://www.w3schools.com/cssref/index.php), [PNGTree](https://pngtree.com/), [LovePik](https://lovepik.com/), [PNGEgg](https://www.pngegg.com/)
|
|
@ -1,30 +0,0 @@
|
||||||
import { gameFrame } from './prototype.js';
|
|
||||||
import { imageAssets } from './sketch.js';
|
|
||||||
import { activeMice } from './GameScene.js';
|
|
||||||
|
|
||||||
export class RobotVacuum {
|
|
||||||
constructor(x, y, row) {
|
|
||||||
this.sprite = createSprite(x, y, gameFrame.robotSize, gameFrame.robotSize)
|
|
||||||
this.sprite.image = imageAssets.robotVacuum;
|
|
||||||
this.sprite.scale = gameFrame.tileWidth / 1000;
|
|
||||||
this.sprite.layer = 2;
|
|
||||||
this.activated = false;
|
|
||||||
this.row = row;
|
|
||||||
}
|
|
||||||
|
|
||||||
action() {
|
|
||||||
if (!this.activated) {
|
|
||||||
this.activated = true;
|
|
||||||
this.sprite.vel.x = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = 0; i < activeMice[this.row].length; i++) {
|
|
||||||
let currMouse = activeMice[this.row][i];
|
|
||||||
if (this.sprite.overlaps(currMouse.sprite)) {
|
|
||||||
activeMice[this.row].splice(i, 1);
|
|
||||||
currMouse.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
BIN
assets/.DS_Store
vendored
BIN
assets/README/cats_vs_mice.png
Normal file
After Width: | Height: | Size: 294 KiB |
BIN
assets/README/classes_uml.png
Normal file
After Width: | Height: | Size: 276 KiB |
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 81 KiB |
BIN
assets/cat/ice_cat_ani.png
Normal file
After Width: | Height: | Size: 60 KiB |
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 81 KiB |
BIN
assets/cat/sleepy_cat_ani.png
Normal file
After Width: | Height: | Size: 61 KiB |
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.2 KiB |
BIN
assets/characters/.DS_Store
vendored
Normal file
BIN
assets/characters/basic_mouse.gif
Normal file
After Width: | Height: | Size: 1015 KiB |
BIN
assets/characters/boss_mouse.gif
Normal file
After Width: | Height: | Size: 559 KiB |
BIN
assets/characters/chef_cat.gif
Normal file
After Width: | Height: | Size: 306 KiB |
BIN
assets/characters/double_yarn_cat.gif
Normal file
After Width: | Height: | Size: 784 KiB |
BIN
assets/characters/helmet_mouse.gif
Normal file
After Width: | Height: | Size: 630 KiB |
BIN
assets/characters/ice_cat.gif
Normal file
After Width: | Height: | Size: 678 KiB |
BIN
assets/characters/single_yarn_cat.gif
Normal file
After Width: | Height: | Size: 762 KiB |
BIN
assets/characters/sleepy_cat.gif
Normal file
After Width: | Height: | Size: 250 KiB |
BIN
assets/characters/sporty_mouse.gif
Normal file
After Width: | Height: | Size: 1.2 MiB |
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.5 KiB |
BIN
assets/game_background.png
Normal file
After Width: | Height: | Size: 71 KiB |
Before Width: | Height: | Size: 16 KiB |
BIN
assets/gray_explosion.png
Normal file
After Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 60 KiB |
BIN
assets/icon/.DS_Store
vendored
Normal file
Before Width: | Height: | Size: 286 KiB After Width: | Height: | Size: 286 KiB |
Before Width: | Height: | Size: 335 KiB After Width: | Height: | Size: 335 KiB |
Before Width: | Height: | Size: 301 KiB After Width: | Height: | Size: 301 KiB |
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 315 KiB After Width: | Height: | Size: 315 KiB |
Before Width: | Height: | Size: 160 KiB After Width: | Height: | Size: 160 KiB |
BIN
assets/mouse.png
Before Width: | Height: | Size: 37 KiB |
BIN
assets/mouse/.DS_Store
vendored
Normal file
BIN
assets/mouse/basic_mouse_ani.png
Normal file
After Width: | Height: | Size: 40 KiB |
BIN
assets/mouse/boss_mouse_ani.png
Normal file
After Width: | Height: | Size: 76 KiB |
BIN
assets/mouse/helmet_mouse_ani.png
Normal file
After Width: | Height: | Size: 46 KiB |
BIN
assets/mouse/sporty_mouse_ani.png
Normal file
After Width: | Height: | Size: 45 KiB |
BIN
assets/red_explosion.png
Normal file
After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 1.2 MiB |
Before Width: | Height: | Size: 61 KiB |
206
css/style.css
|
@ -2,7 +2,9 @@
|
||||||
--dark-brown: #503E28;
|
--dark-brown: #503E28;
|
||||||
--med-brown: #B09472;
|
--med-brown: #B09472;
|
||||||
--light-brown: #CAB49A;
|
--light-brown: #CAB49A;
|
||||||
|
--highlight-brown: #A8845D;
|
||||||
--dark-yellow: #F7E7BE;
|
--dark-yellow: #F7E7BE;
|
||||||
|
--med-yellow: #EDDBBD;
|
||||||
--light-yellow: #FDF4E5;
|
--light-yellow: #FDF4E5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,76 +17,97 @@ body {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: 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 {
|
#gameFrame {
|
||||||
position: relative;
|
position: relative;
|
||||||
flex-shrink: 0;
|
|
||||||
max-width: 800px;
|
max-width: 800px;
|
||||||
width: 60vw;
|
width: 60vw;
|
||||||
aspect-ratio: 800/569;
|
aspect-ratio: 800/569;
|
||||||
border-radius: 35px;
|
border-radius: 35px;
|
||||||
/* border: 10px solid #000; */
|
|
||||||
background: #FFFEF9;
|
background: #FFFEF9;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
canvas {
|
#endingOverlay {
|
||||||
display: block;
|
position: fixed;
|
||||||
width: 100%;
|
max-width: 800px;
|
||||||
height: 100%;
|
width: 60vw;
|
||||||
border-radius: 25px;
|
aspect-ratio: 800/569;
|
||||||
/* border: 1px solid #000; */
|
border-radius: 35px;
|
||||||
z-index: 0;
|
background-color: rgba(80, 62, 40, 0.75);
|
||||||
}
|
z-index: 100;
|
||||||
|
|
||||||
#upperContainer {
|
|
||||||
position: absolute;
|
|
||||||
gap: 16px;
|
|
||||||
align-items: center;
|
|
||||||
background: var(--med-brown);
|
|
||||||
border-radius: 25px 25px 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#controlPanel {
|
|
||||||
/* background: var(--light-brown); */
|
|
||||||
border-radius: 30px 30px 0 0;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
gap: 4px;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
#cheeseLabel {
|
|
||||||
margin-left: 20px;
|
|
||||||
width: 2.6%;
|
|
||||||
}
|
|
||||||
|
|
||||||
#cheeseDisplay {
|
|
||||||
background: var(--light-brown);
|
|
||||||
height: 100%;
|
|
||||||
width: auto;
|
|
||||||
aspect-ratio: 80/83;
|
|
||||||
border-radius: 8px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.buttonIcon {
|
#endingText {
|
||||||
width: 60%
|
color: var(--dark-yellow);
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: x-large;
|
||||||
|
font-family: "Fredoka", sans-serif;
|
||||||
|
margin-top: 5%;
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
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;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#controlPanel {
|
||||||
|
width: 73.4%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#cheeseLabel {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#catsLabel {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#cheeseDisplay {
|
||||||
|
background: var(--light-brown);
|
||||||
|
height: 100%;
|
||||||
|
width: 13.5%;
|
||||||
|
border-radius: 12%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
#catToolbar {
|
#catToolbar {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 46%;
|
width: 65%
|
||||||
gap: 1px;
|
|
||||||
background: var(--light-brown);
|
|
||||||
/* padding: 4px 8px; */
|
|
||||||
border-radius: 6px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.catButton {
|
.catButton {
|
||||||
|
@ -93,31 +116,49 @@ canvas {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background: var(--light-brown);
|
background: var(--light-brown);
|
||||||
border: 1px solid var(--dark-brown);
|
border: 1px solid var(--med-brown);
|
||||||
border-radius: 4px;
|
|
||||||
padding: 4px 6px;
|
padding: 4px 6px;
|
||||||
font-size: 18px;
|
|
||||||
cursor: pointer;
|
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 {
|
.catButton span {
|
||||||
font-size: 12px;
|
margin-top: 5%;
|
||||||
color: #333;
|
font-size: small;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--dark-brown);
|
||||||
}
|
}
|
||||||
|
|
||||||
.catButton:hover {
|
.catButton:not(:disabled):hover {
|
||||||
background: var(--dark-yellow);
|
background: var(--dark-yellow);
|
||||||
}
|
}
|
||||||
|
|
||||||
.catButton.activeButton {
|
.catButton.activeButton {
|
||||||
background: red;
|
background: var(--light-yellow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.catButton.activeButton:hover {
|
||||||
|
background: var(--med-yellow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.divider {
|
||||||
|
width: 1.5%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#petCage {
|
#petCage {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: auto;
|
width: auto;
|
||||||
aspect-ratio: 66/83;
|
aspect-ratio: 60/83;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 12%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.catIcon {
|
.catIcon {
|
||||||
|
@ -126,28 +167,65 @@ canvas {
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cheeseIcon {
|
#cageIcon {
|
||||||
width: 80%;
|
width: 100%;
|
||||||
height: auto;
|
height: auto;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#cheeseIcon {
|
||||||
|
width: 50%;
|
||||||
|
height: auto;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
#cheeseCount {
|
||||||
|
margin-top: 10%;
|
||||||
|
font-size: medium;
|
||||||
|
}
|
||||||
|
|
||||||
#gameProgressWrapper {
|
#gameProgressWrapper {
|
||||||
|
width: 24%;
|
||||||
|
height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
#gameProgress {
|
#gameProgress {
|
||||||
width: 100px;
|
appearance: none;
|
||||||
height: 10px;
|
width: 75%;
|
||||||
|
height: 20%;
|
||||||
|
border-radius: 100px;
|
||||||
|
margin-top: 5%;
|
||||||
|
background-color: var(--dark-brown);
|
||||||
|
border: 4px solid var(--dark-brown);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
#gameProgress::-webkit-progress-bar {
|
||||||
|
background-color: var(--dark-brown);
|
||||||
|
border-radius: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#gameProgress::-webkit-progress-value {
|
||||||
|
background-color: var(--dark-yellow);
|
||||||
|
border-radius: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#gameProgressLabel {
|
||||||
|
font-size: 0.75em;
|
||||||
|
font-weight: 600;
|
||||||
|
font-family: "Inter", sans-serif;
|
||||||
|
margin-top: 4%;
|
||||||
|
color: var(--dark-brown);
|
||||||
}
|
}
|
||||||
|
|
||||||
#gameBackground {
|
#gameBackground {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
translate: -50%;
|
translate: -50%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
/* z-index: -1; */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.Button {
|
.Button {
|
||||||
|
@ -162,5 +240,11 @@ canvas {
|
||||||
}
|
}
|
||||||
|
|
||||||
.Button:hover {
|
.Button:hover {
|
||||||
background: #a8845d;
|
background: var(--highlight-brown);
|
||||||
|
}
|
||||||
|
|
||||||
|
#menuButton {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 15px;
|
||||||
}
|
}
|
50
index.html
|
@ -14,57 +14,69 @@
|
||||||
<script type="module" src="sketch.js"></script>
|
<script type="module" src="sketch.js"></script>
|
||||||
|
|
||||||
<link rel="stylesheet" type="text/css" href="css/style.css" />
|
<link rel="stylesheet" type="text/css" href="css/style.css" />
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Fredoka:wght@300..700&family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&family=Paytone+One&display=swap" rel="stylesheet">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<!-- <script defer src="sketch.js"></script> -->
|
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<h1 id="title">Cats vs Mice</h1>
|
<h1 id="title">Cats vs Mice</h1>
|
||||||
<div id="gameFrame">
|
<div id="gameFrame">
|
||||||
|
<div id="endingOverlay">
|
||||||
|
<h1 id="endingText">Congrats! You've finished level 1!</h1>
|
||||||
|
<button class="Button" id="quitGameButton">Quit Game</button>
|
||||||
|
</div>
|
||||||
<div id="upperContainer">
|
<div id="upperContainer">
|
||||||
<div id="controlPanel">
|
<div id="controlPanel">
|
||||||
<!-- <img src="assets/cheeseLabel.png" alt="Cheese Label" id="cheeseLabel"> -->
|
<img src="assets/cheeseLabel.png" alt="Cheese Label" id="cheeseLabel">
|
||||||
<label>CH</label>
|
|
||||||
<div id="cheeseDisplay">
|
<div id="cheeseDisplay">
|
||||||
<img src="assets/cheese.png" alt="cheeseIcon" class="buttonIcon" id="cheeseIcon">
|
<img src="assets/cheese.png" alt="Cheese Icon" id="cheeseIcon">
|
||||||
<span id="cheeseCount">50</span>
|
<span id="cheeseCount">50</span>
|
||||||
</div>
|
</div>
|
||||||
<!-- <img src="assets/catsLabel.png" alt="Cat Label" id="cheeseLabel"> -->
|
|
||||||
<label>CA</label>
|
<img src="assets/catsLabel.png" alt="Cat Label" id="catsLabel">
|
||||||
|
|
||||||
<div id="catToolbar">
|
<div id="catToolbar">
|
||||||
<button class="catButton" id="chefCat">
|
<button class="catButton" id="chefCat">
|
||||||
<img src="assets/chef_cat_icon.png" alt="Chef Cat" class="catIcon" />
|
<img src="assets/icon/chef_cat_icon.png" alt="Chef Cat" class="catIcon" />
|
||||||
<span>50</span>
|
<span>50</span>
|
||||||
</button>
|
</button>
|
||||||
<button class="catButton" id="singleYarnCat">
|
<button class="catButton" id="singleYarnCat">
|
||||||
<img src="assets/single_yarn_cat_icon.png" alt="Single Yarn Cat" class="catIcon" />
|
<img src="assets/icon/single_yarn_cat_icon.png" alt="Single Yarn Cat" class="catIcon" />
|
||||||
<span>100</span>
|
<span>100</span>
|
||||||
</button>
|
</button>
|
||||||
<button class="catButton" id="doubleYarnCat">
|
<button class="catButton" id="doubleYarnCat">
|
||||||
<img src="assets/double_yarn_cat_icon.png" alt="Double Yarn Cat" class="catIcon" />
|
<img src="assets/icon/double_yarn_cat_icon.png" alt="Double Yarn Cat" class="catIcon" />
|
||||||
<span>200</span>
|
<span>200</span>
|
||||||
</button>
|
</button>
|
||||||
<button class="catButton" id="sleepyCat">
|
<button class="catButton" id="sleepyCat">
|
||||||
<img src="assets/sleepy_cat_icon.png" alt="Sleepy Cat" class="catIcon" />
|
<img src="assets/icon/sleepy_cat_icon.png" alt="Sleepy Cat" class="catIcon" />
|
||||||
<span>150</span>
|
<span>150</span>
|
||||||
</button>
|
</button>
|
||||||
<button class="catButton" id="iceCat">
|
<button class="catButton" id="iceCat">
|
||||||
<img src="assets/ice_cat_icon.png" alt="Ice Cat" class="catIcon" />
|
<img src="assets/icon/ice_cat_icon.png" alt="Ice Cat" class="catIcon" />
|
||||||
<span>150</span>
|
<span>150</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<span class="divider"></span>
|
||||||
|
|
||||||
<button class="catButton" id="petCage">
|
<button class="catButton" id="petCage">
|
||||||
<img src="assets/petCage.png" alt="Pet Cage" class="catIcon" />
|
<img src="assets/icon/petCage.png" alt="Pet Cage" id="cageIcon" />
|
||||||
<span>200</span>
|
|
||||||
</button>
|
</button>
|
||||||
<!-- <div id="gameProgressWrapper">
|
</div>
|
||||||
<progress id="gameProgress" value="30" max="100"></progress>
|
<div id="gameProgressWrapper">
|
||||||
<label>Game Progress</label>
|
<progress id="gameProgress" value="0" max="100"></progress>
|
||||||
</div> -->
|
<label id="gameProgressLabel">Game Progress</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button class="Button" id="startButton">Start Game</button>
|
<button class="Button" id="startButton">Start Game</button>
|
||||||
<button class="Button" id="menuButton">Menu</button>
|
<div id="menuButton">
|
||||||
|
<button class="Button" id="restartButton">Restart Game</button>
|
||||||
|
<button class="Button" id="quitButton">Quit Game</button>
|
||||||
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
|
@ -1,10 +0,0 @@
|
||||||
export const level1Mice = [
|
|
||||||
{ time: 5, type: 'basicMouse', row: 0 },
|
|
||||||
{ time: 10, type: 'basicMouse', row: 3 },
|
|
||||||
{ time: 20, type: 'basicMouse', row: 2 },
|
|
||||||
{ time: 23, type: 'basicMouse', row: 0 },
|
|
||||||
{ time: 30, type: 'basicMouse', row: 4 },
|
|
||||||
{ time: 36, type: 'helmetMouse', row: 1 },
|
|
||||||
{ time: 45, type: 'basicMouse', row: 0},
|
|
||||||
{ time: 55, type: 'basicMouse', row: 0}
|
|
||||||
]
|
|
93
sketch.js
|
@ -1,63 +1,48 @@
|
||||||
import { StartScene } from './StartScene.js';
|
import { StartScene } from './src/scenes/StartScene.js';
|
||||||
import { GameScene } from './GameScene.js';
|
import { GameScene } from './src/scenes/GameScene.js';
|
||||||
|
|
||||||
export let mgr;
|
export let mgr;
|
||||||
const gameParent = document.getElementById('gameFrame');
|
export let startPageAni;
|
||||||
const controlPanel = document.getElementById('controlPanel');
|
export const imageAssets = {};
|
||||||
const startButton = document.getElementById('startButton');
|
|
||||||
export let startPageAni, robotVacuum, gameBackground;
|
|
||||||
export let imageAssets = {};
|
|
||||||
export const catImages = {};
|
|
||||||
export const catAnimation = {};
|
export const catAnimation = {};
|
||||||
|
export const mouseAnimation = {};
|
||||||
export let selectedCatType = null;
|
export let selectedCatType = null;
|
||||||
|
|
||||||
|
const gameParent = document.getElementById('gameFrame');
|
||||||
|
const startButton = document.getElementById('startButton');
|
||||||
|
const restartButton = document.getElementById('restartButton');
|
||||||
|
const quitButton = document.getElementById('quitButton');
|
||||||
|
const quitGameButton = document.getElementById('quitGameButton');
|
||||||
|
|
||||||
function preload() {
|
function preload() {
|
||||||
|
// Load all images and animations
|
||||||
startPageAni = loadAni('assets/start_page_ani.png', {
|
startPageAni = loadAni('assets/start_page_ani.png', {
|
||||||
width: 1440, height: 1024, frames: 5
|
width: 1440, height: 1024, frames: 5
|
||||||
});
|
});
|
||||||
startPageAni.frameDelay = 10;
|
startPageAni.frameDelay = 10;
|
||||||
robotVacuum = loadImage('assets/robot_vacuum.png');
|
|
||||||
gameBackground = loadImage('assets/gamebg3.png');
|
|
||||||
|
|
||||||
imageAssets.cheese = loadImage('assets/cheese.png');
|
imageAssets.cheese = loadImage('assets/cheese.png');
|
||||||
imageAssets.yarn = loadImage('assets/yarn.png');
|
imageAssets.yarn = loadImage('assets/yarn.png');
|
||||||
imageAssets.snowball = loadImage('assets/snowball.png');
|
imageAssets.snowball = loadImage('assets/snowball.png');
|
||||||
imageAssets.robotVacuum = loadImage('assets/robot_vacuum.png');
|
imageAssets.robotVacuum = loadImage('assets/robot_vacuum.png');
|
||||||
imageAssets.gameBackground = loadImage('assets/gamebg3.png');
|
imageAssets.gameBackground = loadImage('assets/game_background.png');
|
||||||
imageAssets.mouse = loadImage('assets/mouse.png');
|
imageAssets.redExplosion = loadImage('assets/red_explosion.png');
|
||||||
|
imageAssets.grayExplosion = loadImage('assets/gray_explosion.png');
|
||||||
|
|
||||||
catImages.chefCat = loadImage('assets/chef_cat_icon.png');
|
catAnimation.chefCat = loadImage('assets/cat/chef_cat_ani.png');
|
||||||
catImages.singleYarnCat = loadImage('assets/single_yarn_cat_icon.png');
|
catAnimation.singleYarnCat = loadImage('assets/cat/single_yarn_cat_ani.png');
|
||||||
catImages.doubleYarnCat = loadImage('assets/double_yarn_cat_icon.png');
|
catAnimation.doubleYarnCat = loadImage('assets/cat/double_yarn_cat_ani.png');
|
||||||
catImages.sleepyCat = loadImage('assets/sleepy_cat_icon.png');
|
catAnimation.sleepyCat = loadImage('assets/cat/sleepy_cat_ani.png');
|
||||||
catImages.iceCat = loadImage('assets/ice_cat_icon.png');
|
catAnimation.iceCat = loadImage('assets/cat/ice_cat_ani.png');
|
||||||
|
|
||||||
catAnimation.chefCat = loadImage('assets/chef_cat_ani.png');
|
mouseAnimation.basicMouse = loadImage('assets/mouse/basic_mouse_ani.png');
|
||||||
catAnimation.singleYarnCat = loadImage('assets/single_yarn_cat_ani.png');
|
mouseAnimation.helmetMouse = loadImage('assets/mouse/helmet_mouse_ani.png');
|
||||||
catAnimation.doubleYarnCat = loadImage('assets/double_yarn_cat_ani.png');
|
mouseAnimation.sportyMouse = loadImage('assets/mouse/sporty_mouse_ani.png');
|
||||||
catAnimation.sleepyCat = loadImage('assets/sleepy_cat_ani.png');
|
mouseAnimation.bossMouse = loadImage('assets/mouse/boss_mouse_ani.png');
|
||||||
catAnimation.iceCat = loadImage('assets/ice_cat_ani.png');
|
|
||||||
|
|
||||||
// catAnimation.chefCat = loadAni('assets/chef_cat_ani.png', {
|
|
||||||
// width: 200, height: 200, frames: 4
|
|
||||||
// });
|
|
||||||
// catAnimation.singleYarnCat = loadAni('assets/single_yarn_cat_ani.png', {
|
|
||||||
// width: 200, height:200, frames: 8
|
|
||||||
// });
|
|
||||||
// catAnimation.doubleYarnCat = loadAni('assets/double_yarn_cat_ani.png', {
|
|
||||||
// width: 200, height: 200, frames: 8
|
|
||||||
// });
|
|
||||||
// catAnimation.iceCat = loadAni('assets/ice_cat_ani.png', {
|
|
||||||
// width: 200, height: 200, frames: 8
|
|
||||||
// });
|
|
||||||
|
|
||||||
// catAnimation.chefCat.frameDelay = 10;
|
|
||||||
// catAnimation.singleYarnCat.frameDelay = 5;
|
|
||||||
// catAnimation.doubleYarnCat.frameDelay = 30;
|
|
||||||
// catAnimation.iceCat.frameDelay = 5;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function setup() {
|
function setup() {
|
||||||
|
// Set the canvas to #gameFrame
|
||||||
const {width, height} = gameParent.getBoundingClientRect();
|
const {width, height} = gameParent.getBoundingClientRect();
|
||||||
|
|
||||||
const canvas = createCanvas(width, height);
|
const canvas = createCanvas(width, height);
|
||||||
|
@ -66,9 +51,8 @@ function setup() {
|
||||||
|
|
||||||
mgr.addScene(StartScene);
|
mgr.addScene(StartScene);
|
||||||
mgr.addScene(GameScene);
|
mgr.addScene(GameScene);
|
||||||
// mgr.addScene(PauseScene);
|
|
||||||
|
|
||||||
mgr.showScene(GameScene);
|
mgr.showScene(StartScene);
|
||||||
}
|
}
|
||||||
|
|
||||||
function draw() {
|
function draw() {
|
||||||
|
@ -79,20 +63,33 @@ function mousePressed() {
|
||||||
mgr.handleEvent("mousePressed");
|
mgr.handleEvent("mousePressed");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset selectedCatType to null
|
||||||
|
*/
|
||||||
export function resetCatType() {
|
export function resetCatType() {
|
||||||
console.log(`reset is called`)
|
|
||||||
selectedCatType = null;
|
selectedCatType = null;
|
||||||
document.querySelectorAll('.catButton').forEach(button =>
|
document.querySelectorAll('.catButton').forEach(button =>
|
||||||
button.classList.remove('activeButton')
|
button.classList.remove('activeButton')
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add event listener to the buttons
|
||||||
startButton.addEventListener('click', function (event) {
|
startButton.addEventListener('click', function (event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
mgr.showScene(GameScene);
|
mgr.showScene(GameScene);
|
||||||
});
|
});
|
||||||
|
|
||||||
menuButton.addEventListener('click', function (event) {
|
restartButton.addEventListener('click', function (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
mgr.showScene(GameScene);
|
||||||
|
});
|
||||||
|
|
||||||
|
quitButton.addEventListener('click', function (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
mgr.showScene(StartScene);
|
||||||
|
});
|
||||||
|
|
||||||
|
quitGameButton.addEventListener('click', function (event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
mgr.showScene(StartScene);
|
mgr.showScene(StartScene);
|
||||||
});
|
});
|
||||||
|
@ -102,7 +99,6 @@ document.querySelectorAll('.catButton').forEach(button => {
|
||||||
if (selectedCatType === button.id) {
|
if (selectedCatType === button.id) {
|
||||||
selectedCatType = null;
|
selectedCatType = null;
|
||||||
button.classList.remove('activeButton');
|
button.classList.remove('activeButton');
|
||||||
// document.body.style.cursor = 'default';
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
selectedCatType = button.id;
|
selectedCatType = button.id;
|
||||||
|
@ -110,17 +106,10 @@ document.querySelectorAll('.catButton').forEach(button => {
|
||||||
btn.classList.remove('activeButton')
|
btn.classList.remove('activeButton')
|
||||||
);
|
);
|
||||||
button.classList.add('activeButton');
|
button.classList.add('activeButton');
|
||||||
// const img = button.querySelector('img');
|
|
||||||
// if (img) {
|
|
||||||
// document.body.style.cursor = `url(${img.src}) 32 32, auto`;
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
console.log('Selected cat type:', selectedCatType);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
window.preload = preload;
|
window.preload = preload;
|
||||||
window.setup = setup;
|
window.setup = setup;
|
||||||
window.draw = draw;
|
window.draw = draw;
|
||||||
|
|
BIN
src/.DS_Store
vendored
Normal file
319
src/classes/Cat.js
Normal file
|
@ -0,0 +1,319 @@
|
||||||
|
import { catAnimation, imageAssets } from '../../sketch.js';
|
||||||
|
import { gameFrame } from '../constants/Prototype.js';
|
||||||
|
import { grid, cheeses, activeCats, activeMice, mouseGroup, throwableGroup, gameSprites } from '../scenes/GameScene.js';
|
||||||
|
import { calculateCell } from '../logic/Helper.js';
|
||||||
|
import { Yarn, Snowball } from './Throwable.js';
|
||||||
|
|
||||||
|
export const throwables = [];
|
||||||
|
const catAniDesc = {
|
||||||
|
chefCat: {
|
||||||
|
idle: { row: 0, frames: 4, frameSize: [200, 200], frameDelay: 10 },
|
||||||
|
action: { row: 0, frames: 4, frameSize: [200, 200], frameDelay: 10 }
|
||||||
|
},
|
||||||
|
singleYarnCat: {
|
||||||
|
idle: { row: 0, frameSize: [200, 200] },
|
||||||
|
action: {row: 1, frames: 8, frameSize: [200, 200], frameDelay: 22 }
|
||||||
|
},
|
||||||
|
doubleYarnCat: {
|
||||||
|
idle: { row: 0, frameSize: [200, 200] },
|
||||||
|
action: {row: 1, frames: 8, frameSize: [200, 200], frameDelay: 22 }
|
||||||
|
},
|
||||||
|
sleepyCat: {
|
||||||
|
idle: {row: 0, frames: 6, frameSize: [200, 200], frameDelay: 20 },
|
||||||
|
action: {row: 1, frames: 9, frameSize: [200, 200], frameDelay: 10 }
|
||||||
|
},
|
||||||
|
explosion: {
|
||||||
|
action: {row: 0, frames: 9, frameSize: [200, 200], frameDelay: 10 }
|
||||||
|
},
|
||||||
|
iceCat: {
|
||||||
|
idle: { row: 0, frameSize: [200, 200] },
|
||||||
|
action: {row: 1, frames: 8, frameSize: [200, 200], frameDelay: 22 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
this.sprite = createSprite(x, y, this.width, this.width);
|
||||||
|
this.sprite.spriteSheet = spriteSheet;
|
||||||
|
this.sprite.scale = gameFrame.catRatio;
|
||||||
|
this.sprite.addAnis(ani);
|
||||||
|
this.sprite.collider = 'static';
|
||||||
|
this.sprite.overlaps(throwableGroup);
|
||||||
|
this.sprite.layer = 1;
|
||||||
|
this.sprite.changeAni('idle');
|
||||||
|
this.active = false;
|
||||||
|
this.explosion = undefined;
|
||||||
|
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
this.cost = cost;
|
||||||
|
this.ani = ani;
|
||||||
|
this.HP = 100;
|
||||||
|
|
||||||
|
const { row, col } = calculateCell(x, y);
|
||||||
|
this.row = row;
|
||||||
|
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;
|
||||||
|
this.HP -= mouse.AP;
|
||||||
|
setTimeout(() => {
|
||||||
|
if (this.HP <= 0) {
|
||||||
|
this.remove();
|
||||||
|
mouse.targetCat = undefined;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.sprite.opacity = (this.HP / 100) * 0.7 + 0.3;
|
||||||
|
}
|
||||||
|
}, 1500);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the cat from the game
|
||||||
|
*/
|
||||||
|
remove() {
|
||||||
|
this.sprite.remove();
|
||||||
|
grid[this.row][this.col] = null;
|
||||||
|
|
||||||
|
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);
|
||||||
|
this.explosion.spriteSheet = spriteSheet;
|
||||||
|
this.explosion.scale = gameFrame.catRatio;
|
||||||
|
this.explosion.life = 90;
|
||||||
|
this.explosion.collider = 'none';
|
||||||
|
this.explosion.addAnis(catAniDesc.explosion);
|
||||||
|
this.explosion.changeAni('action');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cat that generates cheese periodically for resources
|
||||||
|
*/
|
||||||
|
class ChefCat extends Cat {
|
||||||
|
constructor(x, y) {
|
||||||
|
super(x, y, 50, catAnimation.chefCat, catAniDesc.chefCat);
|
||||||
|
this.lastProduced = millis();
|
||||||
|
this.offset = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
action() {
|
||||||
|
// 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();
|
||||||
|
this.offset = (this.offset + 1) % 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
this.lastShot = millis();
|
||||||
|
}
|
||||||
|
|
||||||
|
action() {
|
||||||
|
if (activeMice[this.row].length > 0) this.switchToAction();
|
||||||
|
else this.switchToIdle();
|
||||||
|
|
||||||
|
if (this.active && (millis() - this.lastShot > 3000)) {
|
||||||
|
let yarnX = this.x + gameFrame.tileWidth / 2;
|
||||||
|
let yarnY = this.y;
|
||||||
|
|
||||||
|
const yarn = new Yarn(yarnX, yarnY);
|
||||||
|
if (yarn) {
|
||||||
|
throwables.push(yarn);
|
||||||
|
throwableGroup.add(yarn.sprite);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lastShot = millis();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
this.lastShot = millis();
|
||||||
|
}
|
||||||
|
|
||||||
|
action() {
|
||||||
|
if (activeMice[this.row].length > 0) this.switchToAction();
|
||||||
|
else this.switchToIdle();
|
||||||
|
|
||||||
|
if (this.active && (millis() - this.lastShot > 3000)) {
|
||||||
|
for (let offset of [0, 0.3 * gameFrame.tileWidth]) {
|
||||||
|
let yarnX = this.x + gameFrame.tileWidth / 2 + offset;
|
||||||
|
let yarnY = this.y;
|
||||||
|
|
||||||
|
const yarn = new Yarn(yarnX, yarnY);
|
||||||
|
if (yarn) {
|
||||||
|
throwables.push(yarn);
|
||||||
|
throwableGroup.add(yarn.sprite);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lastShot = millis();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
this.awake = false;
|
||||||
|
this.hasAttacked = false;
|
||||||
|
this.wakeStart = undefined;
|
||||||
|
this.targetMouse = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
action(targetMouse) {
|
||||||
|
if (this.awake) {
|
||||||
|
this.switchToAction();
|
||||||
|
this.addExplosion(imageAssets.redExplosion);
|
||||||
|
this.wakeStart = millis();
|
||||||
|
this.targetMouse = targetMouse;
|
||||||
|
this.targetMouse.sprite.vel.x = 0;
|
||||||
|
this.targetMouse.sprite.changeAni('idle');
|
||||||
|
this.awake = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.wakeStart != undefined) {
|
||||||
|
if (!this.hasAttacked && this.targetMouse && millis() - this.wakeStart > 900) {
|
||||||
|
const explode = { point: 150 };
|
||||||
|
this.targetMouse.attacked(explode);
|
||||||
|
if (this.targetMouse && this.targetMouse.HP > 0) this.targetMouse.sprite.changeAni('walk');
|
||||||
|
this.hasAttacked = true;
|
||||||
|
}
|
||||||
|
if (millis() - this.wakeStart > 1480) {
|
||||||
|
this.remove();
|
||||||
|
this.explosion.remove();
|
||||||
|
this.explosion = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cat that throws snowballs at mice every 3 seconds
|
||||||
|
*/
|
||||||
|
class IceCat extends Cat {
|
||||||
|
constructor(x, y) {
|
||||||
|
super(x, y, 150, catAnimation.iceCat, catAniDesc.iceCat);
|
||||||
|
this.lastShot = millis();
|
||||||
|
}
|
||||||
|
|
||||||
|
action() {
|
||||||
|
if (activeMice[this.row].length > 0) this.switchToAction();
|
||||||
|
else this.switchToIdle();
|
||||||
|
|
||||||
|
if (this.active && (millis() - this.lastShot > 3000)) {
|
||||||
|
const snowballX = this.x + gameFrame.tileWidth / 2;
|
||||||
|
const snowballY = this.y;
|
||||||
|
|
||||||
|
const snowball = new Snowball(snowballX, snowballY)
|
||||||
|
if (snowball) {
|
||||||
|
throwables.push(snowball);
|
||||||
|
throwableGroup.add(snowball.sprite);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lastShot = millis();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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':
|
||||||
|
const chefCat = new ChefCat(x, y);
|
||||||
|
chefCat.action();
|
||||||
|
return chefCat;
|
||||||
|
case 'singleYarnCat':
|
||||||
|
return new SingleYarnCat(x, y);
|
||||||
|
case 'doubleYarnCat':
|
||||||
|
return new DoubleYarnCat(x, y);
|
||||||
|
case 'sleepyCat':
|
||||||
|
return new SleepyCat(x, y);
|
||||||
|
case 'iceCat':
|
||||||
|
return new IceCat(x, y);
|
||||||
|
default:
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
194
src/classes/Mouse.js
Normal file
|
@ -0,0 +1,194 @@
|
||||||
|
import { mouseAnimation } from '../../sketch.js';
|
||||||
|
import { gameFrame } from '../constants/Prototype.js';
|
||||||
|
import { activeMice, mouseGroup, gameSprites } from '../scenes/GameScene.js';
|
||||||
|
import { updateGameProgress } from '../logic/Controller.js';
|
||||||
|
import { Snowball } from './Throwable.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 {
|
||||||
|
/**
|
||||||
|
* 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 {p5.SpriteSheet} 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 * 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.targetCat = undefined;
|
||||||
|
this.lastAttack = 0;
|
||||||
|
this.defaultSpeed = speed;
|
||||||
|
this.defaultHP = HP;
|
||||||
|
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);
|
||||||
|
if (index != -1) activeMice[this.row - 1].splice(index, 1);
|
||||||
|
}
|
||||||
|
if (this.row + 1 < gameFrame.rows) {
|
||||||
|
index = activeMice[this.row + 1].indexOf(this);
|
||||||
|
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;
|
||||||
|
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');
|
||||||
|
if (this.lastAttack == 0 || millis() - this.lastAttack > 3000) {
|
||||||
|
this.targetCat.attacked(this);
|
||||||
|
this.lastAttack = millis();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.sprite.vel.x = this.defaultSpeed;
|
||||||
|
this.sprite.changeAni('walk');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles when the mouse is attacked and reduces its health
|
||||||
|
* @param {number} point - The damage taken by the mouse
|
||||||
|
*/
|
||||||
|
attacked(throwable) {
|
||||||
|
this.HP -= throwable.point;
|
||||||
|
if (this.HP <= 0) this.remove();
|
||||||
|
else {
|
||||||
|
this.sprite.opacity = (this.HP / this.defaultHP) * 0.5 + 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (throwable instanceof Snowball) {
|
||||||
|
this.defaultSpeed = min(-0.05, this.defaultSpeed + 0.02);
|
||||||
|
this.sprite.vel.x = this.defaultSpeed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Basic type of mouse
|
||||||
|
*/
|
||||||
|
class BasicMouse extends Mouse {
|
||||||
|
constructor(row) {
|
||||||
|
super(row, -0.15, 100, 20, mouseAnimation.basicMouse, gameFrame.tileWidth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helmet-wearing mouse
|
||||||
|
* Has a higher HP compared to BasicMouse
|
||||||
|
*/
|
||||||
|
class HelmetMouse extends Mouse {
|
||||||
|
constructor(row) {
|
||||||
|
super(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(row) {
|
||||||
|
super(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(row) {
|
||||||
|
super(row, -0.05, 1000, 50, mouseAnimation.bossMouse, 3 * gameFrame.tileWidth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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(row);
|
||||||
|
case 'helmetMouse':
|
||||||
|
return new HelmetMouse(row);
|
||||||
|
case 'sportyMouse':
|
||||||
|
return new SportyMouse(row);
|
||||||
|
case 'bossMouse':
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
49
src/classes/RobotVacuum.js
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
import { imageAssets } from '../../sketch.js';
|
||||||
|
import { gameFrame } from '../constants/Prototype.js';
|
||||||
|
import { activeMice, catGroup, throwableGroup, gameSprites, robotVacuums } from '../scenes/GameScene.js';
|
||||||
|
|
||||||
|
export class RobotVacuum {
|
||||||
|
constructor(x, y, row) {
|
||||||
|
this.sprite = createSprite(x, y, gameFrame.robotSize, gameFrame.robotSize)
|
||||||
|
this.sprite.image = imageAssets.robotVacuum;
|
||||||
|
this.sprite.scale = gameFrame.tileWidth / 1000;
|
||||||
|
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;
|
||||||
|
this.sprite.vel.x = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < activeMice[this.row].length; i++) {
|
||||||
|
let currMouse = activeMice[this.row][i];
|
||||||
|
if (this.sprite.overlaps(currMouse.sprite)) {
|
||||||
|
activeMice[this.row].splice(i, 1);
|
||||||
|
currMouse.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,14 +1,17 @@
|
||||||
import { gameFrame } from "./prototype.js";
|
import { imageAssets } from '../../sketch.js';
|
||||||
import { imageAssets } from "./sketch.js";
|
import { gameFrame } from '../constants/Prototype.js';
|
||||||
|
import { gameSprites } from '../scenes/GameScene.js';
|
||||||
|
|
||||||
export class Throwable {
|
export class Throwable {
|
||||||
constructor(x, y, point, img, width) {
|
constructor(x, y, point, img, width) {
|
||||||
this.sprite = createSprite(x, y, width, width);
|
this.sprite = createSprite(x, y, width, width);
|
||||||
|
gameSprites.push(this.sprite);
|
||||||
this.sprite.image = img;
|
this.sprite.image = img;
|
||||||
// TODO: check on the scale again
|
|
||||||
this.sprite.scale = gameFrame.tileWidth / 1024;
|
this.sprite.scale = gameFrame.tileWidth / 1024;
|
||||||
this.sprite.vel.x = 1;
|
this.sprite.vel.x = 1;
|
||||||
|
this.sprite.rotationSpeed = 1;
|
||||||
this.sprite.life = 600;
|
this.sprite.life = 600;
|
||||||
|
|
||||||
this.point = point;
|
this.point = point;
|
||||||
this.width = width;
|
this.width = width;
|
||||||
}
|
}
|
||||||
|
@ -18,14 +21,16 @@ export class Throwable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Yarn is thrown by singleYarnCat and doubleYarnCat
|
||||||
export class Yarn extends Throwable {
|
export class Yarn extends Throwable {
|
||||||
constructor(x, y) {
|
constructor(x, y) {
|
||||||
super(x, y, 15, imageAssets.yarn, gameFrame.tileWidth / 4);
|
super(x, y, 15, imageAssets.yarn, gameFrame.tileWidth / 4);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Snowball is thrown by IceCat
|
||||||
export class Snowball extends Throwable {
|
export class Snowball extends Throwable {
|
||||||
constructor(x, y) {
|
constructor(x, y) {
|
||||||
super(x, y, 20, imageAssets.snowball, gameFrame.tileWidth / 4);
|
super(x, y, 8, imageAssets.snowball, gameFrame.tileWidth / 4);
|
||||||
}
|
}
|
||||||
}
|
}
|
7
src/constants/Colors.js
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
export const Colors = {
|
||||||
|
dark_brown: '#503E28',
|
||||||
|
med_brown: '#B09472',
|
||||||
|
light_brown: '#CAB49A',
|
||||||
|
dark_yellow: '#F7E7BE',
|
||||||
|
light_yellow: '#FDF4E5'
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
|
// Numbers from the prototype designed on Figma
|
||||||
export const prototypeFrame = {
|
export const prototypeFrame = {
|
||||||
border: 10,
|
border: 10,
|
||||||
width: 800,
|
width: 800,
|
||||||
|
@ -13,6 +14,7 @@ export const prototypeFrame = {
|
||||||
controlPanelGap: 8
|
controlPanelGap: 8
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Numbers used in the actual gameFrame
|
||||||
export const gameFrame = {
|
export const gameFrame = {
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
65
src/level/Level1.js
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
export const level1Mice = [
|
||||||
|
// First 30 seconds — Setup Time (no mice)
|
||||||
|
|
||||||
|
// First minute (30 - 89) — Basic Mice only
|
||||||
|
{ time: 30, type: 'basicMouse', row: 2 },
|
||||||
|
{ time: 45, type: 'basicMouse', row: 0 },
|
||||||
|
{ time: 57, type: 'basicMouse', row: 3 },
|
||||||
|
{ time: 63, type: 'basicMouse', row: 1 },
|
||||||
|
{ time: 76, type: 'basicMouse', row: 0 },
|
||||||
|
{ time: 84, type: 'basicMouse', row: 1 },
|
||||||
|
|
||||||
|
// Minute 2 (90 - 149) — introduce Helmet Mouse
|
||||||
|
{ time: 92, type: 'helmetMouse', row: 0 },
|
||||||
|
{ time: 98, type: 'basicMouse', row: 4 },
|
||||||
|
{ time: 105, type: 'basicMouse', row: 2 },
|
||||||
|
{ time: 115, type: 'helmetMouse', row: 1 },
|
||||||
|
{ time: 125, type: 'helmetMouse', row: 3 },
|
||||||
|
{ time: 138, type: 'basicMouse', row: 3},
|
||||||
|
{ time: 149, type: 'basicMouse', row: 4},
|
||||||
|
|
||||||
|
// Minute 3 (150 - 209) — introduce Sporty Mouse
|
||||||
|
{ time: 161, type: 'sportyMouse', row: 1 },
|
||||||
|
{ time: 167, type: 'basicMouse', row: 0 },
|
||||||
|
{ time: 175, type: 'helmetMouse', row: 2 },
|
||||||
|
{ time: 179, type: 'basicMouse', row: 2 },
|
||||||
|
{ time: 185, type: 'sportyMouse', row: 4 },
|
||||||
|
{ time: 194, type: 'basicMouse', row: 3},
|
||||||
|
{ time: 208, type: 'basicMouse', row: 1},
|
||||||
|
|
||||||
|
// Minute 4 (210 - 269) — mixed mice
|
||||||
|
{ time: 212, type: 'basicMouse', row: 3 },
|
||||||
|
{ time: 216, type: 'helmetMouse', row: 4 },
|
||||||
|
{ time: 219, type: 'sportyMouse', row: 1 },
|
||||||
|
{ time: 230, type: 'basicMouse', row: 0 },
|
||||||
|
{ time: 235, type: 'helmetMouse', row: 2 },
|
||||||
|
{ time: 238, type: 'sportyMouse', row: 0 },
|
||||||
|
{ time: 243, type: 'sportyMouse', row: 3 },
|
||||||
|
{ time: 246, type: 'helmetMouse', row: 4 },
|
||||||
|
{ time: 251, type: 'sportyMouse', row: 2},
|
||||||
|
{ time: 253, type: 'helmetMouse', row: 1},
|
||||||
|
{ time: 257, type: 'basicMouse', row: 0 },
|
||||||
|
{ time: 260, type: 'helmetMouse', row: 1},
|
||||||
|
{ time: 266, type: 'sportyMouse', row: 4 },
|
||||||
|
|
||||||
|
// Minute 5 (270 - 329) — boss and support
|
||||||
|
{ time: 272, type: 'bossMouse', row: 2 },
|
||||||
|
{ time: 275, type: 'basicMouse', row: 1 },
|
||||||
|
{ time: 280, type: 'basicMouse', row: 4},
|
||||||
|
{ time: 282, type: 'helmetMouse', row: 1},
|
||||||
|
{ time: 285, type: 'sportyMouse', row: 0 },
|
||||||
|
{ time: 290, type: 'helmetMouse', row: 3 },
|
||||||
|
{ time: 296, type: 'sportyMouse', row: 2},
|
||||||
|
{ time: 299, type: 'basicMouse', row: 4 },
|
||||||
|
{ time: 303, type: 'helmetMouse', row: 2 },
|
||||||
|
{ time: 305, type: 'bossMouse', row: 3},
|
||||||
|
{ time: 307, type: 'sportyMouse', row: 0 },
|
||||||
|
{ time: 310, type: 'sportyMouse', row: 1 },
|
||||||
|
{ time: 312, type: 'basicMouse', row: 0},
|
||||||
|
{ time: 314, type: 'sportyMouse', row: 2 },
|
||||||
|
{ time: 317, type: 'helmetMouse', row: 1 },
|
||||||
|
{ time: 320, type: 'basicMouse', row: 3 },
|
||||||
|
{ time: 323, type: 'helmetMouse', row: 4},
|
||||||
|
{ time: 327, type: 'basicMouse', row: 0}
|
||||||
|
];
|
||||||
|
|
25
src/level/WinLose.js
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
const endingOverlay = document.getElementById('endingOverlay');
|
||||||
|
const endingText = document.getElementById('endingText');
|
||||||
|
const menuButton = document.getElementById('menuButton');
|
||||||
|
|
||||||
|
const endingMessage = {
|
||||||
|
win: "Nice job! The Cheese Feast is safe!",
|
||||||
|
lose: "Oh no! The mice crashed the Cheese Feast!<br>Give it another shot! (whiskers crossed)"
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Displays the winning screen with a congratulatory message
|
||||||
|
*/
|
||||||
|
export function showWinningScreen() {
|
||||||
|
endingText.innerHTML = endingMessage.win;
|
||||||
|
endingOverlay.style.display = 'flex';
|
||||||
|
menuButton.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays the losing screen with a retry encouragement message
|
||||||
|
*/
|
||||||
|
export function showLosingScreen() {
|
||||||
|
endingText.innerHTML = endingMessage.lose;
|
||||||
|
endingOverlay.style.display = 'flex';
|
||||||
|
menuButton.style.display = 'none';
|
||||||
|
}
|
64
src/logic/Controller.js
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
import { level1Mice } from '../level/Level1.js';
|
||||||
|
import { showWinningScreen } from '../level/WinLose.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';
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function restartGameProgress() {
|
||||||
|
miceKilled = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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();
|
||||||
|
}
|
||||||
|
}
|
30
src/logic/Helper.js
Normal file
|
@ -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;
|
||||||
|
}
|
266
src/scenes/GameScene.js
Normal file
|
@ -0,0 +1,266 @@
|
||||||
|
import { imageAssets, selectedCatType, resetCatType } from '../../sketch.js';
|
||||||
|
import { prototypeFrame, gameFrame } from '../constants/Prototype.js';
|
||||||
|
import { Colors } from '../constants/Colors.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 '../logic/Controller.js';
|
||||||
|
import { calculateCell, isCellValid } from '../logic/Helper.js';
|
||||||
|
|
||||||
|
const gameParent = document.getElementById('gameFrame');
|
||||||
|
const endingOverlay = document.getElementById('endingOverlay');
|
||||||
|
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';
|
||||||
|
endingOverlay.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)
|
||||||
|
}
|
|
@ -1,10 +1,11 @@
|
||||||
import { startPageAni } from './sketch.js';
|
import { startPageAni } from '../../sketch.js';
|
||||||
|
|
||||||
export function StartScene() {
|
export function StartScene() {
|
||||||
this.enter = function() {
|
this.enter = function() {
|
||||||
const self = this;
|
const self = this;
|
||||||
|
|
||||||
select('#upperContainer').hide();
|
select('#upperContainer').hide();
|
||||||
|
select('#endingOverlay').hide();
|
||||||
select('#menuButton').hide();
|
select('#menuButton').hide();
|
||||||
select('#startButton').show();
|
select('#startButton').show();
|
||||||
}
|
}
|