mirror of
https://github.com/tiennm99/sokoban.git
synced 2026-10-15 06:16:19 +00:00
feat: init
This commit is contained in:
1 parent
a878bdc6b5
commit
361f558e19
18 files changed
+2175
No files matched your search
+24
@@ -0,0 +1,24 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
.
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/png" href="/favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="stylesheet" href="/style.css">
|
||||
<title>Phaser - Template</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app">
|
||||
<div id="game-container"></div>
|
||||
</div>
|
||||
<script type="module" src="src/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,36 @@
|
||||
import * as fs from 'fs';
|
||||
import * as https from 'https';
|
||||
|
||||
const main = async () => {
|
||||
const args = process.argv.slice(2);
|
||||
const packageData = JSON.parse(fs.readFileSync('./package.json', 'utf8'));
|
||||
const event = args[0] || 'unknown';
|
||||
const phaserVersion = packageData.dependencies.phaser;
|
||||
|
||||
const options = {
|
||||
hostname: 'gryzor.co',
|
||||
port: 443,
|
||||
path: `/v/${event}/${phaserVersion}/${packageData.name}`,
|
||||
method: 'GET'
|
||||
};
|
||||
|
||||
try {
|
||||
const req = https.request(options, (res) => {
|
||||
res.on('data', () => {});
|
||||
res.on('end', () => {
|
||||
process.exit(0);
|
||||
});
|
||||
});
|
||||
|
||||
req.on('error', (error) => {
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
req.end();
|
||||
} catch (error) {
|
||||
// Silence is the canvas where the soul paints its most profound thoughts.
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
Generated
+1159
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "template-vite",
|
||||
"description": "A Phaser 3 template using Vite.",
|
||||
"version": "1.4.0",
|
||||
"type": "module",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/phaserjs/template-vite.git"
|
||||
},
|
||||
"author": "Phaser Studio <support@phaser.io> (https://phaser.io/)",
|
||||
"license": "MIT",
|
||||
"licenseUrl": "http://www.opensource.org/licenses/mit-license.php",
|
||||
"bugs": {
|
||||
"url": "https://github.com/phaserjs/template-vite/issues"
|
||||
},
|
||||
"homepage": "https://github.com/phaserjs/template-vite#readme",
|
||||
"scripts": {
|
||||
"dev": "node log.js dev & vite --config vite/config.dev.mjs",
|
||||
"build": "node log.js build & vite build --config vite/config.prod.mjs",
|
||||
"dev-nolog": "vite --config vite/config.dev.mjs",
|
||||
"build-nolog": "vite build --config vite/config.prod.mjs"
|
||||
},
|
||||
"devDependencies": {
|
||||
"terser": "^5.39.0",
|
||||
"vite": "^6.3.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"phaser": "^3.88.2"
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 295 KiB |
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"width": 8,
|
||||
"height": 8,
|
||||
"tiles": [
|
||||
["W", "W", "W", "W", "W", "W", "W", "W"],
|
||||
["W", ".", ".", ".", ".", ".", ".", "W"],
|
||||
["W", ".", ".", ".", ".", ".", ".", "W"],
|
||||
["W", ".", "W", "W", ".", ".", ".", "W"],
|
||||
["W", ".", ".", "B", ".", "T", ".", "W"],
|
||||
["W", ".", "P", ".", ".", ".", ".", "W"],
|
||||
["W", ".", ".", ".", ".", ".", ".", "W"],
|
||||
["W", "W", "W", "W", "W", "W", "W", "W"]
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"width": 8,
|
||||
"height": 8,
|
||||
"tiles": [
|
||||
["W", "W", "W", "W", "W", "W", "W", "W"],
|
||||
["W", ".", ".", ".", ".", ".", ".", "W"],
|
||||
["W", ".", "P", ".", ".", ".", ".", "W"],
|
||||
["W", ".", "B", ".", "W", ".", ".", "W"],
|
||||
["W", ".", "B", ".", "T", ".", ".", "W"],
|
||||
["W", ".", ".", ".", "T", ".", ".", "W"],
|
||||
["W", ".", ".", ".", ".", ".", ".", "W"],
|
||||
["W", "W", "W", "W", "W", "W", "W", "W"]
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"width": 10,
|
||||
"height": 10,
|
||||
"tiles": [
|
||||
["W", "W", "W", "W", "W", "W", "W", "W", "W", "W"],
|
||||
["W", ".", ".", ".", ".", ".", ".", ".", ".", "W"],
|
||||
["W", ".", ".", ".", "W", "W", ".", ".", ".", "W"],
|
||||
["W", ".", "P", ".", ".", ".", ".", ".", ".", "W"],
|
||||
["W", ".", ".", "B", ".", ".", "W", ".", ".", "W"],
|
||||
["W", ".", "W", ".", "B", ".", ".", ".", ".", "W"],
|
||||
["W", ".", ".", ".", ".", "B", ".", ".", ".", "W"],
|
||||
["W", ".", ".", "T", "T", "T", ".", ".", ".", "W"],
|
||||
["W", ".", ".", ".", ".", ".", ".", ".", ".", "W"],
|
||||
["W", "W", "W", "W", "W", "W", "W", "W", "W", "W"]
|
||||
]
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 24 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 354 B |
@@ -0,0 +1,15 @@
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
color: rgba(255, 255, 255, 0.87);
|
||||
background-color: #000000;
|
||||
}
|
||||
|
||||
#app {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
import {Game, AUTO} from 'phaser';
|
||||
import MenuScene from './scenes/MenuScene';
|
||||
import LevelScene from './scenes/LevelScene';
|
||||
import MainScene from './scenes/MainScene';
|
||||
|
||||
// Game configuration
|
||||
const config = {
|
||||
type: AUTO,
|
||||
width: 800,
|
||||
height: 600,
|
||||
parent: 'game-container',
|
||||
backgroundColor: '#f9f9f9',
|
||||
scene: [MenuScene, LevelScene, MainScene],
|
||||
physics: {
|
||||
default: 'arcade',
|
||||
arcade: {
|
||||
gravity: {y: 0},
|
||||
debug: false
|
||||
}
|
||||
},
|
||||
pixelArt: true
|
||||
};
|
||||
|
||||
// Create the game instance
|
||||
const game = new Game(config);
|
||||
|
||||
// Global game variables
|
||||
game.globals = {
|
||||
currentLevel: 0,
|
||||
totalLevels: 3, // We'll create 3 levels for now
|
||||
levelCompleted: [false, false, false]
|
||||
};
|
||||
|
||||
export default game;
|
||||
@@ -0,0 +1,113 @@
|
||||
import Phaser from 'phaser';
|
||||
import game from '../main.js';
|
||||
|
||||
class LevelScene extends Phaser.Scene {
|
||||
constructor() {
|
||||
super({ key: 'LevelScene' });
|
||||
}
|
||||
|
||||
preload() {
|
||||
// Load any assets needed for the level selection
|
||||
}
|
||||
|
||||
create() {
|
||||
// Add background
|
||||
this.cameras.main.setBackgroundColor('#f0f0f0');
|
||||
|
||||
// Add title text
|
||||
const title = this.add.text(
|
||||
this.cameras.main.centerX,
|
||||
100,
|
||||
'SELECT LEVEL',
|
||||
{
|
||||
fontSize: '48px',
|
||||
fill: '#000',
|
||||
fontStyle: 'bold'
|
||||
}
|
||||
);
|
||||
title.setOrigin(0.5);
|
||||
|
||||
// Add back button
|
||||
const backButton = this.add.text(
|
||||
100,
|
||||
50,
|
||||
'< BACK',
|
||||
{
|
||||
fontSize: '24px',
|
||||
fill: '#000',
|
||||
backgroundColor: '#f0f0f0',
|
||||
padding: { left: 15, right: 15, top: 5, bottom: 5 }
|
||||
}
|
||||
);
|
||||
backButton.setInteractive({ useHandCursor: true });
|
||||
|
||||
// Add hover effect
|
||||
backButton.on('pointerover', () => {
|
||||
backButton.setStyle({ fill: '#555' });
|
||||
});
|
||||
|
||||
backButton.on('pointerout', () => {
|
||||
backButton.setStyle({ fill: '#000' });
|
||||
});
|
||||
|
||||
// Add click event
|
||||
backButton.on('pointerdown', () => {
|
||||
this.scene.start('MenuScene');
|
||||
});
|
||||
|
||||
// Create level buttons
|
||||
this.createLevelButtons();
|
||||
}
|
||||
|
||||
createLevelButtons() {
|
||||
const buttonWidth = 150;
|
||||
const buttonHeight = 100;
|
||||
const padding = 20;
|
||||
const startX = this.cameras.main.centerX - buttonWidth - padding / 2;
|
||||
const startY = this.cameras.main.centerY - buttonHeight / 2;
|
||||
|
||||
for (let i = 0; i < game.globals.totalLevels; i++) {
|
||||
const x = startX + (i % 3) * (buttonWidth + padding);
|
||||
const y = startY + Math.floor(i / 3) * (buttonHeight + padding);
|
||||
|
||||
// Create button background
|
||||
const buttonBg = this.add.rectangle(
|
||||
x + buttonWidth / 2,
|
||||
y + buttonHeight / 2,
|
||||
buttonWidth,
|
||||
buttonHeight,
|
||||
game.globals.levelCompleted[i] ? 0x4CAF50 : 0x3498db
|
||||
);
|
||||
buttonBg.setInteractive({ useHandCursor: true });
|
||||
|
||||
// Add level number
|
||||
const levelText = this.add.text(
|
||||
x + buttonWidth / 2,
|
||||
y + buttonHeight / 2,
|
||||
`LEVEL ${i + 1}`,
|
||||
{
|
||||
fontSize: '24px',
|
||||
fill: '#fff',
|
||||
align: 'center'
|
||||
}
|
||||
);
|
||||
levelText.setOrigin(0.5);
|
||||
|
||||
// Add click event
|
||||
buttonBg.on('pointerdown', () => {
|
||||
game.globals.currentLevel = i;
|
||||
this.scene.start('MainScene');
|
||||
});
|
||||
|
||||
// Add hover effect
|
||||
buttonBg.on('pointerover', () => {
|
||||
buttonBg.setFillStyle(game.globals.levelCompleted[i] ? 0x45a049 : 0x2980b9);
|
||||
});
|
||||
|
||||
buttonBg.on('pointerout', () => {
|
||||
buttonBg.setFillStyle(game.globals.levelCompleted[i] ? 0x4CAF50 : 0x3498db);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
export default LevelScene;
|
||||
@@ -0,0 +1,569 @@
|
||||
import Phaser from 'phaser';
|
||||
import game from '../main.js';
|
||||
|
||||
class MainScene extends Phaser.Scene {
|
||||
constructor() {
|
||||
super({ key: 'MainScene' });
|
||||
|
||||
// Game objects
|
||||
this.player = null;
|
||||
this.walls = null;
|
||||
this.boxes = null;
|
||||
this.targets = null;
|
||||
|
||||
// Controls
|
||||
this.cursors = null;
|
||||
this.keyState = {
|
||||
up: false,
|
||||
down: false,
|
||||
left: false,
|
||||
right: false
|
||||
};
|
||||
|
||||
// Level data
|
||||
this.levelData = null;
|
||||
|
||||
// Game state
|
||||
this.isMoving = false;
|
||||
this.moveDirection = { x: 0, y: 0 };
|
||||
|
||||
// Tile size
|
||||
this.tileSize = 64;
|
||||
}
|
||||
|
||||
preload() {
|
||||
// Load level data
|
||||
this.load.json('level1', 'public/assets/levels/level1.json');
|
||||
this.load.json('level2', 'public/assets/levels/level2.json');
|
||||
this.load.json('level3', 'public/assets/levels/level3.json');
|
||||
}
|
||||
|
||||
create() {
|
||||
// Set up controls
|
||||
this.cursors = this.input.keyboard.createCursorKeys();
|
||||
|
||||
// Reset key state
|
||||
this.keyState = {
|
||||
up: false,
|
||||
down: false,
|
||||
left: false,
|
||||
right: false
|
||||
};
|
||||
|
||||
// Set up key release listeners
|
||||
this.input.keyboard.on('keyup', (event) => {
|
||||
switch (event.keyCode) {
|
||||
case 37: // Left
|
||||
this.keyState.left = false;
|
||||
break;
|
||||
case 38: // Up
|
||||
this.keyState.up = false;
|
||||
break;
|
||||
case 39: // Right
|
||||
this.keyState.right = false;
|
||||
break;
|
||||
case 40: // Down
|
||||
this.keyState.down = false;
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
// Load level data
|
||||
const levelNumber = game.globals.currentLevel + 1;
|
||||
this.levelData = this.cache.json.get(`level${levelNumber}`);
|
||||
|
||||
if (!this.levelData || !this.levelData.tiles) {
|
||||
console.error(`Failed to load level data for level ${levelNumber}`);
|
||||
this.showErrorMessage(`Failed to load level ${levelNumber}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create game objects
|
||||
this.createLevel();
|
||||
|
||||
// Add UI elements
|
||||
this.createUI();
|
||||
} catch (error) {
|
||||
console.error('Error creating level:', error);
|
||||
this.showErrorMessage('Error loading level');
|
||||
}
|
||||
}
|
||||
|
||||
showErrorMessage(message) {
|
||||
// Add error message
|
||||
const errorText = this.add.text(
|
||||
this.cameras.main.centerX,
|
||||
this.cameras.main.centerY,
|
||||
message,
|
||||
{
|
||||
fontSize: '32px',
|
||||
fill: '#FF0000',
|
||||
backgroundColor: '#000000',
|
||||
padding: { left: 20, right: 20, top: 10, bottom: 10 }
|
||||
}
|
||||
);
|
||||
errorText.setOrigin(0.5);
|
||||
|
||||
// Add back button
|
||||
const backButton = this.add.text(
|
||||
this.cameras.main.centerX,
|
||||
this.cameras.main.centerY + 80,
|
||||
'BACK TO MENU',
|
||||
{
|
||||
fontSize: '24px',
|
||||
fill: '#FFFFFF',
|
||||
backgroundColor: '#333333',
|
||||
padding: { left: 15, right: 15, top: 5, bottom: 5 }
|
||||
}
|
||||
);
|
||||
backButton.setOrigin(0.5);
|
||||
backButton.setInteractive({ useHandCursor: true });
|
||||
|
||||
// Add hover effect
|
||||
backButton.on('pointerover', () => {
|
||||
backButton.setStyle({ backgroundColor: '#555555' });
|
||||
});
|
||||
|
||||
backButton.on('pointerout', () => {
|
||||
backButton.setStyle({ backgroundColor: '#333333' });
|
||||
});
|
||||
|
||||
// Add click event
|
||||
backButton.on('pointerdown', () => {
|
||||
this.scene.start('MenuScene');
|
||||
});
|
||||
}
|
||||
|
||||
update() {
|
||||
// Skip if player is already moving or player is not defined
|
||||
if (this.isMoving || !this.player) return;
|
||||
|
||||
// Handle player movement with key state tracking
|
||||
if (this.cursors.left.isDown && !this.keyState.left) {
|
||||
this.keyState.left = true;
|
||||
this.movePlayer(-1, 0);
|
||||
} else if (this.cursors.right.isDown && !this.keyState.right) {
|
||||
this.keyState.right = true;
|
||||
this.movePlayer(1, 0);
|
||||
} else if (this.cursors.up.isDown && !this.keyState.up) {
|
||||
this.keyState.up = true;
|
||||
this.movePlayer(0, -1);
|
||||
} else if (this.cursors.down.isDown && !this.keyState.down) {
|
||||
this.keyState.down = true;
|
||||
this.movePlayer(0, 1);
|
||||
}
|
||||
|
||||
// Check win condition
|
||||
this.checkWinCondition();
|
||||
}
|
||||
|
||||
createLevel() {
|
||||
// Create groups for game objects
|
||||
this.walls = this.physics.add.staticGroup();
|
||||
this.boxes = this.physics.add.group();
|
||||
this.targets = this.physics.add.staticGroup();
|
||||
|
||||
// Calculate level offset to center it
|
||||
const levelWidth = this.levelData.width * this.tileSize;
|
||||
const levelHeight = this.levelData.height * this.tileSize;
|
||||
const offsetX = (this.cameras.main.width - levelWidth) / 2;
|
||||
const offsetY = (this.cameras.main.height - levelHeight) / 2;
|
||||
|
||||
// Create floor tiles (light gray background)
|
||||
this.add.rectangle(
|
||||
offsetX + levelWidth / 2,
|
||||
offsetY + levelHeight / 2,
|
||||
levelWidth,
|
||||
levelHeight,
|
||||
0xeeeeee
|
||||
);
|
||||
|
||||
// Create grid lines
|
||||
const graphics = this.add.graphics();
|
||||
graphics.lineStyle(1, 0xcccccc, 0.5);
|
||||
|
||||
// Draw horizontal grid lines
|
||||
for (let y = 0; y <= this.levelData.height; y++) {
|
||||
graphics.moveTo(offsetX, offsetY + y * this.tileSize);
|
||||
graphics.lineTo(offsetX + levelWidth, offsetY + y * this.tileSize);
|
||||
}
|
||||
|
||||
// Draw vertical grid lines
|
||||
for (let x = 0; x <= this.levelData.width; x++) {
|
||||
graphics.moveTo(offsetX + x * this.tileSize, offsetY);
|
||||
graphics.lineTo(offsetX + x * this.tileSize, offsetY + levelHeight);
|
||||
}
|
||||
|
||||
graphics.strokePath();
|
||||
|
||||
// Create level objects based on level data
|
||||
for (let y = 0; y < this.levelData.height; y++) {
|
||||
for (let x = 0; x < this.levelData.width; x++) {
|
||||
const tileX = offsetX + x * this.tileSize + this.tileSize / 2;
|
||||
const tileY = offsetY + y * this.tileSize + this.tileSize / 2;
|
||||
|
||||
const tile = this.levelData.tiles[y][x];
|
||||
|
||||
switch (tile) {
|
||||
case 'W': // Wall (dark gray rectangle)
|
||||
const wall = this.add.rectangle(tileX, tileY, this.tileSize - 4, this.tileSize - 4, 0x555555);
|
||||
this.walls.add(wall);
|
||||
break;
|
||||
case 'B': // Box (brown rectangle)
|
||||
const box = this.add.rectangle(tileX, tileY, this.tileSize - 10, this.tileSize - 10, 0x8B4513);
|
||||
this.physics.add.existing(box);
|
||||
box.setData('onTarget', false);
|
||||
this.boxes.add(box);
|
||||
break;
|
||||
case 'P': // Player (blue circle)
|
||||
this.player = this.add.circle(tileX, tileY, this.tileSize / 2 - 5, 0x0000FF);
|
||||
this.physics.add.existing(this.player);
|
||||
this.player.setData('gridX', x);
|
||||
this.player.setData('gridY', y);
|
||||
break;
|
||||
case 'T': // Target (red circle outline)
|
||||
const target = this.add.circle(tileX, tileY, this.tileSize / 3, 0xFF0000, 0.2);
|
||||
target.setStrokeStyle(2, 0xFF0000);
|
||||
this.targets.add(target);
|
||||
break;
|
||||
case 'BT': // Box on target (brown rectangle with red outline)
|
||||
const targetBox = this.add.rectangle(tileX, tileY, this.tileSize - 10, this.tileSize - 10, 0x8B4513);
|
||||
targetBox.setStrokeStyle(3, 0xFF0000);
|
||||
this.physics.add.existing(targetBox);
|
||||
targetBox.setData('onTarget', true);
|
||||
this.boxes.add(targetBox);
|
||||
|
||||
const boxTarget = this.add.circle(tileX, tileY, this.tileSize / 3, 0xFF0000, 0.2);
|
||||
boxTarget.setStrokeStyle(2, 0xFF0000);
|
||||
this.targets.add(boxTarget);
|
||||
break;
|
||||
case '.': // Empty space - do nothing
|
||||
break;
|
||||
default:
|
||||
console.warn(`Unknown tile type: ${tile} at position (${x}, ${y})`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set up collisions
|
||||
this.physics.add.collider(this.player, this.walls);
|
||||
this.physics.add.collider(this.boxes, this.walls);
|
||||
this.physics.add.collider(this.boxes, this.boxes);
|
||||
}
|
||||
|
||||
createUI() {
|
||||
// Add level text
|
||||
const levelText = this.add.text(
|
||||
20,
|
||||
20,
|
||||
`LEVEL ${game.globals.currentLevel + 1}`,
|
||||
{
|
||||
fontSize: '24px',
|
||||
fill: '#000'
|
||||
}
|
||||
);
|
||||
|
||||
// Add restart button
|
||||
const restartButton = this.add.text(
|
||||
this.cameras.main.width - 20,
|
||||
20,
|
||||
'RESTART',
|
||||
{
|
||||
fontSize: '24px',
|
||||
fill: '#000',
|
||||
backgroundColor: '#f0f0f0',
|
||||
padding: { left: 15, right: 15, top: 5, bottom: 5 }
|
||||
}
|
||||
);
|
||||
restartButton.setOrigin(1, 0);
|
||||
restartButton.setInteractive({ useHandCursor: true });
|
||||
|
||||
// Add hover effect
|
||||
restartButton.on('pointerover', () => {
|
||||
restartButton.setStyle({ fill: '#555' });
|
||||
});
|
||||
|
||||
restartButton.on('pointerout', () => {
|
||||
restartButton.setStyle({ fill: '#000' });
|
||||
});
|
||||
|
||||
// Add click event
|
||||
restartButton.on('pointerdown', () => {
|
||||
this.scene.restart();
|
||||
});
|
||||
|
||||
// Add back button
|
||||
const backButton = this.add.text(
|
||||
20,
|
||||
this.cameras.main.height - 20,
|
||||
'BACK TO MENU',
|
||||
{
|
||||
fontSize: '24px',
|
||||
fill: '#000',
|
||||
backgroundColor: '#f0f0f0',
|
||||
padding: { left: 15, right: 15, top: 5, bottom: 5 }
|
||||
}
|
||||
);
|
||||
backButton.setOrigin(0, 1);
|
||||
backButton.setInteractive({ useHandCursor: true });
|
||||
|
||||
// Add hover effect
|
||||
backButton.on('pointerover', () => {
|
||||
backButton.setStyle({ fill: '#555' });
|
||||
});
|
||||
|
||||
backButton.on('pointerout', () => {
|
||||
backButton.setStyle({ fill: '#000' });
|
||||
});
|
||||
|
||||
// Add click event
|
||||
backButton.on('pointerdown', () => {
|
||||
this.scene.start('MenuScene');
|
||||
});
|
||||
}
|
||||
|
||||
movePlayer(dx, dy) {
|
||||
// Set moving flag
|
||||
this.isMoving = true;
|
||||
|
||||
// Get player's current grid position
|
||||
const gridX = this.player.getData('gridX');
|
||||
const gridY = this.player.getData('gridY');
|
||||
|
||||
// Calculate new position
|
||||
const newX = gridX + dx;
|
||||
const newY = gridY + dy;
|
||||
|
||||
// Check if the new position is valid
|
||||
if (this.isValidMove(newX, newY, dx, dy)) {
|
||||
// Update player's grid position
|
||||
this.player.setData('gridX', newX);
|
||||
this.player.setData('gridY', newY);
|
||||
|
||||
// Calculate pixel position
|
||||
const levelWidth = this.levelData.width * this.tileSize;
|
||||
const levelHeight = this.levelData.height * this.tileSize;
|
||||
const offsetX = (this.cameras.main.width - levelWidth) / 2;
|
||||
const offsetY = (this.cameras.main.height - levelHeight) / 2;
|
||||
|
||||
const pixelX = offsetX + newX * this.tileSize + this.tileSize / 2;
|
||||
const pixelY = offsetY + newY * this.tileSize + this.tileSize / 2;
|
||||
|
||||
// Move player
|
||||
this.tweens.add({
|
||||
targets: this.player,
|
||||
x: pixelX,
|
||||
y: pixelY,
|
||||
duration: 100,
|
||||
onComplete: () => {
|
||||
this.isMoving = false;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.isMoving = false;
|
||||
}
|
||||
}
|
||||
|
||||
isValidMove(newX, newY, dx, dy) {
|
||||
// Check if the new position is out of bounds
|
||||
if (newX < 0 || newX >= this.levelData.width || newY < 0 || newY >= this.levelData.height) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Calculate level offset
|
||||
const levelWidth = this.levelData.width * this.tileSize;
|
||||
const levelHeight = this.levelData.height * this.tileSize;
|
||||
const offsetX = (this.cameras.main.width - levelWidth) / 2;
|
||||
const offsetY = (this.cameras.main.height - levelHeight) / 2;
|
||||
|
||||
// Calculate pixel position
|
||||
const pixelX = offsetX + newX * this.tileSize + this.tileSize / 2;
|
||||
const pixelY = offsetY + newY * this.tileSize + this.tileSize / 2;
|
||||
|
||||
// Check if there's a wall at the new position
|
||||
let wallAtPosition = false;
|
||||
this.walls.getChildren().forEach(wall => {
|
||||
if (wall.x === pixelX && wall.y === pixelY) {
|
||||
wallAtPosition = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (wallAtPosition) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if there's a box at the new position
|
||||
let boxAtPosition = null;
|
||||
this.boxes.getChildren().forEach(box => {
|
||||
if (box.x === pixelX && box.y === pixelY) {
|
||||
boxAtPosition = box;
|
||||
}
|
||||
});
|
||||
|
||||
if (boxAtPosition) {
|
||||
// Calculate the position behind the box
|
||||
const behindX = newX + dx;
|
||||
const behindY = newY + dy;
|
||||
|
||||
// Check if the position behind the box is valid
|
||||
if (this.isValidBoxMove(behindX, behindY)) {
|
||||
// Calculate pixel position behind the box
|
||||
const behindPixelX = offsetX + behindX * this.tileSize + this.tileSize / 2;
|
||||
const behindPixelY = offsetY + behindY * this.tileSize + this.tileSize / 2;
|
||||
|
||||
// Move the box
|
||||
this.tweens.add({
|
||||
targets: boxAtPosition,
|
||||
x: behindPixelX,
|
||||
y: behindPixelY,
|
||||
duration: 100,
|
||||
onComplete: () => {
|
||||
// Check if the box is on a target
|
||||
let onTarget = false;
|
||||
this.targets.getChildren().forEach(target => {
|
||||
if (target.x === behindPixelX && target.y === behindPixelY) {
|
||||
onTarget = true;
|
||||
}
|
||||
});
|
||||
|
||||
// Update box appearance
|
||||
if (onTarget) {
|
||||
boxAtPosition.setStrokeStyle(3, 0xFF0000);
|
||||
boxAtPosition.setData('onTarget', true);
|
||||
} else {
|
||||
boxAtPosition.setStrokeStyle(0);
|
||||
boxAtPosition.setData('onTarget', false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
isValidBoxMove(x, y) {
|
||||
// Check if the position is out of bounds
|
||||
if (x < 0 || x >= this.levelData.width || y < 0 || y >= this.levelData.height) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Calculate level offset
|
||||
const levelWidth = this.levelData.width * this.tileSize;
|
||||
const levelHeight = this.levelData.height * this.tileSize;
|
||||
const offsetX = (this.cameras.main.width - levelWidth) / 2;
|
||||
const offsetY = (this.cameras.main.height - levelHeight) / 2;
|
||||
|
||||
// Calculate pixel position
|
||||
const pixelX = offsetX + x * this.tileSize + this.tileSize / 2;
|
||||
const pixelY = offsetY + y * this.tileSize + this.tileSize / 2;
|
||||
|
||||
// Check if there's a wall at the position
|
||||
let wallAtPosition = false;
|
||||
this.walls.getChildren().forEach(wall => {
|
||||
if (wall.x === pixelX && wall.y === pixelY) {
|
||||
wallAtPosition = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (wallAtPosition) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if there's a box at the position
|
||||
let boxAtPosition = false;
|
||||
this.boxes.getChildren().forEach(box => {
|
||||
if (box.x === pixelX && box.y === pixelY) {
|
||||
boxAtPosition = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (boxAtPosition) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
checkWinCondition() {
|
||||
// Check if all boxes are on targets
|
||||
let allBoxesOnTargets = true;
|
||||
|
||||
this.boxes.getChildren().forEach(box => {
|
||||
if (!box.getData('onTarget')) {
|
||||
allBoxesOnTargets = false;
|
||||
}
|
||||
});
|
||||
|
||||
if (allBoxesOnTargets && this.boxes.getChildren().length > 0) {
|
||||
// Mark level as completed
|
||||
game.globals.levelCompleted[game.globals.currentLevel] = true;
|
||||
|
||||
// Show win message
|
||||
const winText = this.add.text(
|
||||
this.cameras.main.centerX,
|
||||
this.cameras.main.centerY,
|
||||
'LEVEL COMPLETE!',
|
||||
{
|
||||
fontSize: '48px',
|
||||
fill: '#000',
|
||||
backgroundColor: '#4CAF50',
|
||||
padding: { left: 30, right: 30, top: 15, bottom: 15 }
|
||||
}
|
||||
);
|
||||
winText.setOrigin(0.5);
|
||||
|
||||
// Add continue button
|
||||
const continueButton = this.add.text(
|
||||
this.cameras.main.centerX,
|
||||
this.cameras.main.centerY + 100,
|
||||
'CONTINUE',
|
||||
{
|
||||
fontSize: '32px',
|
||||
fill: '#000',
|
||||
backgroundColor: '#f0f0f0',
|
||||
padding: { left: 20, right: 20, top: 10, bottom: 10 }
|
||||
}
|
||||
);
|
||||
continueButton.setOrigin(0.5);
|
||||
continueButton.setInteractive({ useHandCursor: true });
|
||||
|
||||
// Add hover effect
|
||||
continueButton.on('pointerover', () => {
|
||||
continueButton.setStyle({ fill: '#555' });
|
||||
});
|
||||
|
||||
continueButton.on('pointerout', () => {
|
||||
continueButton.setStyle({ fill: '#000' });
|
||||
});
|
||||
|
||||
// Add click event
|
||||
continueButton.on('pointerdown', () => {
|
||||
this.scene.start('LevelScene');
|
||||
});
|
||||
|
||||
// Disable player movement
|
||||
this.isMoving = true;
|
||||
}
|
||||
}
|
||||
|
||||
shutdown() {
|
||||
// Clean up event listeners
|
||||
this.input.keyboard.off('keyup');
|
||||
|
||||
// Reset key state
|
||||
this.keyState = {
|
||||
up: false,
|
||||
down: false,
|
||||
left: false,
|
||||
right: false
|
||||
};
|
||||
}
|
||||
}
|
||||
export default MainScene;
|
||||
@@ -0,0 +1,70 @@
|
||||
class MenuScene extends Phaser.Scene {
|
||||
constructor() {
|
||||
super({ key: 'MenuScene' });
|
||||
}
|
||||
|
||||
preload() {
|
||||
// No assets to preload
|
||||
}
|
||||
|
||||
create() {
|
||||
// Add background
|
||||
this.cameras.main.setBackgroundColor('#f0f0f0');
|
||||
|
||||
// Add title text
|
||||
const title = this.add.text(
|
||||
this.cameras.main.centerX,
|
||||
this.cameras.main.centerY - 100,
|
||||
'SOKOBAN',
|
||||
{
|
||||
fontSize: '64px',
|
||||
fill: '#000',
|
||||
fontStyle: 'bold'
|
||||
}
|
||||
);
|
||||
title.setOrigin(0.5);
|
||||
|
||||
// Add play button
|
||||
const playButton = this.add.text(
|
||||
this.cameras.main.centerX,
|
||||
this.cameras.main.centerY,
|
||||
'PLAY',
|
||||
{
|
||||
fontSize: '32px',
|
||||
fill: '#000',
|
||||
backgroundColor: '#4CAF50',
|
||||
padding: { left: 30, right: 30, top: 10, bottom: 10 }
|
||||
}
|
||||
);
|
||||
playButton.setOrigin(0.5);
|
||||
playButton.setInteractive({ useHandCursor: true });
|
||||
|
||||
// Add hover effect
|
||||
playButton.on('pointerover', () => {
|
||||
playButton.setStyle({ fill: '#fff' });
|
||||
});
|
||||
|
||||
playButton.on('pointerout', () => {
|
||||
playButton.setStyle({ fill: '#000' });
|
||||
});
|
||||
|
||||
// Add click event
|
||||
playButton.on('pointerdown', () => {
|
||||
this.scene.start('LevelScene');
|
||||
});
|
||||
|
||||
// Add instructions
|
||||
const instructions = this.add.text(
|
||||
this.cameras.main.centerX,
|
||||
this.cameras.main.centerY + 100,
|
||||
'Use arrow keys to move\nPush boxes to the targets',
|
||||
{
|
||||
fontSize: '24px',
|
||||
fill: '#000',
|
||||
align: 'center'
|
||||
}
|
||||
);
|
||||
instructions.setOrigin(0.5);
|
||||
}
|
||||
}
|
||||
export default MenuScene;
|
||||
@@ -0,0 +1,17 @@
|
||||
import { defineConfig } from 'vite';
|
||||
|
||||
export default defineConfig({
|
||||
base: './',
|
||||
build: {
|
||||
rollupOptions: {
|
||||
output: {
|
||||
manualChunks: {
|
||||
phaser: ['phaser']
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
server: {
|
||||
port: 8080
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,47 @@
|
||||
import { defineConfig } from 'vite';
|
||||
|
||||
const phasermsg = () => {
|
||||
return {
|
||||
name: 'phasermsg',
|
||||
buildStart() {
|
||||
process.stdout.write(`Building for production...\n`);
|
||||
},
|
||||
buildEnd() {
|
||||
const line = "---------------------------------------------------------";
|
||||
const msg = `❤️❤️❤️ Tell us about your game! - games@phaser.io ❤️❤️❤️`;
|
||||
process.stdout.write(`${line}\n${msg}\n${line}\n`);
|
||||
|
||||
process.stdout.write(`✨ Done ✨\n`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default defineConfig({
|
||||
base: './',
|
||||
logLevel: 'warn',
|
||||
build: {
|
||||
rollupOptions: {
|
||||
output: {
|
||||
manualChunks: {
|
||||
phaser: ['phaser']
|
||||
}
|
||||
}
|
||||
},
|
||||
minify: 'terser',
|
||||
terserOptions: {
|
||||
compress: {
|
||||
passes: 2
|
||||
},
|
||||
mangle: true,
|
||||
format: {
|
||||
comments: false
|
||||
}
|
||||
}
|
||||
},
|
||||
server: {
|
||||
port: 8080
|
||||
},
|
||||
plugins: [
|
||||
phasermsg()
|
||||
]
|
||||
});
|
||||
Reference in new issue
Block a user