503 lines
15 KiB
JavaScript
503 lines
15 KiB
JavaScript
// Phaser is loaded from CDN, so we don't need to import it as a module
|
|
|
|
/**
|
|
* Main game configuration and initialization
|
|
*/
|
|
const config = {
|
|
type: Phaser.AUTO,
|
|
width: 1600,
|
|
height: 900,
|
|
parent: 'game-container',
|
|
scene: {
|
|
preload: preload,
|
|
create: create,
|
|
update: update
|
|
},
|
|
scale: {
|
|
mode: Phaser.Scale.FIT,
|
|
autoCenter: Phaser.Scale.CENTER_BOTH
|
|
},
|
|
physics: {
|
|
default: 'arcade',
|
|
arcade: {
|
|
gravity: { y: 0 },
|
|
debug: false
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Game instance
|
|
*/
|
|
let game;
|
|
|
|
/**
|
|
* Preload function - load all assets before the game starts
|
|
*/
|
|
function preload() {
|
|
console.log('Preloading game assets...');
|
|
|
|
// Create a simple loading bar
|
|
const progressBar = this.add.graphics();
|
|
const progressBox = this.add.graphics();
|
|
progressBox.fillStyle(0x242424, 0.8);
|
|
progressBox.fillRect(700, 430, 200, 50);
|
|
|
|
const width = this.cameras.main.width;
|
|
const height = this.cameras.main.height;
|
|
const loadingText = this.make.text({
|
|
x: width / 2,
|
|
y: height / 2 - 50,
|
|
text: 'Loading...',
|
|
style: {
|
|
font: '20px monospace',
|
|
fill: '#ffffff'
|
|
}
|
|
});
|
|
loadingText.setOrigin(0.5, 0.5);
|
|
|
|
const percentText = this.make.text({
|
|
x: width / 2,
|
|
y: height / 2 - 5,
|
|
text: '0%',
|
|
style: {
|
|
font: '18px monospace',
|
|
fill: '#ffffff'
|
|
}
|
|
});
|
|
percentText.setOrigin(0.5, 0.5);
|
|
|
|
const assetText = this.make.text({
|
|
x: width / 2,
|
|
y: height / 2 + 50,
|
|
text: '',
|
|
style: {
|
|
font: '18px monospace',
|
|
fill: '#ffffff'
|
|
}
|
|
});
|
|
assetText.setOrigin(0.5, 0.5);
|
|
|
|
// Update loading progress
|
|
this.load.on('progress', (value) => {
|
|
percentText.setText(parseInt(value * 100) + '%');
|
|
progressBar.clear();
|
|
progressBar.fillStyle(0xffffff, 1);
|
|
progressBar.fillRect(700, 430, 200 * value, 50);
|
|
});
|
|
|
|
this.load.on('fileprogress', (file) => {
|
|
assetText.setText('Loading asset: ' + file.key);
|
|
});
|
|
|
|
this.load.on('complete', () => {
|
|
progressBar.destroy();
|
|
progressBox.destroy();
|
|
loadingText.destroy();
|
|
percentText.destroy();
|
|
assetText.destroy();
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Create function - setup the game world and initial state
|
|
*/
|
|
function create() {
|
|
console.log('Creating game scene...');
|
|
|
|
// Set background color
|
|
this.cameras.main.setBackgroundColor('#2d5a8c');
|
|
|
|
// Add a simple title text
|
|
const title = this.add.text(800, 100, 'Deck Building Card Battle', {
|
|
fontSize: '32px',
|
|
fill: '#ffffff',
|
|
align: 'center'
|
|
});
|
|
title.setOrigin(0.5);
|
|
|
|
// Add player and opponent rows for cards in center of screen
|
|
// Player row at bottom (closest to player)
|
|
const playerRow = this.add.rectangle(800, 600, 1400, 120, 0x3a7c2d);
|
|
playerRow.setOrigin(0.5);
|
|
|
|
// Opponent row at top (closest to opponent)
|
|
const opponentRow = this.add.rectangle(800, 300, 1400, 120, 0x3a7c2d);
|
|
opponentRow.setOrigin(0.5);
|
|
|
|
// Add some instructions
|
|
const instructions = this.add.text(800, 850, 'Use mouse to play cards and build your deck', {
|
|
fontSize: '16px',
|
|
fill: '#ffffff',
|
|
align: 'center'
|
|
});
|
|
instructions.setOrigin(0.5);
|
|
|
|
// Initialize game state
|
|
this.gameState = {
|
|
playerDeck: [],
|
|
opponentDeck: [],
|
|
playerHand: [],
|
|
opponentHand: [],
|
|
playerField: [],
|
|
opponentField: [],
|
|
selectedCard: null,
|
|
turnPhase: 'draw',
|
|
currentPlayer: 'player'
|
|
};
|
|
|
|
// Assign the first deck (deck1) to the player
|
|
this.gameState.playerDeck = [...window.deck1];
|
|
|
|
// Shuffle the deck using Fisher-Yates algorithm (using Phaser's scope)
|
|
shuffleDeck(this.gameState.playerDeck);
|
|
|
|
// Deal 3 cards to player's hand
|
|
this.gameState.playerHand = this.gameState.playerDeck.splice(0, 3);
|
|
|
|
// Display the player's hand at the bottom of the screen
|
|
displayPlayerHand.call(this);
|
|
|
|
console.log('Game scene created successfully');
|
|
}
|
|
|
|
/**
|
|
* Shuffle an array using Fisher-Yates algorithm
|
|
*/
|
|
function shuffleDeck(deck) {
|
|
for (let i = deck.length - 1; i > 0; i--) {
|
|
const j = Math.floor(Math.random() * (i + 1));
|
|
[deck[i], deck[j]] = [deck[j], deck[i]];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Display the player's hand at the bottom of the screen
|
|
*/
|
|
function displayPlayerHand() {
|
|
const cardWidth = 100;
|
|
const cardHeight = 140;
|
|
const spacing = 20;
|
|
|
|
// Calculate total width needed for all cards
|
|
const totalWidth = (this.gameState.playerHand.length * cardWidth) + ((this.gameState.playerHand.length - 1) * spacing);
|
|
|
|
// Starting x position to center the hand
|
|
const startX = (1600 - totalWidth) / 2;
|
|
|
|
// Position cards at the bottom of the screen
|
|
const yPosition = 850;
|
|
|
|
this.gameState.playerHand.forEach((card, index) => {
|
|
const xPosition = startX + (index * (cardWidth + spacing));
|
|
|
|
// Create a simple card placeholder with number
|
|
const cardSprite = this.add.rectangle(xPosition, yPosition, cardWidth, cardHeight, 0x8B4513);
|
|
cardSprite.setOrigin(0.5);
|
|
|
|
// Add the number to the card
|
|
const numberText = this.add.text(xPosition, yPosition - 20, `${card.number}`, {
|
|
fontSize: '24px',
|
|
fill: '#ffffff'
|
|
});
|
|
numberText.setOrigin(0.5);
|
|
|
|
// Display attack in upper left corner
|
|
const attackText = this.add.text(xPosition - 40, yPosition - 50, `A:${card.attack}`, {
|
|
fontSize: '16px',
|
|
fill: '#ffffff'
|
|
});
|
|
attackText.setOrigin(0.5);
|
|
|
|
// Display shield in lower left corner
|
|
const shieldText = this.add.text(xPosition - 40, yPosition + 50, `S:${card.shield}`, {
|
|
fontSize: '16px',
|
|
fill: '#ffffff'
|
|
});
|
|
shieldText.setOrigin(0.5);
|
|
|
|
// Display health in lower right corner
|
|
const healthText = this.add.text(xPosition + 40, yPosition + 50, `H:${card.health}`, {
|
|
fontSize: '16px',
|
|
fill: '#ffffff'
|
|
});
|
|
healthText.setOrigin(0.5);
|
|
|
|
// Store references to stat texts with sprite reference
|
|
card.attackText = attackText;
|
|
card.shieldText = shieldText;
|
|
card.healthText = healthText;
|
|
|
|
// Store card data with sprite reference
|
|
card.sprite = cardSprite;
|
|
card.text = numberText;
|
|
|
|
// Add hover events for the card
|
|
addInteractiveCard.call(this, card, xPosition, yPosition, cardWidth, cardHeight);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Make a single card interactive with hover effects
|
|
*/
|
|
function addInteractiveCard(card, xPosition, yPosition, width, height) {
|
|
const originalY = yPosition;
|
|
|
|
// Create a transparent hit area for the card that matches its dimensions
|
|
const hitArea = this.add.rectangle(xPosition, yPosition + 70, width - 10, height - 10);
|
|
hitArea.setOrigin(0.5);
|
|
hitArea.setInteractive();
|
|
|
|
// Add event listeners for hover effects
|
|
hitArea.on('pointerover', () => {
|
|
// Only allow hover effects if the card is in hand (not already on field)
|
|
const isInHand = this.gameState.playerHand.includes(card);
|
|
if (isInHand && !card.isLocked) {
|
|
// Raise the card up to full view (move it up by 30 pixels)
|
|
if (card.sprite) {
|
|
card.sprite.setY(originalY - 30);
|
|
}
|
|
if (card.text) {
|
|
card.text.setY(originalY - 50);
|
|
}
|
|
// Move stat texts with the card
|
|
if (card.attackText) {
|
|
card.attackText.setY(originalY - 80);
|
|
}
|
|
if (card.shieldText) {
|
|
card.shieldText.setY(originalY + 20);
|
|
}
|
|
if (card.healthText) {
|
|
card.healthText.setY(originalY + 20);
|
|
}
|
|
}
|
|
});
|
|
|
|
hitArea.on('pointerout', () => {
|
|
// Only allow hover effects if the card is in hand (not already on field)
|
|
const isInHand = this.gameState.playerHand.includes(card);
|
|
if (isInHand && !card.isLocked) {
|
|
// Return the card to its original position
|
|
if (card.sprite) {
|
|
card.sprite.setY(originalY);
|
|
}
|
|
if (card.text) {
|
|
card.text.setY(originalY - 20);
|
|
}
|
|
// Return stat texts to original positions
|
|
if (card.attackText) {
|
|
card.attackText.setY(originalY - 50);
|
|
}
|
|
if (card.shieldText) {
|
|
card.shieldText.setY(originalY + 50);
|
|
}
|
|
if (card.healthText) {
|
|
card.healthText.setY(originalY + 50);
|
|
}
|
|
}
|
|
});
|
|
|
|
// Add click event to lock the card in place
|
|
hitArea.on('pointerdown', () => {
|
|
// Only allow clicking if the card is in hand (not already on field)
|
|
const isInHand = this.gameState.playerHand.includes(card);
|
|
if (isInHand) {
|
|
// Lock the card in its current position
|
|
card.isLocked = true;
|
|
|
|
// Remove hover effects while locked
|
|
if (card.sprite) {
|
|
card.sprite.setY(originalY - 30); // Keep it raised up when locked
|
|
}
|
|
if (card.text) {
|
|
card.text.setY(originalY - 50);
|
|
}
|
|
|
|
// Create Play and Cancel buttons above the card
|
|
createCardButtons.call(this, card, xPosition, originalY - 30);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Update function - called every frame
|
|
*/
|
|
function update() {
|
|
// Game logic updates go here
|
|
}
|
|
|
|
/**
|
|
* Create Play and Cancel buttons for a locked card
|
|
*/
|
|
function createCardButtons(card, xPosition, yPosition) {
|
|
const buttonWidth = 60;
|
|
const buttonHeight = 30;
|
|
const spacing = 10;
|
|
|
|
// Position the buttons above the card (at yPosition - 40)
|
|
const playButtonX = xPosition - buttonWidth - spacing / 2; // Left of card
|
|
const cancelButtonX = xPosition + buttonWidth + spacing / 2; // Right of card
|
|
const buttonY = yPosition - 40;
|
|
|
|
// Create Play button
|
|
const playButton = this.add.rectangle(playButtonX, buttonY, buttonWidth, buttonHeight, 0x00ff00); // Green color
|
|
playButton.setOrigin(0.5);
|
|
playButton.setInteractive();
|
|
|
|
// Add text to the Play button
|
|
const playText = this.add.text(playButtonX, buttonY, 'Play', {
|
|
fontSize: '14px',
|
|
fill: '#ffffff'
|
|
});
|
|
playText.setOrigin(0.5);
|
|
|
|
// Create Cancel button
|
|
const cancelButton = this.add.rectangle(cancelButtonX, buttonY, buttonWidth, buttonHeight, 0xff0000); // Red color
|
|
cancelButton.setOrigin(0.5);
|
|
cancelButton.setInteractive();
|
|
|
|
// Add text to the Cancel button
|
|
const cancelText = this.add.text(cancelButtonX, buttonY, 'Cancel', {
|
|
fontSize: '14px',
|
|
fill: '#ffffff'
|
|
});
|
|
cancelText.setOrigin(0.5);
|
|
|
|
// Store references to buttons in the card object
|
|
card.playButton = playButton;
|
|
card.cancelButton = cancelButton;
|
|
card.playButtonText = playText;
|
|
card.cancelButtonText = cancelText;
|
|
|
|
// Add click events for the buttons
|
|
playButton.on('pointerdown', () => {
|
|
// Move the card to the player's field (center of screen)
|
|
moveCardToField.call(this, card);
|
|
|
|
// Remove buttons and unlock the card
|
|
removeCardButtons(card);
|
|
});
|
|
|
|
cancelButton.on('pointerdown', () => {
|
|
// Return card to original position in hand
|
|
returnCardToHand.call(this, card);
|
|
|
|
// Remove buttons and unlock the card
|
|
removeCardButtons(card);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Move a card to the player's field (player row on far right)
|
|
*/
|
|
function moveCardToField(card) {
|
|
// Remove it from hand and add it to field
|
|
const index = this.gameState.playerHand.indexOf(card);
|
|
if (index > -1) {
|
|
this.gameState.playerHand.splice(index, 1);
|
|
this.gameState.playerField.push(card);
|
|
}
|
|
|
|
// Calculate starting position for the animation (right side of screen)
|
|
const startX = 1600 + 50; // Start just off-screen to the right
|
|
const startY = 600; // Player field y-position (bottom row)
|
|
|
|
// Calculate target x position based on how many cards are already in the field
|
|
// Cards stack from right to left with each card being 120px wide + 20px spacing
|
|
const cardWidth = 100;
|
|
const spacing = 20;
|
|
const currentFieldCount = this.gameState.playerField.length - 1; // -1 because we just added the new card
|
|
const targetX = 1600 - (cardWidth + spacing) * currentFieldCount - (cardWidth / 2); // Position from right edge
|
|
|
|
// Animate the card moving from right to left towards its final position in player field
|
|
this.tweens.add({
|
|
targets: [card.sprite, card.text, card.attackText, card.shieldText, card.healthText],
|
|
x: targetX,
|
|
y: startY,
|
|
duration: 500, // 500ms animation time
|
|
ease: 'Power2'
|
|
});
|
|
|
|
// Re-display the player's hand to update positions of remaining cards after animation completes
|
|
setTimeout(() => {
|
|
// Clear existing hand display first by removing all sprites and texts
|
|
this.gameState.playerHand.forEach(handCard => {
|
|
if (handCard.sprite) {
|
|
handCard.sprite.destroy();
|
|
}
|
|
if (handCard.text) {
|
|
handCard.text.destroy();
|
|
}
|
|
});
|
|
|
|
// Redisplay the updated player's hand at the bottom of the screen
|
|
displayPlayerHand.call(this);
|
|
}, 500); // Wait for animation to complete before updating hand
|
|
}
|
|
|
|
/**
|
|
* Return a locked card to its original position in the player's hand
|
|
*/
|
|
function returnCardToHand(card) {
|
|
// Reset the locked state
|
|
card.isLocked = false;
|
|
|
|
// Remove buttons and text references from the card object
|
|
if (card.playButton) {
|
|
card.playButton.destroy();
|
|
card.playButtonText.destroy();
|
|
}
|
|
if (card.cancelButton) {
|
|
card.cancelButton.destroy();
|
|
card.cancelButtonText.destroy();
|
|
}
|
|
|
|
// Reset positions to original hand position
|
|
const yPosition = 850; // Original hand position at bottom of screen
|
|
const originalY = yPosition;
|
|
|
|
if (card.sprite) {
|
|
card.sprite.setY(originalY);
|
|
}
|
|
if (card.text) {
|
|
card.text.setY(originalY - 20);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Remove buttons from a locked card
|
|
*/
|
|
function removeCardButtons(card) {
|
|
// Reset the locked state
|
|
card.isLocked = false;
|
|
|
|
// Destroy button elements if they exist
|
|
if (card.playButton) {
|
|
card.playButton.destroy();
|
|
card.playButtonText.destroy();
|
|
delete card.playButton;
|
|
delete card.playButtonText;
|
|
}
|
|
if (card.cancelButton) {
|
|
card.cancelButton.destroy();
|
|
card.cancelButtonText.destroy();
|
|
delete card.cancelButton;
|
|
delete card.cancelButtonText;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Initialize the game when the page loads
|
|
*/
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
console.log('Initializing Phaser 3 card battle game...');
|
|
|
|
// Create the game instance
|
|
game = new Phaser.Game(config);
|
|
|
|
console.log('Phaser 3 card battle game initialized');
|
|
});
|
|
|
|
// Export for potential module usage (not needed when using CDN)
|
|
// export { config, game };
|