the-jewel-weaver/src/GameScene.js

1211 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 = 5;
this.level = 1;
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'];
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;
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();
}
});
});
}
// 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();
}
});
});
}
});
});
}
}