1230 lines
42 KiB
JavaScript
1230 lines
42 KiB
JavaScript
import { LEVEL_CONFIG } from './config.js';
|
|
|
|
export class GameScene extends Phaser.Scene {
|
|
constructor() {
|
|
super({ key: 'GameScene' });
|
|
|
|
this.gridConfig = {
|
|
allPadding: 50,
|
|
leftPadding: 700,
|
|
rows: 8,
|
|
cols: 8,
|
|
jewelWidth: 100,
|
|
jewelHeight: 100,
|
|
};
|
|
|
|
// Stats
|
|
this.numberOfJewels = 4;
|
|
this.startRows = 2;
|
|
this.level = 12;
|
|
this.matchesNeeded = 8;
|
|
this.score = 0;
|
|
this.ally = 'goblin';
|
|
this.spritePlus = 0;
|
|
this.gridColor = 0x000000;
|
|
|
|
// Status Indication
|
|
this.isDropping = false;
|
|
this.isSwapping = false;
|
|
this.isDestroying = false;
|
|
this.isMovingUp = false;
|
|
this.isPlayingVideo = false;
|
|
this.isPlayingAudio = false;
|
|
this.gameStatus = true;
|
|
this.rowOne = false;
|
|
|
|
// Add selectedJewel property
|
|
this.selectedJewel = null;
|
|
this.swapWithJewel = null;
|
|
|
|
// Timer properties
|
|
this.moveTimer = 0;
|
|
this.moveInterval = 12000;
|
|
this.timerText = null;
|
|
this.countdownTimer = false;
|
|
}
|
|
|
|
preload() {
|
|
// Jewels
|
|
this.load.spritesheet('jewels', 'assets/jewels.png', {
|
|
frameWidth: 100,
|
|
frameHeight: 100
|
|
});
|
|
|
|
// Fonts
|
|
this.load.font('cruiser', 'assets/NEUROPOL.ttf');
|
|
this.load.font('code', 'assets/CodePredators-Regular.otf');
|
|
|
|
// Music
|
|
this.load.audio('clear', 'assets/clear.mp3');
|
|
this.load.audio('level-up', 'assets/level-up.mp3');
|
|
this.load.audio('level-complete', 'assets/level-complete.mp3');
|
|
this.load.audio('switch', 'assets/switch.mp3');
|
|
this.load.audio('game-over', 'assets/game-over-boom.mp3');
|
|
this.load.audio('countdown', 'assets/countdown.mp3');
|
|
this.load.audio('alarm', 'assets/alarm.mp3');
|
|
|
|
// Ally Assets
|
|
const allys = ['goblin', 'surfer', 'bear', 'wizard'];
|
|
allys.forEach((ally) => {
|
|
this.load.video(`${ally}-resting`, `assets/${ally}-resting.mp4`);
|
|
this.load.video(`${ally}-excited`, `assets/${ally}-excited.mp4`);
|
|
this.load.video(`${ally}-pleased`, `assets/${ally}-pleased.mp4`);
|
|
this.load.video(`${ally}-match`, `assets/${ally}-match.mp4`);
|
|
this.load.video(`${ally}-background`, `assets/${ally}-background.mp4`);
|
|
this.load.audio(`${ally}-a1`, `assets/${ally}-a1.mp3`);
|
|
this.load.audio(`${ally}-a2`, `assets/${ally}-a2.mp3`);
|
|
this.load.audio(`${ally}-a3`, `assets/${ally}-a3.mp3`);
|
|
this.load.audio(`${ally}-b`, `assets/${ally}-b.mp3`);
|
|
this.load.audio(`${ally}-c`, `assets/${ally}-c.mp3`);
|
|
this.load.audio(`${ally}-intro`, `assets/${ally}-intro.mp3`);
|
|
this.load.audio(`${ally}-outro`, `assets/${ally}-outro.mp3`);
|
|
this.load.audio(`${ally}-music`, `assets/${ally}-music.mp3`);
|
|
});
|
|
}
|
|
|
|
create() {
|
|
// Set Start Level
|
|
const newLevel = LEVEL_CONFIG[this.level];
|
|
this.numberOfJewels = newLevel.numberOfJewels;
|
|
this.matchesNeeded = newLevel.matchesNeeded;
|
|
this.moveInterval = newLevel.moveInterval;
|
|
this.ally = newLevel.ally;
|
|
this.spritePlus = newLevel.spritePlus;
|
|
this.gridColor = newLevel.gridColor;
|
|
|
|
// Background Video
|
|
this.bgVideo = this.add.video(0, 0, `${this.ally}-background`);
|
|
this.bgVideo.setOrigin(0);
|
|
console.log("width",this.scale.width);
|
|
this.bgVideo.scaleX = this.scale.width / 848;
|
|
this.bgVideo.scaleY = this.scale.height / 480;
|
|
this.bgVideo.play(true);
|
|
|
|
// Create the Game Grid
|
|
this.makeGrid(this.gridColor);
|
|
this.physics.world.setBounds(this.grid.getBounds().x - 50, this.grid.getBounds().y - 50, this.grid.getBounds().width + 100, this.grid.getBounds().height + 100);
|
|
this.jewels = this.physics.add.group({
|
|
collideWorldBounds: true,
|
|
});
|
|
this.physics.add.collider(this.jewels, this.jewels);
|
|
this.createStart();
|
|
|
|
// Background Music
|
|
this.bgMusic = this.sound.add(`${this.ally}-music`, { volume: 0.2 });
|
|
this.bgMusic.loop = true;
|
|
this.bgMusic.play();
|
|
|
|
// Create Ally Video
|
|
this.allyVideo = this.add.video(350, 610, `${this.ally}-resting`).setOrigin(0.5);
|
|
this.allyVideo.play(true);
|
|
this.allyVideo.postFX.addGlow();
|
|
this.time.delayedCall(500, () => {
|
|
this.sound.play(`${this.ally}-intro`);
|
|
});
|
|
|
|
// Create Text Background
|
|
this.add.rectangle(10, 40, 650, 270, 0x000000, .5).setOrigin(0);
|
|
|
|
// Create the score text
|
|
this.scoreText = this.add.text(20, 50, 'Score: 0', {
|
|
fontFamily: 'cruiser, arial',
|
|
fontSize: '36px',
|
|
fill: '#ffffff',
|
|
padding: {
|
|
left: 10,
|
|
right: 10,
|
|
top: 5,
|
|
bottom: 5
|
|
}
|
|
}).setOrigin(0);
|
|
|
|
// Create the Level text
|
|
this.LevelText = this.add.text(20, 116, `Level: ${this.level}`, {
|
|
fontFamily: 'cruiser, arial',
|
|
fontSize: '36px',
|
|
fill: '#ffffff',
|
|
padding: {
|
|
left: 10,
|
|
right: 10,
|
|
top: 5,
|
|
bottom: 5
|
|
}
|
|
}).setOrigin(0);
|
|
|
|
// Create the timer text
|
|
this.timerText = this.add.text(20, 182, 'Next Row:', {
|
|
fontFamily: 'cruiser, arial',
|
|
fontSize: '36px',
|
|
fill: '#ffffff',
|
|
padding: {
|
|
left: 10,
|
|
right: 10,
|
|
top: 5,
|
|
bottom: 5
|
|
}
|
|
}).setOrigin(0);
|
|
|
|
// Create the Matches Needed text
|
|
this.matchesText = this.add.text(20, 248, `Remaining Matches: ${this.matchesNeeded}`, {
|
|
fontFamily: 'cruiser, arial',
|
|
fontSize: '36px',
|
|
fill: '#ffffff',
|
|
padding: {
|
|
left: 10,
|
|
right: 10,
|
|
top: 5,
|
|
bottom: 5
|
|
}
|
|
}).setOrigin(0);
|
|
}
|
|
|
|
update(time, delta) {
|
|
// Update the move timer
|
|
this.moveTimer += delta;
|
|
|
|
// Check if it's time to move all jewels up
|
|
if (
|
|
this.moveTimer >= this.moveInterval &&
|
|
this.isDropping === false &&
|
|
this.isSwapping === false &&
|
|
this.isDestroying === false &&
|
|
this.gameStatus === true
|
|
) {
|
|
this.moveAllJewelsUp();
|
|
this.moveTimer = 0; // Reset the timer
|
|
}
|
|
|
|
if (this.moveInterval - this.moveTimer <= 3000) {
|
|
this.startCountdownTimer();
|
|
}
|
|
|
|
// Update the timer display
|
|
const timeRemaining = (this.moveInterval - this.moveTimer) / 1000;
|
|
const displayTime = timeRemaining.toFixed(2);
|
|
if (this.timerText) {
|
|
this.timerText.setText(`Next Row: ${displayTime}`);
|
|
}
|
|
}
|
|
|
|
startCountdownTimer() {
|
|
if (this.countdownTimer === true || this.gameStatus === false) {
|
|
return;
|
|
}
|
|
|
|
this.countdownTimer = true;
|
|
this.sound.play('countdown');
|
|
this.time.delayedCall(6000, () => {
|
|
this.countdownTimer = false;
|
|
});
|
|
}
|
|
|
|
makeGrid(gridColor = 0x000000) {
|
|
this.grid = this.add.rectangle(
|
|
this.gridConfig.leftPadding + this.gridConfig.allPadding,
|
|
0 + this.gridConfig.allPadding,
|
|
this.gridConfig.cols * this.gridConfig.jewelWidth,
|
|
this.gridConfig.rows*this.gridConfig.jewelHeight,
|
|
gridColor,
|
|
.5
|
|
).setOrigin(0);
|
|
this.grid.setInteractive();
|
|
this.grid.on('pointerdown', (pointer) => {
|
|
this.handleGridClick(pointer.x, pointer.y);
|
|
});
|
|
}
|
|
|
|
createJewel(type, col, row) {
|
|
// Create a jewel sprite from the spritesheet using the type as frame index
|
|
const jewel = this.physics.add.sprite(
|
|
col * this.gridConfig.jewelWidth + this.gridConfig.leftPadding,
|
|
row * this.gridConfig.jewelHeight,
|
|
'jewels',
|
|
type + this.spritePlus
|
|
);
|
|
jewel.setOrigin(0.5);
|
|
jewel.setDisplaySize(this.gridConfig.jewelWidth, this.gridConfig.jewelHeight);
|
|
jewel.jewelType = type;
|
|
this.jewels.add(jewel);
|
|
|
|
// Add click event to the jewel
|
|
jewel.setInteractive();
|
|
jewel.on('pointerdown', () => {
|
|
this.handleJewelClick(jewel.x, jewel.y);
|
|
});
|
|
|
|
return jewel;
|
|
}
|
|
|
|
createStart() {
|
|
// Create jewels at the bottom of the grid
|
|
let createWait = 0;
|
|
const startRow = this.gridConfig.rows - this.startRows;
|
|
|
|
for (let row = this.startRows; row > 0; row--) {
|
|
for (let col = 1; col <= this.gridConfig.cols; col++) {
|
|
let type = null;
|
|
let match = false;
|
|
|
|
do {
|
|
type = Phaser.Math.Between(0, this.numberOfJewels - 1)
|
|
match = false;
|
|
|
|
if (this.getJewelAtPosition(col-1, startRow + row) === type &&
|
|
this.getJewelAtPosition(col-2, startRow + row) === type) {
|
|
match = true;
|
|
}
|
|
|
|
if (this.getJewelAtPosition(col, startRow + row + 1) === type &&
|
|
this.getJewelAtPosition(col, startRow + row + 2) === type) {
|
|
match = true;
|
|
}
|
|
} while (match === true);
|
|
|
|
// Create the jewel at the correct position
|
|
this.createJewel(type, col, startRow + row);
|
|
}
|
|
}
|
|
}
|
|
|
|
getJewelAtPosition(col, row) {
|
|
// Convert grid coordinates to world coordinates
|
|
const x = col * this.gridConfig.jewelWidth + this.gridConfig.leftPadding;
|
|
const y = row * this.gridConfig.jewelHeight;
|
|
|
|
// Find the jewel at that position
|
|
let jewelAtPosition = null;
|
|
|
|
// Iterate through all jewels to find one near the specified position
|
|
this.jewels.children.iterate((jewel) => {
|
|
if (jewel &&
|
|
Math.abs(jewel.x - x) < this.gridConfig.jewelWidth / 2 &&
|
|
Math.abs(jewel.y - y) < this.gridConfig.jewelHeight / 2) {
|
|
jewelAtPosition = jewel;
|
|
return false; // Stop iteration
|
|
}
|
|
});
|
|
|
|
// Return the jewel type or null if not found
|
|
return jewelAtPosition ? jewelAtPosition.jewelType : null;
|
|
}
|
|
|
|
// Handle clicks on the grid
|
|
handleGridClick(x, y) {
|
|
if (!this.selectedJewel) {
|
|
return;
|
|
}
|
|
|
|
const selectedX = this.selectedJewel.x;
|
|
const selectedY = this.selectedJewel.y;
|
|
|
|
// Calculate which grid tile was clicked
|
|
const clickCol = Math.floor((x+50) / this.gridConfig.jewelWidth);
|
|
const clickRow = Math.floor((y+50) / this.gridConfig.jewelHeight);
|
|
|
|
// Calculate selected jewel's grid position
|
|
const selectedCol = Math.floor(selectedX / this.gridConfig.jewelWidth);
|
|
const selectedRow = Math.floor(selectedY / this.gridConfig.jewelHeight);
|
|
|
|
// Check if click is adjacent (up, down, left, right)
|
|
const isAdjacent =
|
|
(clickCol === selectedCol && Math.abs(clickRow - selectedRow) === 1) || // Up or down
|
|
(clickRow === selectedRow && Math.abs(clickCol - selectedCol) === 1); // Left or right
|
|
|
|
if (isAdjacent) {
|
|
this.tweens.add({
|
|
targets: this.selectedJewel,
|
|
scale: 1,
|
|
duration: 200
|
|
});
|
|
this.moveJewel(clickCol * this.gridConfig.jewelWidth, clickRow * this.gridConfig.jewelHeight);
|
|
}
|
|
}
|
|
|
|
// Modified swap function to disable clicks during execution
|
|
moveJewel(x ,y) {
|
|
if (this.isMovingUp === true) {
|
|
this.time.delayedCall(1000, ()=> {
|
|
this.moveJewel(x, y);
|
|
})
|
|
return;
|
|
}
|
|
// Set swapping flag to prevent new clicks from being processed
|
|
this.isSwapping = true;
|
|
this.sound.play('switch');
|
|
|
|
// Disable all jewel interactivity temporarily
|
|
this.jewels.children.iterate((jewel) => {
|
|
if (jewel) {
|
|
jewel.disableInteractive();
|
|
}
|
|
});
|
|
|
|
// Perform the swap animation here - currently empty
|
|
const fromX = this.selectedJewel.x;
|
|
const fromY = this.selectedJewel.y;
|
|
const toX = x;
|
|
const toY = y;
|
|
this.tweens.add({
|
|
targets: this.selectedJewel,
|
|
x: toX,
|
|
y: toY,
|
|
duration: 300,
|
|
onComplete: () => {
|
|
this.dropJewels();
|
|
this.time.delayedCall(300, () => {
|
|
this.checkMatches();
|
|
})
|
|
}
|
|
});
|
|
this.selectedJewel = null;
|
|
this.swapWithJewel = null;
|
|
|
|
|
|
// After swap completes, re-enable interactivity and reset flag
|
|
this.time.delayedCall(300, () => { // Adjust delay as needed for animation duration
|
|
this.jewels.children.iterate((jewel) => {
|
|
if (jewel) {
|
|
jewel.setInteractive();
|
|
}
|
|
});
|
|
|
|
this.isSwapping = false;
|
|
});
|
|
}
|
|
|
|
// New function to handle jewel clicks
|
|
handleJewelClick(x, y) {
|
|
// Find which jewel was clicked
|
|
const clickedJewel = this.getJewelAtWorldPosition(x, y);
|
|
|
|
//const isAdjacentButEmpty = this.isAdjacentButEmtpy(this.selectedJewel, x, y);
|
|
|
|
if (!clickedJewel || this.isSwapping) {
|
|
return;
|
|
}
|
|
|
|
// If no jewel is currently selected
|
|
if (!this.selectedJewel) {
|
|
this.selectJewel(clickedJewel);
|
|
return;
|
|
}
|
|
|
|
// If clicking on the already selected jewel, deselect it
|
|
if (clickedJewel === this.selectedJewel) {
|
|
this.deselectJewel();
|
|
return;
|
|
}
|
|
|
|
// Check if clicked jewel is adjacent to selected jewel
|
|
const isAdjacent = this.isAdjacent(this.selectedJewel, clickedJewel);
|
|
|
|
if (isAdjacent) {
|
|
// Mark for swap
|
|
this.selectJewelForSwap(clickedJewel);
|
|
} else {
|
|
// Deselect current and select new jewel
|
|
this.deselectJewel();
|
|
this.selectJewel(clickedJewel);
|
|
}
|
|
}
|
|
|
|
// Helper function to get jewel at world position
|
|
getJewelAtWorldPosition(x, y) {
|
|
let closestJewel = null;
|
|
let minDistance = Infinity;
|
|
|
|
this.jewels.children.iterate((jewel) => {
|
|
if (jewel) {
|
|
const distance = Phaser.Math.Distance.Between(jewel.x, jewel.y, x, y);
|
|
if (distance < minDistance && distance < 50) { // 50 is half of jewel width/height
|
|
minDistance = distance;
|
|
closestJewel = jewel;
|
|
}
|
|
}
|
|
});
|
|
|
|
return closestJewel;
|
|
}
|
|
|
|
// Helper function to check if two jewels are adjacent (horizontally or vertically)
|
|
isAdjacent(jewel1, jewel2) {
|
|
const col1 = Math.floor((jewel1.x - this.gridConfig.leftPadding) / this.gridConfig.jewelWidth);
|
|
const row1 = Math.floor(jewel1.y / this.gridConfig.jewelHeight);
|
|
|
|
const col2 = Math.floor((jewel2.x - this.gridConfig.leftPadding) / this.gridConfig.jewelWidth);
|
|
const row2 = Math.floor(jewel2.y / this.gridConfig.jewelHeight);
|
|
|
|
// Check if jewels are adjacent (same row and adjacent columns, or same column and adjacent rows)
|
|
return (row1 === row2 && Math.abs(col1 - col2) === 1) || (col1 === col2 && Math.abs(row1 - row2) === 1);
|
|
}
|
|
|
|
// Helper function to select a jewel for swap
|
|
selectJewelForSwap(jewel) {
|
|
this.swapWithJewel = jewel;
|
|
this.tweens.add({
|
|
targets: this.selectedJewel,
|
|
scale: 1,
|
|
duration: 200
|
|
});
|
|
this.swapJewel();
|
|
}
|
|
|
|
// Helper function to select a jewel for swap
|
|
selectJewel(jewel) {
|
|
this.selectedJewel = jewel;
|
|
this.tweens.add({
|
|
targets: jewel,
|
|
scale: 1.2,
|
|
duration: 200
|
|
});
|
|
}
|
|
|
|
// Helper function to deselect a jewel
|
|
deselectJewel() {
|
|
if (this.selectedJewel) {
|
|
this.tweens.add({
|
|
targets: this.selectedJewel,
|
|
scale: 1,
|
|
duration: 200
|
|
});
|
|
this.selectedJewel = null;
|
|
}
|
|
}
|
|
|
|
// Modified swap function to disable clicks during execution
|
|
swapJewel() {
|
|
if (this.isMovingUp === true) {
|
|
this.time.delayedCall(500, ()=> {
|
|
this.swapJewel();
|
|
})
|
|
return;
|
|
}
|
|
// Set swapping flag to prevent new clicks from being processed
|
|
this.isSwapping = true;
|
|
this.sound.play('switch');
|
|
|
|
// Disable all jewel interactivity temporarily
|
|
this.jewels.children.iterate((jewel) => {
|
|
if (jewel) {
|
|
jewel.disableInteractive();
|
|
}
|
|
});
|
|
|
|
// Perform the swap animation here - currently empty
|
|
const fromX = this.selectedJewel.x;
|
|
const fromY = this.selectedJewel.y;
|
|
const toX = this.swapWithJewel.x;
|
|
const toY = this.swapWithJewel.y;
|
|
this.tweens.add({
|
|
targets: this.selectedJewel,
|
|
x: toX,
|
|
y: toY,
|
|
duration: 300
|
|
});
|
|
this.tweens.add({
|
|
targets: this.swapWithJewel,
|
|
x: fromX,
|
|
y: fromY,
|
|
duration: 300,
|
|
onComplete: () => {
|
|
this.checkMatches();
|
|
}
|
|
});
|
|
this.selectedJewel = null;
|
|
this.swapWithJewel = null;
|
|
|
|
|
|
// After swap completes, re-enable interactivity and reset flag
|
|
this.time.delayedCall(300, () => { // Adjust delay as needed for animation duration
|
|
this.jewels.children.iterate((jewel) => {
|
|
if (jewel) {
|
|
jewel.setInteractive();
|
|
}
|
|
});
|
|
|
|
this.isSwapping = false;
|
|
});
|
|
}
|
|
|
|
reduceMatches(amount) {
|
|
this.matchesNeeded -= amount;
|
|
if (this.matchesNeeded <= 0) {
|
|
this.levelUp();
|
|
}
|
|
this.matchesText.setText(`Matches Needed: ${this.matchesNeeded}`);
|
|
}
|
|
|
|
// Function to check for matches and destroy them
|
|
checkMatches() {
|
|
this.checkWarning();
|
|
const matchedJewels = new Set();
|
|
|
|
// Check horizontal matches
|
|
for (let row = 1; row <= this.gridConfig.rows; row++) {
|
|
let count = 1;
|
|
let currentType = this.getJewelAtPosition(1, row);
|
|
|
|
for (let col = 2; col <= this.gridConfig.cols; col++) {
|
|
const jewelType = this.getJewelAtPosition(col, row);
|
|
|
|
if (jewelType === currentType && jewelType !== null) {
|
|
count++;
|
|
} else {
|
|
if (count >= 3) {
|
|
this.reduceMatches(1);
|
|
// Add all jewels in this match to the matched set
|
|
for (let i = col - count; i < col; i++) {
|
|
const key = `${i},${row}`;
|
|
matchedJewels.add(key);
|
|
}
|
|
}
|
|
count = 1;
|
|
currentType = jewelType;
|
|
}
|
|
}
|
|
|
|
// Check for match at the end of row
|
|
if (count >= 3) {
|
|
this.reduceMatches(1);
|
|
for (let i = this.gridConfig.cols - count + 1; i <= this.gridConfig.cols; i++) {
|
|
const key = `${i},${row}`;
|
|
matchedJewels.add(key);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check vertical matches
|
|
for (let col = 1; col <= this.gridConfig.cols; col++) {
|
|
let count = 1;
|
|
let currentType = this.getJewelAtPosition(col, 0);
|
|
|
|
for (let row = 1; row <= this.gridConfig.rows; row++) {
|
|
const jewelType = this.getJewelAtPosition(col, row);
|
|
|
|
if (jewelType === currentType && jewelType !== null) {
|
|
count++;
|
|
} else {
|
|
if (count >= 3) {
|
|
this.reduceMatches(1);
|
|
// Add all jewels in this match to the matched set
|
|
for (let i = row - count; i < row; i++) {
|
|
const key = `${col},${i}`;
|
|
matchedJewels.add(key);
|
|
}
|
|
}
|
|
count = 1;
|
|
currentType = jewelType;
|
|
}
|
|
}
|
|
|
|
// Check for match at the end of column
|
|
if (count >= 3) {
|
|
this.reduceMatches(1);
|
|
for (let i = this.gridConfig.rows - count + 1; i <= this.gridConfig.rows; i++) {
|
|
const key = `${col},${i}`;
|
|
matchedJewels.add(key);
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we found matches, destroy them and play video
|
|
if (matchedJewels.size > 0) {
|
|
this.playVideo(matchedJewels.size);
|
|
this.playAudio(matchedJewels.size);
|
|
const scoreMatches = matchedJewels.size / 3;
|
|
const scoreAdd = Math.ceil(scoreMatches ** 2) * 10;
|
|
this.score += scoreAdd;
|
|
this.scoreText.setText(`Score: ${this.score}`);
|
|
// ADD SCORE
|
|
this.destroyMatchedJewels(matchedJewels);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
playVideo(amount) {
|
|
if (this.isPlayingVideo === true) {
|
|
return;
|
|
}
|
|
|
|
this.isPlayingVideo = true;
|
|
let video = null;
|
|
|
|
if (amount >= 5) {
|
|
video = `${this.ally}-excited`;
|
|
} else if (amount >= 4) {
|
|
video = `${this.ally}-pleased`;
|
|
} else {
|
|
video = `${this.ally}-match`;
|
|
}
|
|
const showVideo = this.add.video(350, 610, video).setOrigin(0.5).setDepth(100);
|
|
showVideo.play(false);
|
|
this.time.delayedCall(5000, () => {
|
|
this.isPlayingVideo = false;
|
|
showVideo.destroy();
|
|
});
|
|
}
|
|
|
|
playAudio(amount) {
|
|
if (this.isPlayingAudio === true) {
|
|
return;
|
|
}
|
|
|
|
this.isPlayingAudio = true;
|
|
let audio = null;
|
|
|
|
if (amount >= 5) {
|
|
audio = `${this.ally}-c`;
|
|
} else if (amount >= 4) {
|
|
audio = `${this.ally}-b`;
|
|
} else {
|
|
const rand = Phaser.Math.Between(1, 3);
|
|
audio = `${this.ally}-a${rand}`;
|
|
}
|
|
this.sound.play(audio);
|
|
this.time.delayedCall(2000, () => {
|
|
this.isPlayingAudio = false;
|
|
});
|
|
}
|
|
|
|
// Function to destroy matched jewels
|
|
destroyMatchedJewels(matchedJewels) {
|
|
if (this.isDestroying) {
|
|
return;
|
|
}
|
|
|
|
this.isDestroying = true;
|
|
// Create an array of jewels to destroy
|
|
const jewelsToDestroy = [];
|
|
|
|
this.jewels.children.iterate((jewel) => {
|
|
if (jewel) {
|
|
const col = Math.floor((jewel.x - this.gridConfig.leftPadding) / this.gridConfig.jewelWidth);
|
|
const row = Math.floor(jewel.y / this.gridConfig.jewelHeight);
|
|
const key = `${col},${row}`;
|
|
|
|
if (matchedJewels.has(key)) {
|
|
jewelsToDestroy.push(jewel);
|
|
}
|
|
}
|
|
});
|
|
|
|
this.sound.play('clear');
|
|
|
|
// Animate destruction
|
|
jewelsToDestroy.forEach((jewel) => {
|
|
this.tweens.add({
|
|
targets: jewel,
|
|
scaleX: 0,
|
|
scaleY: 0,
|
|
alpha: 0,
|
|
duration: 200,
|
|
onComplete: () => {
|
|
jewel.destroy();
|
|
this.time.delayedCall(100, () => {
|
|
this.isDestroying = false;
|
|
this.dropJewels();
|
|
});
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
dropJewels() {
|
|
if (this.isDropping) {
|
|
return;
|
|
}
|
|
|
|
this.isDropping = true;
|
|
|
|
// Create a grid representation to track jewel positions
|
|
const grid = [];
|
|
for (let row = 1; row <= this.gridConfig.rows; row++) {
|
|
grid[row] = [];
|
|
for (let col = 1; col <= this.gridConfig.cols; col++) {
|
|
grid[row][col] = null;
|
|
}
|
|
}
|
|
|
|
// Populate the grid with jewel positions
|
|
this.jewels.children.iterate((jewel) => {
|
|
if (jewel) {
|
|
const col = Math.floor((jewel.x - this.gridConfig.leftPadding) / this.gridConfig.jewelWidth);
|
|
const row = Math.floor(jewel.y / this.gridConfig.jewelHeight);
|
|
|
|
// Ensure we're within grid bounds
|
|
if (col >= 1 && col <= this.gridConfig.cols && row >= 1 && row <= this.gridConfig.rows) {
|
|
grid[row][col] = jewel;
|
|
}
|
|
}
|
|
});
|
|
|
|
// Find jewels that need to drop and their target positions
|
|
const jewelsToDrop = [];
|
|
|
|
for (let col = 1; col <= this.gridConfig.cols; col++) {
|
|
let emptySpaces = 0;
|
|
|
|
// Process from bottom to top
|
|
for (let row = this.gridConfig.rows; row >= 1; row--) {
|
|
if (grid[row][col] === null) {
|
|
emptySpaces++;
|
|
} else if (emptySpaces > 0) {
|
|
// This jewel needs to drop
|
|
const jewel = grid[row][col];
|
|
const targetRow = row + emptySpaces;
|
|
|
|
jewelsToDrop.push({
|
|
jewel: jewel,
|
|
fromRow: row,
|
|
toRow: targetRow
|
|
});
|
|
|
|
// Update grid reference
|
|
grid[row][col] = null;
|
|
grid[targetRow][col] = jewel;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If no jewels need to drop, we're done
|
|
if (jewelsToDrop.length === 0) {
|
|
this.isDropping = false;
|
|
return;
|
|
}
|
|
|
|
// Animate the dropping
|
|
let droppedCount = 0;
|
|
|
|
jewelsToDrop.forEach((dropInfo) => {
|
|
const { jewel, fromRow, toRow } = dropInfo;
|
|
|
|
// Calculate target y position
|
|
const targetY = toRow * this.gridConfig.jewelHeight;
|
|
|
|
// Animate the jewel dropping
|
|
this.tweens.add({
|
|
targets: jewel,
|
|
y: targetY,
|
|
duration: 300,
|
|
ease: 'Linear',
|
|
onComplete: () => {
|
|
droppedCount++;
|
|
|
|
// When all jewels have dropped, check for new matches
|
|
if (droppedCount === jewelsToDrop.length) {
|
|
this.time.delayedCall(400, () => {
|
|
this.isDropping = false;
|
|
// Check for new matches after dropping
|
|
this.checkMatches();
|
|
});
|
|
}
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
// Function to move all jewels up one row
|
|
moveAllJewelsUp() {
|
|
this.isMovingUp = true;
|
|
// Check if any jewel is at row 1 (topmost) that would go to row 0
|
|
let gameOver = false;
|
|
|
|
this.jewels.children.iterate((jewel) => {
|
|
if (jewel) {
|
|
const col = Math.floor((jewel.x - this.gridConfig.leftPadding) / this.gridConfig.jewelWidth);
|
|
const row = Math.floor(jewel.y / this.gridConfig.jewelHeight);
|
|
|
|
// If jewel is at row 1 and would move up to row 0, game over
|
|
if (row === 1 && col >= 1 && col <= this.gridConfig.cols) {
|
|
gameOver = true;
|
|
}
|
|
}
|
|
});
|
|
|
|
// If game over, end the game
|
|
if (gameOver) {
|
|
this.gameOver();
|
|
return;
|
|
}
|
|
|
|
// Move all jewels up one row
|
|
this.jewels.children.iterate((jewel) => {
|
|
if (jewel) {
|
|
const col = Math.floor((jewel.x - this.gridConfig.leftPadding) / this.gridConfig.jewelWidth);
|
|
const row = Math.floor(jewel.y / this.gridConfig.jewelHeight);
|
|
|
|
// Only move jewels that are not at the top row
|
|
if (row > 1 && col >= 1 && col <= this.gridConfig.cols) {
|
|
const targetRow = row - 1;
|
|
const targetY = targetRow * this.gridConfig.jewelHeight;
|
|
|
|
// Animate the jewel moving up
|
|
this.tweens.add({
|
|
targets: jewel,
|
|
y: targetY,
|
|
duration: 300,
|
|
ease: 'Linear'
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
// Create new bottom row after moving all jewels up
|
|
this.time.delayedCall(300, () => {
|
|
this.isMovingUp = false;
|
|
this.createBottomRow();
|
|
this.checkWarning();
|
|
});
|
|
}
|
|
|
|
// Function to create a new row of jewels at the bottom
|
|
createBottomRow() {
|
|
const newRow = 8;
|
|
let jewelDelay = 0;
|
|
|
|
for (let col = 1; col <= this.gridConfig.cols; col++) {
|
|
let type = null;
|
|
let match = false;
|
|
|
|
do {
|
|
type = Phaser.Math.Between(0, this.numberOfJewels - 1);
|
|
match = false;
|
|
|
|
// Check horizontal matches in the new row
|
|
if (col >= 3) {
|
|
// Check if this would create a horizontal match of 3 or more
|
|
const leftType = this.getJewelAtPosition(col-1, newRow);
|
|
const leftLeftType = this.getJewelAtPosition(col-2, newRow);
|
|
|
|
if (leftType === type && leftLeftType === type) {
|
|
match = true;
|
|
}
|
|
}
|
|
|
|
// Check vertical matches in the columns above
|
|
if (newRow <= this.gridConfig.rows) {
|
|
const aboveType = this.getJewelAtPosition(col, newRow - 1);
|
|
|
|
if (aboveType === type) {
|
|
// Check if there's a match of 3 or more by looking at additional positions
|
|
let count = 1;
|
|
|
|
// Look up to see how many matching jewels we have in the column
|
|
for (let row = newRow - 2; row < this.gridConfig.rows; row++) {
|
|
const jewelType = this.getJewelAtPosition(col, row);
|
|
if (jewelType === type) {
|
|
count++;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If we have 3 or more in a vertical line, it's a match
|
|
if (count >= 3) {
|
|
match = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Also check for matches with jewels above the new row (if they exist)
|
|
if (newRow < this.gridConfig.rows && col <= this.gridConfig.cols) {
|
|
const jewelAbove = this.getJewelAtPosition(col, newRow - 1);
|
|
|
|
if (jewelAbove !== null) {
|
|
// Check horizontal match with jewels in the same row
|
|
let count = 0;
|
|
|
|
// Look left to see how many matching jewels we have in that row
|
|
for (let c = col - 1; c >= 1; c--) {
|
|
const jewelType = this.getJewelAtPosition(c, newRow);
|
|
if (jewelType === type) {
|
|
count++;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Look right to see how many matching jewels we have in that row
|
|
for (let c = col + 1; c <= this.gridConfig.cols; c++) {
|
|
const jewelType = this.getJewelAtPosition(c, newRow);
|
|
if (jewelType === type) {
|
|
count++;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If we have at least 2 matching jewels in the row and one more above,
|
|
// or any other match scenario that creates a 3-in-a-row
|
|
if (count >= 2 && jewelAbove === type) {
|
|
match = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
} while (match === true);
|
|
|
|
// Create the jewel at the correct position
|
|
this.createJewel(type, col, newRow);
|
|
}
|
|
}
|
|
|
|
refreshJewelSprites() {
|
|
// Iterate through all jewels and update their sprite frames
|
|
this.jewels.children.iterate((jewel) => {
|
|
if (jewel && jewel.jewelType !== undefined) {
|
|
// Update the jewel's frame to reflect the new spritePlus value
|
|
jewel.setFrame(jewel.jewelType + this.spritePlus);
|
|
}
|
|
});
|
|
}
|
|
|
|
refreshScene(originalAlly) {
|
|
// Stop the current music
|
|
this.bgMusic.stop();
|
|
|
|
// Play the new ally's music
|
|
this.bgMusic = this.sound.add(`${this.ally}-music`, { volume: 0.2 });
|
|
this.bgMusic.loop = true;
|
|
this.bgMusic.play();
|
|
|
|
this.time.delayedCall(4000, () => {
|
|
this.sound.play(`${this.ally}-intro`);
|
|
});
|
|
|
|
// Refresh the ally video with the new ally
|
|
if (this.allyVideo) {
|
|
console.log('update-vid ',this.ally);
|
|
// Destroy the existing video
|
|
this.allyVideo.destroy();
|
|
|
|
// Create a new video with the updated ally
|
|
this.allyVideo = this.add.video(350, 610, `${this.ally}-resting`).setOrigin(0.5);
|
|
this.allyVideo.play(true);
|
|
this.allyVideo.postFX.addGlow();
|
|
}
|
|
|
|
// Refresh the background video with the new ally
|
|
if (this.bgVideo) {
|
|
// Destroy the existing background video
|
|
this.bgVideo.destroy();
|
|
|
|
// Create a new background video with the updated ally
|
|
this.bgVideo = this.add.video(0, 0, `${this.ally}-background`).setOrigin(0);
|
|
this.bgVideo.scaleX = this.scale.width / 848;
|
|
this.bgVideo.scaleY = this.scale.height / 480;
|
|
this.bgVideo.play(true).setDepth(-1);
|
|
}
|
|
}
|
|
|
|
checkWarning() {
|
|
// Check if there are any jewels in row 1 (topmost row)
|
|
let hasJewelsInRow1 = false;
|
|
|
|
this.time.delayedCall(500, () => {
|
|
this.jewels.children.iterate((jewel) => {
|
|
if (jewel) {
|
|
const col = Math.floor((jewel.x - this.gridConfig.leftPadding) / this.gridConfig.jewelWidth);
|
|
const row = Math.floor(jewel.y / this.gridConfig.jewelHeight);
|
|
|
|
// If jewel is in row 1, set the flag to true
|
|
if (row === 1 && col >= 1 && col <= this.gridConfig.cols) {
|
|
hasJewelsInRow1 = true;
|
|
return false; // Stop iteration once we find one
|
|
}
|
|
}
|
|
});
|
|
|
|
if (this.rowOne === true && hasJewelsInRow1 === false) {
|
|
this.rowOne = false;
|
|
this.grid.setFillStyle(this.gridColor).setAlpha(0.5);
|
|
this.alarm.stop();
|
|
this.alarmFlash.remove();
|
|
this.alarmFlash = null;
|
|
} else if (this.rowOne === false && hasJewelsInRow1) {
|
|
console.log('in row one');
|
|
this.rowOne = true;
|
|
this.alarm = this.sound.add('alarm', { loop: true, volume: 1 });
|
|
this.alarm.play();
|
|
this.alarmFlash = this.tweens.add({
|
|
targets: this.grid,
|
|
fillColor: 0xFF0000,
|
|
duration: 3000,
|
|
alpha: .7,
|
|
ease: 'Linear',
|
|
yoyo: true,
|
|
repeat: -1
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
levelUp() {
|
|
this.level ++;
|
|
const newLevel = LEVEL_CONFIG[this.level];
|
|
this.numberOfJewels = newLevel.numberOfJewels;
|
|
this.matchesNeeded = newLevel.matchesNeeded;
|
|
this.moveInterval = newLevel.moveInterval;
|
|
this.gridColor = newLevel.gridColor;
|
|
if (newLevel.challenge !== undefined) {
|
|
this.clearAndResetBoard();
|
|
}
|
|
this.LevelText.setText(`Level: ${this.level}`);
|
|
this.sound.play('level-up');
|
|
this.sound.play('level-complete');
|
|
|
|
this.grid.setFillStyle(newLevel.gridColor).setAlpha(0.5);
|
|
|
|
if (this.ally !== newLevel.ally) {
|
|
this.sound.play(`${this.ally}-outro`);
|
|
this.time.delayedCall(1000, () => {
|
|
const originalAlly = this.ally;
|
|
this.ally = newLevel.ally;
|
|
this.refreshScene(originalAlly);
|
|
this.spritePlus = newLevel.spritePlus;
|
|
this.refreshJewelSprites();
|
|
});
|
|
}
|
|
|
|
// Create the New Level text
|
|
const newLevelText = this.add.text(1150, 250, `Level ${this.level}`, {
|
|
fontFamily: 'code, arial',
|
|
fontSize: '100px',
|
|
fill: '#ea00ffff',
|
|
padding: {
|
|
left: 10,
|
|
right: 10,
|
|
top: 5,
|
|
bottom: 5
|
|
}
|
|
}).setOrigin(0.5);
|
|
|
|
// Add cool eye-catching effect
|
|
this.tweens.add({
|
|
targets: newLevelText,
|
|
scale: { from: 0.5, to: 1.5 },
|
|
alpha: { from: 0, to: 1 },
|
|
duration: 1000,
|
|
ease: 'Back.easeOut',
|
|
yoyo: true
|
|
});
|
|
|
|
// Fade out and destroy after 500ms
|
|
this.time.delayedCall(1200, () => {
|
|
this.tweens.add({
|
|
targets: newLevelText,
|
|
angle: 360,
|
|
scale: 0,
|
|
alpha: 0,
|
|
duration: 500,
|
|
onComplete: () => {
|
|
newLevelText.destroy();
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
clearAndResetBoard() {
|
|
// Destroy all existing jewels
|
|
this.jewels.children.iterate((jewel) => {
|
|
if (jewel) {
|
|
jewel.destroy();
|
|
}
|
|
});
|
|
|
|
// Clear the jewels group
|
|
this.jewels.clear(true, true);
|
|
|
|
// Bring in 2 fresh rows
|
|
this.startRows = 2;
|
|
this.createStart();
|
|
}
|
|
|
|
// Game over function
|
|
gameOver() {
|
|
console.log('Game Over!');
|
|
this.gameStatus = false;
|
|
this.sound.play('game-over');
|
|
this.rowOne = false;
|
|
this.grid.setFillStyle(this.gridColor).setAlpha(0.5);
|
|
this.alarm.stop();
|
|
this.alarmFlash.remove();
|
|
this.alarmFlash = null;
|
|
|
|
// Make all jewels bounce off screen
|
|
this.jewels.children.iterate((jewel) => {
|
|
if (jewel) {
|
|
// Enable physics for the jewel if not already enabled
|
|
if (!jewel.body) {
|
|
this.physics.add.existing(jewel);
|
|
}
|
|
|
|
// Remove world bounds collision
|
|
jewel.body.setCollideWorldBounds(false);
|
|
|
|
// Set random velocity for bouncing effect
|
|
const angle = Phaser.Math.Between(0, 360);
|
|
const speed = Phaser.Math.Between(100, 300);
|
|
|
|
jewel.body.setVelocity(
|
|
Math.cos(angle) * speed,
|
|
Math.sin(angle) * speed
|
|
);
|
|
|
|
// Add some rotation for visual effect
|
|
jewel.body.angularVelocity = Phaser.Math.Between(-200, 200);
|
|
}
|
|
});
|
|
|
|
// Create the New Level text
|
|
const gameOverText = this.add.text(1150, 250, `Game Over`, {
|
|
fontFamily: 'code, arial',
|
|
fontSize: '100px',
|
|
fill: '#ea00ffff',
|
|
padding: {
|
|
left: 10,
|
|
right: 10,
|
|
top: 5,
|
|
bottom: 5
|
|
}
|
|
}).setOrigin(0.5);
|
|
|
|
// Add cool eye-catching effect
|
|
this.tweens.add({
|
|
targets: gameOverText,
|
|
scale: { from: 0.5, to: 1.5 },
|
|
alpha: { from: 0, to: 1 },
|
|
duration: 1000,
|
|
ease: 'Back.easeOut',
|
|
});
|
|
|
|
// Fade out and destroy after 2000ms
|
|
this.time.delayedCall(10200, () => {
|
|
this.tweens.add({
|
|
targets: gameOverText,
|
|
angle: 360,
|
|
scale: 0,
|
|
alpha: 0,
|
|
duration: 500,
|
|
onComplete: () => {
|
|
gameOverText.destroy();
|
|
this.time.delayedCall(3000, () => {
|
|
// Fade out camera
|
|
this.tweens.add({
|
|
targets: this.cameras.main,
|
|
opacity: 0,
|
|
duration: 1500,
|
|
ease: 'Linear',
|
|
onComplete: () => {
|
|
this.scene.restart();
|
|
}
|
|
});
|
|
});
|
|
}
|
|
});
|
|
});
|
|
}
|
|
} |