Compare commits
No commits in common. "master" and "tileImplmentation" have entirely different histories.
master
...
tileImplme
189 changed files with 965 additions and 318080 deletions
17
.gitignore
vendored
17
.gitignore
vendored
|
|
@ -1,20 +1,3 @@
|
|||
# Godot 4+ specific ignores
|
||||
.godot/
|
||||
/android/
|
||||
# Node modules
|
||||
**/node_modules/
|
||||
# Assets/*
|
||||
# Chess engine binaries and their subdirectories
|
||||
Assets/ChessEngines/stockfish/*
|
||||
Assets/ChessEngines/Fairy-Stockfish/*
|
||||
Assets/ChessEngines/quack/*
|
||||
# Assets/ChessEngines/stockfish/**
|
||||
# Assets/ChessEngines/Fairy-Stockfish/**
|
||||
|
||||
|
||||
# # Keep only necessary files in fairy-chess-server
|
||||
Assets/ChessEngines/fairy-chess-server/*
|
||||
!Assets/ChessEngines/fairy-chess-server/*.js
|
||||
!Assets/ChessEngines/fairy-chess-server/*.json
|
||||
|
||||
build/*
|
||||
|
|
@ -1,48 +0,0 @@
|
|||
// Assets/ChessEngines/fairy-chess-server/engine-path.js
|
||||
import path from 'path';
|
||||
import os from 'os';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { dirname } from 'path';
|
||||
|
||||
// Get current file's directory in ES modules
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
function getEnginePath() {
|
||||
console.log("getEnginePath")
|
||||
// First check if path is provided through environment variable
|
||||
if (process.env.FAIRY_STOCKFISH_PATH) {
|
||||
return process.env.FAIRY_STOCKFISH_PATH;
|
||||
}
|
||||
|
||||
// Get the current directory (fairy-chess-server)
|
||||
const currentDir = __dirname;
|
||||
console.log(currentDir )
|
||||
|
||||
// Navigate up to Assets/ChessEngines
|
||||
const engineBaseDir = path.join(currentDir, '..', '..');
|
||||
console.log(engineBaseDir )
|
||||
|
||||
// Determine OS and set appropriate path
|
||||
if (os.platform() === 'win32') {
|
||||
// Windows path
|
||||
console.log(engineBaseDir + '/ChessEngines' + '/stockfish' + '/stockfish.exe')
|
||||
return engineBaseDir + '/ChessEngines' + '/stockfish' + '/stockfish.exe'
|
||||
} else {
|
||||
//res://Assets/ChessEngines/Fairy-Stockfish/src/stockfish
|
||||
// Unix-like systems (Linux, macOS)
|
||||
console.log(engineBaseDir + '/ChessEngines'+ '/Fairy-Stockfish' + '/src' + '/stockfish')
|
||||
return engineBaseDir + '/ChessEngines'+ '/Fairy-Stockfish' + '/src' + '/stockfish';
|
||||
}
|
||||
}
|
||||
|
||||
// Verify engine path exists
|
||||
async function verifyEnginePath(enginePath) {
|
||||
const fs = await import('fs');
|
||||
if (!fs.existsSync(enginePath)) {
|
||||
throw new Error(`Chess engine not found at path: ${enginePath}`);
|
||||
}
|
||||
return enginePath;
|
||||
}
|
||||
|
||||
export { getEnginePath, verifyEnginePath };
|
||||
|
|
@ -1,203 +0,0 @@
|
|||
// engine.js
|
||||
import { spawn } from 'child_process';
|
||||
import { EventEmitter } from 'events';
|
||||
|
||||
class ChessEngine extends EventEmitter {
|
||||
constructor(enginePath) {
|
||||
super();
|
||||
this.enginePath = enginePath;
|
||||
this.engine = null;
|
||||
this.isReady = false;
|
||||
this.currentFen = null;
|
||||
}
|
||||
|
||||
start() {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
console.log()
|
||||
this.engine = spawn(this.enginePath);
|
||||
|
||||
this.engine.stdout.on('data', (data) => {
|
||||
const messages = data.toString().trim().split('\n');
|
||||
messages.forEach(msg => this.handleEngineMessage(msg));
|
||||
});
|
||||
|
||||
this.engine.stderr.on('data', (data) => {
|
||||
console.error('Engine Error:', data.toString());
|
||||
});
|
||||
|
||||
this.engine.on('close', (code) => {
|
||||
console.log('Engine process closed with code:', code);
|
||||
this.isReady = false;
|
||||
// console.log('Attempting to restart engine...');
|
||||
// // Add a small delay before restarting to avoid rapid restart loops
|
||||
// setTimeout(() => {
|
||||
// this.start()
|
||||
// .then(() => {
|
||||
// // Restore previous state if needed
|
||||
// if (this.currentFen) {
|
||||
// this.setPosition(this.currentFen);
|
||||
// }
|
||||
// this.emit('restarted', { code });
|
||||
// })
|
||||
// .catch(err => {
|
||||
// console.error('Failed to restart engine:', err);
|
||||
// this.emit('restart-failed', { error: err });
|
||||
// });
|
||||
// }, 1000);
|
||||
});
|
||||
|
||||
// Initialize engine
|
||||
this.sendCommand('uci');
|
||||
this.waitforReady(resolve);
|
||||
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
waitforReady(resolve){
|
||||
this.sendCommand('isready');
|
||||
|
||||
// Wait for readyok
|
||||
this.once('ready', () => {
|
||||
this.isReady = true;
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
|
||||
handleEngineMessage(message) {
|
||||
console.log('Engine:', message);
|
||||
|
||||
if (message.includes('readyok')) {
|
||||
this.emit('ready');
|
||||
}
|
||||
else if (message.startsWith('bestmove')) {
|
||||
const [, move] = message.split(' ');
|
||||
this.emit('bestmove', move);
|
||||
}
|
||||
else if (message.startsWith('info')) {
|
||||
this.emit('info', this.parseInfo(message));
|
||||
}
|
||||
}
|
||||
|
||||
parseInfo(info) {
|
||||
const parts = info.split(' ');
|
||||
const result = {};
|
||||
|
||||
for (let i = 1; i < parts.length; i++) {
|
||||
switch (parts[i]) {
|
||||
case 'depth':
|
||||
result.depth = parseInt(parts[++i]);
|
||||
break;
|
||||
case 'seldepth':
|
||||
result.seldepth = parseInt(parts[++i]);
|
||||
break;
|
||||
case 'score':
|
||||
result.score = {
|
||||
type: parts[++i],
|
||||
value: parseInt(parts[++i])
|
||||
};
|
||||
break;
|
||||
case 'nodes':
|
||||
result.nodes = parseInt(parts[++i]);
|
||||
break;
|
||||
case 'time':
|
||||
result.time = parseInt(parts[++i]);
|
||||
break;
|
||||
case 'pv':
|
||||
result.pv = [];
|
||||
while (++i < parts.length && !parts[i].includes('bmc')) {
|
||||
result.pv.push(parts[i]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
sendCommand(cmd) {
|
||||
if (this.engine && this.engine.stdin.writable) {
|
||||
console.log('Sending:', cmd);
|
||||
this.engine.stdin.write(cmd + '\n');
|
||||
}
|
||||
}
|
||||
async startPos() {
|
||||
if (!this.isReady) throw new Error('Engine not ready');
|
||||
|
||||
this.sendCommand(`position startpos`);
|
||||
|
||||
// Ensure engine is ready after position set
|
||||
this.sendCommand('isready');
|
||||
await new Promise(resolve => this.once('ready', resolve));
|
||||
}
|
||||
async setBoardPosition(fen) {
|
||||
if (!this.isReady) throw new Error('Engine not ready');
|
||||
|
||||
this.currentFen = fen;
|
||||
this.sendCommand(`position fen ${fen}`);
|
||||
|
||||
// Ensure engine is ready after position set
|
||||
this.sendCommand('isready');
|
||||
await new Promise(resolve => this.once('ready', resolve));
|
||||
}
|
||||
|
||||
async setNewGame() {
|
||||
// if (!this.isReady) throw new Error('Engine not ready');
|
||||
|
||||
this.sendCommand(`ucinewgame`);
|
||||
|
||||
// Ensure engine is ready after position set
|
||||
this.sendCommand('isready');
|
||||
await new Promise(resolve => this.once('ready', resolve));
|
||||
}
|
||||
|
||||
async getBestMove(options = {}) {
|
||||
if (!this.isReady) throw new Error('Engine not ready');
|
||||
if (!this.currentFen) throw new Error('Position not set');
|
||||
|
||||
const {
|
||||
depth = 15,
|
||||
movetime = 1000,
|
||||
nodes = null,
|
||||
} = options;
|
||||
|
||||
return new Promise((resolve) => {
|
||||
// Construct go command
|
||||
let goCmd = 'go';
|
||||
if (depth) goCmd += ` depth ${depth}`;
|
||||
if (movetime) goCmd += ` movetime ${movetime}`;
|
||||
if (nodes) goCmd += ` nodes ${nodes}`;
|
||||
|
||||
this.sendCommand(goCmd);
|
||||
|
||||
// Wait for bestmove response
|
||||
this.once('bestmove', (move) => {
|
||||
resolve(move);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async getAnalysis(fen, options = {}) {
|
||||
await this.setBoardPosition(fen);
|
||||
const bestMove = await this.getBestMove(options);
|
||||
return { bestMove };
|
||||
}
|
||||
|
||||
async createNewGame(){
|
||||
await this.setNewGame();
|
||||
return true
|
||||
}
|
||||
|
||||
quit() {
|
||||
if (this.engine) {
|
||||
this.sendCommand('quit');
|
||||
this.engine = null;
|
||||
this.isReady = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default ChessEngine;
|
||||
|
|
@ -1,408 +0,0 @@
|
|||
import express from 'express';
|
||||
import ffish from 'ffish';
|
||||
import ChessEngine from './engine.js';
|
||||
import { getEnginePath, verifyEnginePath } from './engine-path.js';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import os from 'os';
|
||||
|
||||
function getLogPath() {
|
||||
if (process.platform === 'linux') {
|
||||
// Use ~/.local/share/ChessBuilder/logs
|
||||
return path.join(os.homedir(), '.local', 'share', 'ChessBuilder', 'logs');
|
||||
} else if (process.platform === 'win32') {
|
||||
// Use %APPDATA%\ChessBuilder\logs
|
||||
return path.join(os.homedir(), 'AppData', 'Roaming', 'ChessBuilder', 'logs');
|
||||
} else {
|
||||
// macOS: ~/Library/Logs/ChessBuilder
|
||||
return path.join(os.homedir(), 'Library', 'ChessBuilder', 'logs');
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure log directory exists
|
||||
const logDir = getLogPath();
|
||||
fs.mkdirSync(logDir, { recursive: true });
|
||||
|
||||
const logFile = path.join(logDir, 'chess-server.log');
|
||||
console.log(`Logging to: ${logFile}`);
|
||||
|
||||
// Create a write stream for logging
|
||||
const logStream = fs.createWriteStream(logFile, { flags: 'a' });
|
||||
|
||||
// Redirect console.log and console.error to both console and file
|
||||
const originalConsoleLog = console.log;
|
||||
const originalConsoleError = console.error;
|
||||
|
||||
console.log = (...args) => {
|
||||
const message = args.map(arg =>
|
||||
typeof arg === 'object' ? JSON.stringify(arg) : arg
|
||||
).join(' ') + '\n';
|
||||
|
||||
logStream.write(`[${new Date().toISOString()}] ${message}`);
|
||||
originalConsoleLog.apply(console, args);
|
||||
};
|
||||
|
||||
console.error = (...args) => {
|
||||
const message = args.map(arg =>
|
||||
typeof arg === 'object' ? JSON.stringify(arg) : arg
|
||||
).join(' ') + '\n';
|
||||
|
||||
logStream.write(`[${new Date().toISOString()}] ERROR: ${message}`);
|
||||
originalConsoleError.apply(console, args);
|
||||
};
|
||||
|
||||
|
||||
try {
|
||||
fs.writeFileSync(logFile, `Server started at ${new Date().toISOString()}\n`, { flag: 'a' });
|
||||
fs.writeFileSync(logFile, `Process ID: ${process.pid}\n`, { flag: 'a' });
|
||||
fs.writeFileSync(logFile, `Working directory: ${process.cwd()}\n`, { flag: 'a' });
|
||||
fs.writeFileSync(logFile, `Node version: ${process.version}\n`, { flag: 'a' });
|
||||
} catch (error) {
|
||||
console.error('Failed to write startup log:', error);
|
||||
}
|
||||
|
||||
const app = express();
|
||||
const port = 27531;
|
||||
|
||||
let engine = null;
|
||||
let isReady = false;
|
||||
let lastResponse = null
|
||||
const SERVER_WAIT_THRESHOLD = 1 * 60 * 1000;
|
||||
const CHECK_INTERVAL = 5000;
|
||||
|
||||
// Initialize ffish and engine
|
||||
ffish.onRuntimeInitialized = async () => {
|
||||
try {
|
||||
isReady = true;
|
||||
console.log('Fairy-Chess library ready asd');
|
||||
|
||||
// Get and verify engine path
|
||||
const enginePath = getEnginePath();
|
||||
console.log('ENGINE PATH', enginePath);
|
||||
|
||||
// Initialize Stockfish
|
||||
fs.chmodSync(enginePath, '755');
|
||||
engine = new ChessEngine(enginePath);
|
||||
await engine.start();
|
||||
console.log('Chess engine initialized at:', enginePath, engine.isReady);
|
||||
|
||||
// Set initial engine options
|
||||
if (engine.isReady) {
|
||||
engine.sendCommand('setoption name Threads value 4');
|
||||
engine.sendCommand('setoption name Hash value 128');
|
||||
engine.sendCommand('setoption name MultiPV value 1');
|
||||
engine.sendCommand('setoption name UCI_LimitStrength value true');
|
||||
engine.sendCommand('uci');
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('Initialization error:', error);
|
||||
}
|
||||
};
|
||||
|
||||
app.use(express.json());
|
||||
|
||||
// Health check endpoint
|
||||
app.get('/health', (req, res) => {
|
||||
console.log("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@")
|
||||
console.log("@@@@@@@@@@@@@@@@@HEALTH CHECK@@@@@@@@@@@@@@@@@@@@@@")
|
||||
console.log("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@")
|
||||
console.log(JSON.stringify(ffish.variants()))
|
||||
lastResponse = new Date().getTime()
|
||||
res.json({
|
||||
status: 'ok',
|
||||
engineReady: isReady && engine && engine.isReady,
|
||||
enginePath: getEnginePath(),
|
||||
platform: process.platform,
|
||||
variants: ffish.variants()
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
// Set Options
|
||||
app.post('/setoptions', (req, res) => {
|
||||
lastResponse = new Date().getTime()
|
||||
const { name, value, options } = req.body;
|
||||
if(name && value !== null){
|
||||
engine.sendCommand(`setoption name ${name} value ${value}`);
|
||||
res.json({
|
||||
status: 'ok',
|
||||
success: name,
|
||||
errs: null,
|
||||
});
|
||||
}else if(options && Array.isArray(options)){
|
||||
let success = [];
|
||||
let errs = [];
|
||||
for(let x = 0; x < options.length; x++){
|
||||
let option = options[x];
|
||||
const {name, value} = option;
|
||||
if(name && value !== null ){
|
||||
engine.sendCommand(`setoption name ${name} value ${value}`);
|
||||
success.push(name)
|
||||
}else {
|
||||
errs.push(name)
|
||||
}
|
||||
}
|
||||
res.json({
|
||||
status: 'ok',
|
||||
success: success,
|
||||
errs: errs,
|
||||
});
|
||||
}else {
|
||||
return res.status(400).json({ error: 'No Params Provided' });
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
|
||||
// Validate FEN endpoint
|
||||
app.post('/validate', (req, res) => {
|
||||
lastResponse = new Date().getTime()
|
||||
const { fen, variant = 'chess' } = req.body;
|
||||
|
||||
if (!fen) {
|
||||
return res.status(400).json({ error: 'FEN string required' });
|
||||
}
|
||||
|
||||
try {
|
||||
const tempBoard = new ffish.Board(variant);
|
||||
const isValid = tempBoard.setFen(fen);
|
||||
tempBoard.delete();
|
||||
|
||||
res.json({
|
||||
status: 'ok',
|
||||
isValid,
|
||||
startingFen: ffish.startingFen(variant)
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(400).json({ error: 'Invalid FEN or variant' });
|
||||
}
|
||||
});
|
||||
|
||||
// New game endpoint
|
||||
app.post('/new', async (req, res) => {
|
||||
lastResponse = new Date().getTime()
|
||||
const { variant = 'chess' } = req.body;
|
||||
|
||||
try {
|
||||
|
||||
const engineAnalysis = await engine.createNewGame();
|
||||
res.json({
|
||||
status: 'ok',
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// Set position endpoint
|
||||
app.post('/position', async(req, res) => {
|
||||
lastResponse = new Date().getTime()
|
||||
const { fen, variant = 'chess', start } = req.body;
|
||||
|
||||
|
||||
try {
|
||||
//we have a lot of funky rules lets not validate
|
||||
// const isValid = ffish.validateFen(fen) == 1
|
||||
|
||||
if(start){
|
||||
await engine.startPos()
|
||||
engine.sendCommand('d');
|
||||
}else if(fen){
|
||||
await engine.setBoardPosition(fen)
|
||||
}
|
||||
|
||||
res.json({
|
||||
status: 'ok',
|
||||
msg: "Set Position",
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message, poserr: true });
|
||||
}
|
||||
});
|
||||
|
||||
// Make move endpoint
|
||||
app.post('/move', (req, res) => {
|
||||
lastResponse = new Date().getTime()
|
||||
const { move, notation = 'uci', variant = 'chess' } = req.body;
|
||||
|
||||
if (!move) {
|
||||
return res.status(400).json({ error: 'Move required' });
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
const response = {
|
||||
status: 'ok',
|
||||
};
|
||||
|
||||
res.json(response);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// State endpoint
|
||||
app.get('/state', (req, res) => {
|
||||
lastResponse = new Date().getTime()
|
||||
try {
|
||||
res.json({
|
||||
status: 'ok',
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
createNewGame
|
||||
*/
|
||||
app.post('/newgame', async (req, res) => {
|
||||
lastResponse = new Date().getTime()
|
||||
|
||||
|
||||
try {
|
||||
|
||||
const response = {
|
||||
status: 'ok',
|
||||
};
|
||||
|
||||
// If engine is available, get engine analysis
|
||||
if (engine && engine.isReady) {
|
||||
const engineAnalysis = await engine.createNewGame();
|
||||
}
|
||||
|
||||
res.json(response);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// Analysis endpoint
|
||||
app.post('/analyze', async (req, res) => {
|
||||
lastResponse = new Date().getTime()
|
||||
|
||||
try {
|
||||
const { depth = 15, movetime = 1000 } = req.body;
|
||||
|
||||
// Get basic position analysis
|
||||
const positionAnalysis = {
|
||||
status: 'ok',
|
||||
};
|
||||
|
||||
// If engine is available, get engine analysis
|
||||
if (engine && engine.isReady) {
|
||||
const engineAnalysis = await engine.getAnalysis("", {
|
||||
depth,
|
||||
movetime
|
||||
});
|
||||
positionAnalysis.engineAnalysis = engineAnalysis;
|
||||
}
|
||||
|
||||
res.json(positionAnalysis);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// Engine move endpoint
|
||||
app.post('/enginemove', async (req, res) => {
|
||||
lastResponse = new Date().getTime()
|
||||
|
||||
if (!engine || !engine.isReady) {
|
||||
return res.status(503).json({
|
||||
error: 'Engine not available',
|
||||
details: 'Chess engine is not initialized or not ready'
|
||||
});
|
||||
}
|
||||
|
||||
const {
|
||||
depth = 15,
|
||||
movetime = 1000,
|
||||
nodes = null,
|
||||
fen
|
||||
} = req.body;
|
||||
|
||||
try {
|
||||
const analysis = await engine.getAnalysis(fen, {
|
||||
depth,
|
||||
movetime,
|
||||
nodes
|
||||
});
|
||||
|
||||
|
||||
res.json({
|
||||
status: 'ok',
|
||||
move: analysis.bestMove,
|
||||
analysis: analysis,
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
error: 'Engine analysis failed',
|
||||
details: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
app.post('/shutdown', (req, res) => {
|
||||
lastResponse = new Date().getTime()
|
||||
res.json({ status: 'shutting_down' });
|
||||
|
||||
// Give time for response to be sent
|
||||
setTimeout(() => {
|
||||
closeServer()
|
||||
}, 100);
|
||||
});
|
||||
|
||||
// Cleanup handling
|
||||
process.on('SIGTERM', () => {
|
||||
console.log('Shutting down...');
|
||||
closeServer()
|
||||
logStream.end();
|
||||
});
|
||||
|
||||
process.on('SIGINT', () => {
|
||||
console.log('Received SIGINT, shutting down...');
|
||||
closeServer()
|
||||
logStream.end();
|
||||
});
|
||||
|
||||
app.listen(port, () => {
|
||||
lastResponse = new Date().getTime()
|
||||
startIdleMonitor();
|
||||
console.log(`Fairy-Chess server running on port ${port}`);
|
||||
});
|
||||
|
||||
function startIdleMonitor() {
|
||||
const checkIdle = () => {
|
||||
const currentTime = new Date().getTime();
|
||||
const timeSinceLastResponse = currentTime - lastResponse;
|
||||
|
||||
if (timeSinceLastResponse > SERVER_WAIT_THRESHOLD) {
|
||||
console.log(`Server idle for ${timeSinceLastResponse/1000} seconds. Shutting down...`);
|
||||
console.log(currentTime, lastResponse, timeSinceLastResponse, SERVER_WAIT_THRESHOLD)
|
||||
closeServer();
|
||||
}
|
||||
};
|
||||
|
||||
// Start the monitoring interval
|
||||
const monitorInterval = setInterval(checkIdle, CHECK_INTERVAL);
|
||||
|
||||
// Clean up interval on server close
|
||||
process.on('SIGTERM', () => {
|
||||
clearInterval(monitorInterval);
|
||||
closeServer();
|
||||
});
|
||||
|
||||
process.on('SIGINT', () => {
|
||||
clearInterval(monitorInterval);
|
||||
closeServer();
|
||||
});
|
||||
}
|
||||
|
||||
function closeServer(){
|
||||
if (engine) engine.quit();
|
||||
process.exit(0);
|
||||
}
|
||||
607
Assets/ChessEngines/fairy-chess-server/package-lock.json
generated
607
Assets/ChessEngines/fairy-chess-server/package-lock.json
generated
|
|
@ -1,607 +0,0 @@
|
|||
{
|
||||
"name": "fairy-chess-server",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"accepts": {
|
||||
"version": "1.3.8",
|
||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
|
||||
"integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
|
||||
"requires": {
|
||||
"mime-types": "~2.1.34",
|
||||
"negotiator": "0.6.3"
|
||||
}
|
||||
},
|
||||
"array-flatten": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
||||
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
|
||||
},
|
||||
"asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
|
||||
},
|
||||
"axios": {
|
||||
"version": "1.7.9",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz",
|
||||
"integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==",
|
||||
"requires": {
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.0",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"body-parser": {
|
||||
"version": "1.20.3",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
|
||||
"integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==",
|
||||
"requires": {
|
||||
"bytes": "3.1.2",
|
||||
"content-type": "~1.0.5",
|
||||
"debug": "2.6.9",
|
||||
"depd": "2.0.0",
|
||||
"destroy": "1.2.0",
|
||||
"http-errors": "2.0.0",
|
||||
"iconv-lite": "0.4.24",
|
||||
"on-finished": "2.4.1",
|
||||
"qs": "6.13.0",
|
||||
"raw-body": "2.5.2",
|
||||
"type-is": "~1.6.18",
|
||||
"unpipe": "1.0.0"
|
||||
}
|
||||
},
|
||||
"bytes": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
|
||||
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="
|
||||
},
|
||||
"call-bind-apply-helpers": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
||||
"requires": {
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2"
|
||||
}
|
||||
},
|
||||
"call-bound": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz",
|
||||
"integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==",
|
||||
"requires": {
|
||||
"call-bind-apply-helpers": "^1.0.1",
|
||||
"get-intrinsic": "^1.2.6"
|
||||
}
|
||||
},
|
||||
"combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"requires": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
}
|
||||
},
|
||||
"content-disposition": {
|
||||
"version": "0.5.4",
|
||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
|
||||
"integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
|
||||
"requires": {
|
||||
"safe-buffer": "5.2.1"
|
||||
}
|
||||
},
|
||||
"content-type": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
|
||||
"integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="
|
||||
},
|
||||
"cookie": {
|
||||
"version": "0.7.1",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
|
||||
"integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w=="
|
||||
},
|
||||
"cookie-signature": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
||||
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
|
||||
},
|
||||
"debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="
|
||||
},
|
||||
"depd": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
||||
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="
|
||||
},
|
||||
"destroy": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
|
||||
"integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg=="
|
||||
},
|
||||
"dunder-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
||||
"requires": {
|
||||
"call-bind-apply-helpers": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"gopd": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"ee-first": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
|
||||
},
|
||||
"encodeurl": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
|
||||
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="
|
||||
},
|
||||
"es-define-property": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="
|
||||
},
|
||||
"es-errors": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="
|
||||
},
|
||||
"es-object-atoms": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
||||
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
||||
"requires": {
|
||||
"es-errors": "^1.3.0"
|
||||
}
|
||||
},
|
||||
"es-set-tostringtag": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
||||
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
||||
"requires": {
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.6",
|
||||
"has-tostringtag": "^1.0.2",
|
||||
"hasown": "^2.0.2"
|
||||
}
|
||||
},
|
||||
"escape-html": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
||||
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
|
||||
},
|
||||
"etag": {
|
||||
"version": "1.8.1",
|
||||
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
|
||||
"integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="
|
||||
},
|
||||
"express": {
|
||||
"version": "4.21.2",
|
||||
"resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
|
||||
"integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==",
|
||||
"requires": {
|
||||
"accepts": "~1.3.8",
|
||||
"array-flatten": "1.1.1",
|
||||
"body-parser": "1.20.3",
|
||||
"content-disposition": "0.5.4",
|
||||
"content-type": "~1.0.4",
|
||||
"cookie": "0.7.1",
|
||||
"cookie-signature": "1.0.6",
|
||||
"debug": "2.6.9",
|
||||
"depd": "2.0.0",
|
||||
"encodeurl": "~2.0.0",
|
||||
"escape-html": "~1.0.3",
|
||||
"etag": "~1.8.1",
|
||||
"finalhandler": "1.3.1",
|
||||
"fresh": "0.5.2",
|
||||
"http-errors": "2.0.0",
|
||||
"merge-descriptors": "1.0.3",
|
||||
"methods": "~1.1.2",
|
||||
"on-finished": "2.4.1",
|
||||
"parseurl": "~1.3.3",
|
||||
"path-to-regexp": "0.1.12",
|
||||
"proxy-addr": "~2.0.7",
|
||||
"qs": "6.13.0",
|
||||
"range-parser": "~1.2.1",
|
||||
"safe-buffer": "5.2.1",
|
||||
"send": "0.19.0",
|
||||
"serve-static": "1.16.2",
|
||||
"setprototypeof": "1.2.0",
|
||||
"statuses": "2.0.1",
|
||||
"type-is": "~1.6.18",
|
||||
"utils-merge": "1.0.1",
|
||||
"vary": "~1.1.2"
|
||||
}
|
||||
},
|
||||
"ffish": {
|
||||
"version": "0.7.7",
|
||||
"resolved": "https://registry.npmjs.org/ffish/-/ffish-0.7.7.tgz",
|
||||
"integrity": "sha512-s356geaE1BzkWTGW9y8bKMI3iobyxjds0QfK7X1rhA5Gxco5UttpAypFpFad+ruFCvdR8t/OcgXlfQLp2qAPaA=="
|
||||
},
|
||||
"finalhandler": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz",
|
||||
"integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==",
|
||||
"requires": {
|
||||
"debug": "2.6.9",
|
||||
"encodeurl": "~2.0.0",
|
||||
"escape-html": "~1.0.3",
|
||||
"on-finished": "2.4.1",
|
||||
"parseurl": "~1.3.3",
|
||||
"statuses": "2.0.1",
|
||||
"unpipe": "~1.0.0"
|
||||
}
|
||||
},
|
||||
"follow-redirects": {
|
||||
"version": "1.15.9",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
|
||||
"integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ=="
|
||||
},
|
||||
"form-data": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz",
|
||||
"integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==",
|
||||
"requires": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"es-set-tostringtag": "^2.1.0",
|
||||
"mime-types": "^2.1.12"
|
||||
}
|
||||
},
|
||||
"forwarded": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
|
||||
"integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="
|
||||
},
|
||||
"fresh": {
|
||||
"version": "0.5.2",
|
||||
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
|
||||
"integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q=="
|
||||
},
|
||||
"function-bind": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="
|
||||
},
|
||||
"get-intrinsic": {
|
||||
"version": "1.2.7",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.7.tgz",
|
||||
"integrity": "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==",
|
||||
"requires": {
|
||||
"call-bind-apply-helpers": "^1.0.1",
|
||||
"es-define-property": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"es-object-atoms": "^1.0.0",
|
||||
"function-bind": "^1.1.2",
|
||||
"get-proto": "^1.0.0",
|
||||
"gopd": "^1.2.0",
|
||||
"has-symbols": "^1.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"math-intrinsics": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"get-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
||||
"requires": {
|
||||
"dunder-proto": "^1.0.1",
|
||||
"es-object-atoms": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"gopd": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="
|
||||
},
|
||||
"has-symbols": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="
|
||||
},
|
||||
"has-tostringtag": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
||||
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
||||
"requires": {
|
||||
"has-symbols": "^1.0.3"
|
||||
}
|
||||
},
|
||||
"hasown": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||
"requires": {
|
||||
"function-bind": "^1.1.2"
|
||||
}
|
||||
},
|
||||
"http-errors": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
|
||||
"integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
|
||||
"requires": {
|
||||
"depd": "2.0.0",
|
||||
"inherits": "2.0.4",
|
||||
"setprototypeof": "1.2.0",
|
||||
"statuses": "2.0.1",
|
||||
"toidentifier": "1.0.1"
|
||||
}
|
||||
},
|
||||
"iconv-lite": {
|
||||
"version": "0.4.24",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
|
||||
"requires": {
|
||||
"safer-buffer": ">= 2.1.2 < 3"
|
||||
}
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
||||
},
|
||||
"ipaddr.js": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
||||
"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="
|
||||
},
|
||||
"math-intrinsics": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="
|
||||
},
|
||||
"media-typer": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
||||
"integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ=="
|
||||
},
|
||||
"merge-descriptors": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
|
||||
"integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ=="
|
||||
},
|
||||
"methods": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
|
||||
"integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w=="
|
||||
},
|
||||
"mime": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
|
||||
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="
|
||||
},
|
||||
"mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="
|
||||
},
|
||||
"mime-types": {
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"requires": {
|
||||
"mime-db": "1.52.0"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
|
||||
},
|
||||
"negotiator": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
|
||||
"integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="
|
||||
},
|
||||
"object-inspect": {
|
||||
"version": "1.13.4",
|
||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
|
||||
"integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="
|
||||
},
|
||||
"on-finished": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
|
||||
"integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
|
||||
"requires": {
|
||||
"ee-first": "1.1.1"
|
||||
}
|
||||
},
|
||||
"parseurl": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
||||
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="
|
||||
},
|
||||
"path-to-regexp": {
|
||||
"version": "0.1.12",
|
||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
|
||||
"integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ=="
|
||||
},
|
||||
"proxy-addr": {
|
||||
"version": "2.0.7",
|
||||
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
|
||||
"integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
|
||||
"requires": {
|
||||
"forwarded": "0.2.0",
|
||||
"ipaddr.js": "1.9.1"
|
||||
}
|
||||
},
|
||||
"proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
|
||||
},
|
||||
"qs": {
|
||||
"version": "6.13.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
|
||||
"integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
|
||||
"requires": {
|
||||
"side-channel": "^1.0.6"
|
||||
}
|
||||
},
|
||||
"range-parser": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
||||
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="
|
||||
},
|
||||
"raw-body": {
|
||||
"version": "2.5.2",
|
||||
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
|
||||
"integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
|
||||
"requires": {
|
||||
"bytes": "3.1.2",
|
||||
"http-errors": "2.0.0",
|
||||
"iconv-lite": "0.4.24",
|
||||
"unpipe": "1.0.0"
|
||||
}
|
||||
},
|
||||
"safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
|
||||
},
|
||||
"safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||
},
|
||||
"send": {
|
||||
"version": "0.19.0",
|
||||
"resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",
|
||||
"integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==",
|
||||
"requires": {
|
||||
"debug": "2.6.9",
|
||||
"depd": "2.0.0",
|
||||
"destroy": "1.2.0",
|
||||
"encodeurl": "~1.0.2",
|
||||
"escape-html": "~1.0.3",
|
||||
"etag": "~1.8.1",
|
||||
"fresh": "0.5.2",
|
||||
"http-errors": "2.0.0",
|
||||
"mime": "1.6.0",
|
||||
"ms": "2.1.3",
|
||||
"on-finished": "2.4.1",
|
||||
"range-parser": "~1.2.1",
|
||||
"statuses": "2.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"encodeurl": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
|
||||
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w=="
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"serve-static": {
|
||||
"version": "1.16.2",
|
||||
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
|
||||
"integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==",
|
||||
"requires": {
|
||||
"encodeurl": "~2.0.0",
|
||||
"escape-html": "~1.0.3",
|
||||
"parseurl": "~1.3.3",
|
||||
"send": "0.19.0"
|
||||
}
|
||||
},
|
||||
"setprototypeof": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
|
||||
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
|
||||
},
|
||||
"side-channel": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
|
||||
"integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
|
||||
"requires": {
|
||||
"es-errors": "^1.3.0",
|
||||
"object-inspect": "^1.13.3",
|
||||
"side-channel-list": "^1.0.0",
|
||||
"side-channel-map": "^1.0.1",
|
||||
"side-channel-weakmap": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"side-channel-list": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
|
||||
"integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
|
||||
"requires": {
|
||||
"es-errors": "^1.3.0",
|
||||
"object-inspect": "^1.13.3"
|
||||
}
|
||||
},
|
||||
"side-channel-map": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
|
||||
"integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
|
||||
"requires": {
|
||||
"call-bound": "^1.0.2",
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.5",
|
||||
"object-inspect": "^1.13.3"
|
||||
}
|
||||
},
|
||||
"side-channel-weakmap": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
|
||||
"integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
|
||||
"requires": {
|
||||
"call-bound": "^1.0.2",
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.5",
|
||||
"object-inspect": "^1.13.3",
|
||||
"side-channel-map": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"statuses": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
|
||||
"integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="
|
||||
},
|
||||
"toidentifier": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
|
||||
"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="
|
||||
},
|
||||
"type-is": {
|
||||
"version": "1.6.18",
|
||||
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
|
||||
"integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
|
||||
"requires": {
|
||||
"media-typer": "0.3.0",
|
||||
"mime-types": "~2.1.24"
|
||||
}
|
||||
},
|
||||
"unpipe": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
||||
"integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="
|
||||
},
|
||||
"utils-merge": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
|
||||
"integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA=="
|
||||
},
|
||||
"vary": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
{
|
||||
"name": "fairy-chess-server",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "node index.js",
|
||||
"test": "node test.js"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"axios": "^1.7.9",
|
||||
"express": "^4.21.2",
|
||||
"ffish": "^0.7.7"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,157 +0,0 @@
|
|||
import axios from 'axios';
|
||||
|
||||
const baseURL = 'http://localhost:27531';
|
||||
|
||||
async function delay(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
async function testServer() {
|
||||
try {
|
||||
|
||||
await axios.post(`${baseURL}/shutdown`);
|
||||
return
|
||||
// Test 1: Health check
|
||||
console.log('\nTest 1: Health Check');
|
||||
const health = await axios.get(`${baseURL}/health`);
|
||||
console.log('Health status:', health.data);
|
||||
|
||||
// Wait for engine initialization
|
||||
await delay(2000);
|
||||
// Test 2: Validate FEN
|
||||
console.log('\nTest 1.5: Validate MOVE');
|
||||
const moveVal = await axios.post(`${baseURL}/move`, {
|
||||
move: '@a6'
|
||||
});
|
||||
console.log('MOVE validation:', moveVal);
|
||||
await delay(2000);
|
||||
|
||||
|
||||
// Test 2: Validate FEN
|
||||
console.log('\nTest 2: Validate FEN');
|
||||
const startPos = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1';
|
||||
const validation = await axios.post(`${baseURL}/validate`, {
|
||||
fen: startPos
|
||||
});
|
||||
console.log('FEN validation:', validation.data);
|
||||
|
||||
// Test 3: Create new game
|
||||
console.log('\nTest 3: Create New Game');
|
||||
const newGame = await axios.post(`${baseURL}/new`, {
|
||||
variant: 'chess'
|
||||
});
|
||||
console.log('New game created:', newGame.data);
|
||||
|
||||
// Test 4: Set position
|
||||
console.log('\nTest 4: Set Position');
|
||||
const position = await axios.post(`${baseURL}/position`, {
|
||||
fen: startPos,
|
||||
variant: 'chess'
|
||||
});
|
||||
console.log('Position set:', position.data);
|
||||
|
||||
// Test 5: Get engine move from starting position
|
||||
console.log('\nTest 5: Get Engine Move');
|
||||
const engineMove = await axios.post(`${baseURL}/enginemove`, {
|
||||
depth: 15,
|
||||
movetime: 1000
|
||||
});
|
||||
console.log('Engine move:', engineMove.data);
|
||||
|
||||
// Test 6: Make a specific move
|
||||
console.log('\nTest 6: Make Move (UCI)');
|
||||
const moveUCI = await axios.post(`${baseURL}/move`, {
|
||||
move: 'e2e4',
|
||||
notation: 'uci'
|
||||
});
|
||||
console.log('UCI move made:', moveUCI.data);
|
||||
|
||||
// Test 7: Make move using SAN notation
|
||||
console.log('\nTest 7: Make Move (SAN)');
|
||||
const moveSAN = await axios.post(`${baseURL}/move`, {
|
||||
move: 'Nf6',
|
||||
notation: 'san'
|
||||
});
|
||||
console.log('SAN move made:', moveSAN.data);
|
||||
|
||||
// Test 8: Get current state
|
||||
console.log('\nTest 8: Get Current State');
|
||||
const state = await axios.get(`${baseURL}/state`);
|
||||
console.log('Current state:', state.data);
|
||||
|
||||
// Test 9: Position Analysis
|
||||
console.log('\nTest 9: Position Analysis');
|
||||
const analysis = await axios.post(`${baseURL}/analyze`, {
|
||||
depth: 15,
|
||||
movetime: 1000
|
||||
});
|
||||
console.log('Position analysis:', analysis.data);
|
||||
|
||||
// Test 10: Test variant chess
|
||||
console.log('\nTest 10: Test Chess Variant');
|
||||
const variantGame = await axios.post(`${baseURL}/new`, {
|
||||
variant: 'crazyhouse'
|
||||
});
|
||||
console.log('Crazyhouse game created:', variantGame.data);
|
||||
|
||||
// Test 11: Test invalid FEN
|
||||
console.log('\nTest 11: Test Invalid FEN');
|
||||
try {
|
||||
await axios.post(`${baseURL}/position`, {
|
||||
fen: 'invalid fen string',
|
||||
variant: 'chess'
|
||||
});
|
||||
} catch (error) {
|
||||
console.log('Invalid FEN correctly rejected:', error.response.data);
|
||||
}
|
||||
|
||||
// Test 12: Test invalid move
|
||||
console.log('\nTest 12: Test Invalid Move');
|
||||
try {
|
||||
await axios.post(`${baseURL}/move`, {
|
||||
move: 'e2e5', // Invalid move
|
||||
notation: 'uci'
|
||||
});
|
||||
} catch (error) {
|
||||
console.log('Invalid move correctly rejected:', error.response.data);
|
||||
}
|
||||
|
||||
// Test 13: Engine Analysis with Different Settings
|
||||
console.log('\nTest 13: Engine Analysis with Custom Settings');
|
||||
const customAnalysis = await axios.post(`${baseURL}/analyze`, {
|
||||
depth: 20,
|
||||
movetime: 2000
|
||||
});
|
||||
console.log('Custom analysis:', customAnalysis.data);
|
||||
|
||||
// Test 14: Final State Check
|
||||
console.log('\nTest 14: State Check');
|
||||
const finalHealth = await axios.get(`${baseURL}/health`);
|
||||
console.log('Final server status:', {
|
||||
engineReady: finalHealth.data.engineReady,
|
||||
status: finalHealth.data.status
|
||||
});
|
||||
console.log('\nTest 15: SHUTDOWN');
|
||||
await axios.post(`${baseURL}/shutdown`);
|
||||
|
||||
|
||||
console.log('\nAll tests completed successfully!');
|
||||
|
||||
} catch (error) {
|
||||
console.error('Test failed:', error.message);
|
||||
if (error.response) {
|
||||
console.error('Error details:', {
|
||||
status: error.response.status,
|
||||
data: error.response.data
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Run tests
|
||||
console.log('Starting Fairy-Chess server tests...');
|
||||
testServer().then(() => {
|
||||
console.log('Test suite completed.');
|
||||
}).catch(err => {
|
||||
console.error('Test suite failed:', err);
|
||||
});
|
||||
|
|
@ -1,87 +0,0 @@
|
|||
import fs from 'fs'
|
||||
|
||||
const pieceSetups = [
|
||||
"nk", // 2 cells
|
||||
"nkr", // 3 cells
|
||||
"nkbr", // 4 cells
|
||||
"rnkbr", // 5 cells
|
||||
"rbnkqr", // 6 cells
|
||||
"rnbkqnr", // 7 cells
|
||||
"rnbqkbnr", // 8 cells (standard chess)
|
||||
"rbnqknbnr", // 9 cells
|
||||
"rnbnqknbnr", // 10 cells
|
||||
"rnbnqknbnnr", // 11 cells
|
||||
"rnnbnqknbnnr" // 12 cells
|
||||
];
|
||||
|
||||
const files = "abcdefghijkl";
|
||||
|
||||
function generateVariant(width, height) {
|
||||
if (width < 2 || width > 12) {
|
||||
console.error("Width must be between 2 and 12");
|
||||
return "";
|
||||
}
|
||||
|
||||
if (height < 6 || height > 12) {
|
||||
console.error("Height must be between 6 and 12");
|
||||
return "";
|
||||
}
|
||||
|
||||
const pieceSetup = pieceSetups[width - 2];
|
||||
|
||||
|
||||
const emptyRanks = height - 4;
|
||||
|
||||
|
||||
const whitePieces = pieceSetup.toUpperCase();
|
||||
const blackPieces = pieceSetup.toLowerCase();
|
||||
|
||||
const whitePawns = "P".repeat(width);
|
||||
const blackPawns = "p".repeat(width);
|
||||
const emptyRank = width.toString();
|
||||
|
||||
let fenParts = [blackPieces, blackPawns];
|
||||
|
||||
for (let i = 0; i < emptyRanks; i++) {
|
||||
fenParts.push(emptyRank);
|
||||
}
|
||||
|
||||
fenParts.push(whitePawns, whitePieces);
|
||||
const fen = fenParts.join("/") + " w - - 0 1";
|
||||
|
||||
const maxFile = files[width - 1];
|
||||
|
||||
return `[chessbuilder${width}x${height}:chess]
|
||||
pieceToCharTable = PNBRQK..*@...........pnbrqk..*@...........
|
||||
maxRank = ${height}
|
||||
maxFile = ${maxFile}
|
||||
startFen = ${fen}
|
||||
|
||||
walltype = * # Duck wall
|
||||
walltype = @ # Stone wall
|
||||
|
||||
wallingRule = static
|
||||
wallOrMove = false
|
||||
mobilityRegion = *:@
|
||||
prohibitedMove = *@
|
||||
`;
|
||||
}
|
||||
function generateAllVariants() {
|
||||
let allVariants = "";
|
||||
let count = 0;
|
||||
|
||||
for (let height = 6; height <= 12; height++) {
|
||||
for (let width = 2; width <= 12; width++) {
|
||||
const variant = generateVariant(width, height);
|
||||
allVariants += variant + "\n";
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
fs.writeFileSync('chessbuilder_variants.ini', allVariants);
|
||||
console.log(`Generated ${count} variants and saved to chessbuilder_variants.ini`);
|
||||
|
||||
return allVariants;
|
||||
}
|
||||
|
||||
generateAllVariants();
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
[gd_resource type="Theme" format=3 uid="uid://btgbiqdc4kf15"]
|
||||
|
||||
[resource]
|
||||
default_font_size = 49
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
[gd_resource type="Theme" format=3 uid="uid://cuq0xndnachqb"]
|
||||
|
||||
[resource]
|
||||
default_font_size = 80
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 9.7 KiB |
|
|
@ -1,34 +0,0 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bexpni52h8527"
|
||||
path="res://.godot/imported/characters.png-aa4b087f4ba916c3768cf8d9d020e5a0.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Assets/main_menu/characters.png"
|
||||
dest_files=["res://.godot/imported/characters.png-aa4b087f4ba916c3768cf8d9d020e5a0.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 2.2 KiB |
|
|
@ -1,34 +0,0 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bdb3grojgjtaq"
|
||||
path="res://.godot/imported/icon.png-a1f010172d24ada42d71428b39fa8e05.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Assets/main_menu/icon.png"
|
||||
dest_files=["res://.godot/imported/icon.png-a1f010172d24ada42d71428b39fa8e05.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.6 KiB |
|
|
@ -1,34 +0,0 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bn0offg4w11w4"
|
||||
path="res://.godot/imported/label_continue.png-73ecf71c03ce72f5729846d86d279818.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Assets/main_menu/label_continue.png"
|
||||
dest_files=["res://.godot/imported/label_continue.png-73ecf71c03ce72f5729846d86d279818.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.6 KiB |
|
|
@ -1,34 +0,0 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://b8khh5b1iwic1"
|
||||
path="res://.godot/imported/label_options.png-19b15ef319e033f5146c10542dd5f109.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Assets/main_menu/label_options.png"
|
||||
dest_files=["res://.godot/imported/label_options.png-19b15ef319e033f5146c10542dd5f109.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
|
|
@ -12,15 +12,15 @@ stateDiagram
|
|||
HandSetup --> DrawPhase
|
||||
DrawPhase --> ResolvePersistentEffects : Draw/Discard
|
||||
|
||||
ResolvePersistentEffects --> PreMovePhase : Update Durations
|
||||
ResolvePersistentEffects --> ApplyTileEffects : Update Durations
|
||||
ApplyTileEffects --> PreMovePhase
|
||||
|
||||
PreMovePhase --> AttachCards : Play Cards
|
||||
AttachCards --> ApplyCardEffects
|
||||
ApplyCardEffects --> Movement
|
||||
|
||||
Movement --> PostMovePhase
|
||||
PostMovePhase --> ApplyTileEffects : Resolve Move Effects
|
||||
ApplyTileEffects --> EvaluatePosition
|
||||
PostMovePhase --> EvaluatePosition : Resolve Move Effects
|
||||
|
||||
state EvaluatePosition {
|
||||
[*] --> CheckStatus
|
||||
|
|
|
|||
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"version": "0.0.1",
|
||||
"title": "ChessBuilder",
|
||||
"developer": "Comet Studios",
|
||||
"release_date": "2025-01-15"
|
||||
}
|
||||
|
|
@ -19,15 +19,11 @@ var effectType: EffectType
|
|||
var remaining_turns: int = 0
|
||||
var unitWhitelist: Array[String] = [] # List of piece types this card can be attached to
|
||||
var id: String = Utils.generate_guid()
|
||||
var stored_board_flow = null
|
||||
var stored_game_state = null
|
||||
var is_default: bool = false
|
||||
|
||||
func _init():
|
||||
remaining_turns = duration
|
||||
# print(id)
|
||||
func reset():
|
||||
remaining_turns = duration
|
||||
|
||||
func can_attach_to_piece(piece: Pawn) -> bool:
|
||||
# print(unitWhitelist, " | ", piece.name , " | ", unitWhitelist.has(piece.name))
|
||||
if unitWhitelist.is_empty():
|
||||
|
|
@ -41,8 +37,6 @@ func apply_effect(target_piece = null, board_flow = null, game_state = null):
|
|||
|
||||
remaining_turns = duration
|
||||
attached_piece = target_piece
|
||||
stored_board_flow = board_flow
|
||||
stored_game_state = game_state
|
||||
|
||||
match rank:
|
||||
Rank.RANK_0: burned = true
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
uid://eqadab7yd0qq
|
||||
|
|
@ -1,98 +1,65 @@
|
|||
class_name CardDisplay extends Control
|
||||
|
||||
# Base card dimensions
|
||||
const BASE_CARD_WIDTH = 150
|
||||
const BASE_CARD_HEIGHT = 250
|
||||
const CARD_WIDTH = 150
|
||||
const CARD_HEIGHT = 250
|
||||
const CARD_MARGIN = 10
|
||||
const MAX_HAND_WIDTH_RATIO = 0.7 # Maximum portion of screen width to use for hand
|
||||
|
||||
var cardDisplays = [] # Array of {panel: Node, card: Card}
|
||||
var cardDisplays = []
|
||||
var selectedCard = null
|
||||
var container: HBoxContainer
|
||||
var cardPreviewScene = preload("res://card_preview_panel.tscn")
|
||||
var currently_hovered_card = null
|
||||
# Preview card panel instance for hovering
|
||||
var hoveredPreview: CardPreviewPanel = null
|
||||
var isPreviewVisible = false
|
||||
|
||||
func _ready():
|
||||
# Create the container for the hand
|
||||
# Create the container first
|
||||
container = HBoxContainer.new()
|
||||
container.name = "Hand"
|
||||
container.position = Vector2(10, 500)
|
||||
container.size_flags_horizontal = SIZE_EXPAND_FILL
|
||||
add_child(container)
|
||||
|
||||
# Create a hover preview panel that will follow the mouse
|
||||
hoveredPreview = cardPreviewScene.instantiate()
|
||||
hoveredPreview.visible = false
|
||||
add_child(hoveredPreview)
|
||||
|
||||
func update_hand(hand: Array):
|
||||
clear_cards()
|
||||
|
||||
# Calculate card sizes based on hand size and screen width
|
||||
var card_size = calculate_card_size(hand.size())
|
||||
|
||||
for card in hand:
|
||||
add_card_display(card, card_size)
|
||||
add_card_display(card)
|
||||
|
||||
func calculate_card_size(hand_size: int) -> Vector2:
|
||||
# Get screen dimensions
|
||||
var screen_width = get_viewport_rect().size.x
|
||||
var screen_height = get_viewport_rect().size.y
|
||||
func add_card_display(card):
|
||||
var cardPanel = PanelContainer.new()
|
||||
cardPanel.custom_minimum_size = Vector2(CARD_WIDTH, CARD_HEIGHT)
|
||||
|
||||
var max_hand_width = screen_width * MAX_HAND_WIDTH_RATIO
|
||||
var width_per_card = max_hand_width / max(hand_size, 1)
|
||||
var vbox = VBoxContainer.new()
|
||||
vbox.set_meta("card_id", card.id)
|
||||
cardPanel.add_child(vbox)
|
||||
|
||||
# Ensure minimum size and preserve aspect ratio
|
||||
var card_width = clamp(width_per_card - CARD_MARGIN, 80, BASE_CARD_WIDTH)
|
||||
var card_height = card_width * (BASE_CARD_HEIGHT / BASE_CARD_WIDTH)
|
||||
var nameLabel = Label.new()
|
||||
nameLabel.text = card.cardName
|
||||
nameLabel.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
vbox.add_child(nameLabel)
|
||||
|
||||
# Ensure cards don't extend off the bottom of the screen
|
||||
var max_height = screen_height * 0.4 # Use at most 40% of screen height
|
||||
if card_height > max_height:
|
||||
card_height = max_height
|
||||
card_width = card_height * (BASE_CARD_WIDTH / BASE_CARD_HEIGHT)
|
||||
var rankLabel = Label.new()
|
||||
rankLabel.text = "Rank " + str(card.rank)
|
||||
rankLabel.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
vbox.add_child(rankLabel)
|
||||
|
||||
return Vector2(card_width, card_height)
|
||||
var descLabel = Label.new()
|
||||
descLabel.text = card.description
|
||||
descLabel.autowrap_mode = TextServer.AUTOWRAP_WORD
|
||||
descLabel.custom_minimum_size = Vector2(CARD_WIDTH - 10, 0)
|
||||
vbox.add_child(descLabel)
|
||||
|
||||
var unitWhitelistLabel = Label.new()
|
||||
unitWhitelistLabel.text = ", ".join(card.unitWhitelist)
|
||||
unitWhitelistLabel.autowrap_mode = TextServer.AUTOWRAP_WORD
|
||||
unitWhitelistLabel.custom_minimum_size = Vector2(CARD_WIDTH - 10, 0)
|
||||
vbox.add_child(unitWhitelistLabel)
|
||||
|
||||
func add_card_display(card, card_size: Vector2 = Vector2(BASE_CARD_WIDTH, BASE_CARD_HEIGHT)):
|
||||
# Create a container for the card
|
||||
var cardContainer = PanelContainer.new()
|
||||
cardContainer.custom_minimum_size = card_size
|
||||
var idLabel = Label.new()
|
||||
idLabel.text = card.id.substr(card.id.length() - 8, -1)
|
||||
idLabel.autowrap_mode = true
|
||||
idLabel.custom_minimum_size = Vector2(CARD_WIDTH - 10, 0)
|
||||
vbox.add_child(idLabel)
|
||||
|
||||
cardPanel.gui_input.connect(func(event): _on_card_clicked(event, card))
|
||||
|
||||
# Create a stylebox for selection highlighting
|
||||
var style = StyleBoxFlat.new()
|
||||
style.bg_color = Utils.DARK_CELL
|
||||
style.corner_radius_top_left = 5
|
||||
style.corner_radius_top_right = 5
|
||||
style.corner_radius_bottom_left = 5
|
||||
style.corner_radius_bottom_right = 5
|
||||
cardContainer.add_theme_stylebox_override("panel", style)
|
||||
|
||||
# Create compact card display
|
||||
var compact_card = CompactCardDisplay.new()
|
||||
compact_card.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
compact_card.mouse_filter = Control.MOUSE_FILTER_PASS
|
||||
|
||||
# Add to container
|
||||
cardContainer.add_child(compact_card)
|
||||
compact_card.set_card(card)
|
||||
|
||||
# Connect signals
|
||||
cardContainer.gui_input.connect(func(event): _on_card_clicked(event, card))
|
||||
cardContainer.mouse_entered.connect(func(): _on_card_mouse_entered(card))
|
||||
cardContainer.mouse_exited.connect(func(): _on_card_mouse_exited())
|
||||
|
||||
# Add to hand container
|
||||
container.add_child(cardContainer)
|
||||
|
||||
# Add to display list
|
||||
cardDisplays.append({
|
||||
"panel": cardContainer,
|
||||
"card": card
|
||||
})
|
||||
cardDisplays.append(cardPanel)
|
||||
container.add_child(cardPanel)
|
||||
|
||||
func _on_card_clicked(event: InputEvent, card: Card):
|
||||
if event is InputEventMouseButton and event.pressed:
|
||||
|
|
@ -103,70 +70,29 @@ func _on_card_clicked(event: InputEvent, card: Card):
|
|||
selectedCard = null
|
||||
highlight_selectedCard(selectedCard)
|
||||
|
||||
func _on_card_mouse_entered( card: Card):
|
||||
# Show card preview when hovering
|
||||
# print("_on_card_mouse_entered")
|
||||
currently_hovered_card = card
|
||||
hoveredPreview.preview_card(card)
|
||||
hoveredPreview.visible = true
|
||||
|
||||
# Position it above the hand area
|
||||
var mouse_pos = get_viewport().get_mouse_position()
|
||||
hoveredPreview.position = Vector2(mouse_pos.x - hoveredPreview.size.x/2, mouse_pos.y - hoveredPreview.size.y - 20)
|
||||
|
||||
# Make sure it's visible within screen bounds
|
||||
hoveredPreview.position.x = clamp(hoveredPreview.position.x, 10, get_viewport_rect().size.x - hoveredPreview.size.x - 10)
|
||||
hoveredPreview.position.y = clamp(hoveredPreview.position.y, 10, get_viewport_rect().size.y - hoveredPreview.size.y - 10)
|
||||
|
||||
isPreviewVisible = true
|
||||
|
||||
func _on_card_mouse_exited():
|
||||
# Only hide the preview after a delay and if we're not hovering over a new card
|
||||
var exited_card = currently_hovered_card
|
||||
currently_hovered_card = null
|
||||
|
||||
# Wait a bit before hiding
|
||||
await get_tree().create_timer(0.2).timeout
|
||||
|
||||
# Only hide if we're not hovering over a new card
|
||||
if exited_card == currently_hovered_card or currently_hovered_card == null:
|
||||
hoveredPreview.visible = false
|
||||
isPreviewVisible = false
|
||||
|
||||
func _process(delta):
|
||||
# If preview is visible, update its position to follow the mouse
|
||||
if isPreviewVisible:
|
||||
var mouse_pos = get_viewport().get_mouse_position()
|
||||
hoveredPreview.position = Vector2(mouse_pos.x - hoveredPreview.size.x/2, mouse_pos.y - hoveredPreview.size.y - 20)
|
||||
hoveredPreview.position.x = clamp(hoveredPreview.position.x, 10, get_viewport_rect().size.x - hoveredPreview.size.x - 10)
|
||||
hoveredPreview.position.y = clamp(hoveredPreview.position.y, 10, get_viewport_rect().size.y - hoveredPreview.size.y - 10)
|
||||
|
||||
func highlight_selectedCard(selected: Card) -> void:
|
||||
for card_data in cardDisplays:
|
||||
var panel = card_data.panel
|
||||
var compact_card = panel.get_child(0) as CompactCardDisplay
|
||||
for display in cardDisplays:
|
||||
var style = StyleBoxFlat.new()
|
||||
style.bg_color = Color(0.2, 0.2, 0.2, 1) # Default color
|
||||
|
||||
if selected && card_data.card.id == selected.id:
|
||||
var vbox = display.get_child(0)
|
||||
if selected && vbox.get_meta("card_id") == selected.id:
|
||||
style.bg_color = Color(0.4, 0.6, 0.4, 1) # Selected color
|
||||
|
||||
# Apply highlighting to the CompactCardDisplay
|
||||
if compact_card:
|
||||
compact_card.set_selected(true)
|
||||
else:
|
||||
# Reset highlighting
|
||||
if compact_card:
|
||||
compact_card.set_selected(false)
|
||||
display.add_theme_stylebox_override("panel", style)
|
||||
|
||||
func get_card_by_uuid(uuid: String) -> Card:
|
||||
for card_data in cardDisplays:
|
||||
if card_data.card.id == uuid:
|
||||
return card_data.card
|
||||
for display in cardDisplays:
|
||||
var vbox = display.get_child(0)
|
||||
if vbox.get_meta("card_id") == uuid:
|
||||
return selectedCard
|
||||
return null
|
||||
|
||||
func getSelectedCard() -> Card:
|
||||
return selectedCard
|
||||
|
||||
func clear_cards():
|
||||
for card_data in cardDisplays:
|
||||
card_data.panel.queue_free()
|
||||
for display in cardDisplays:
|
||||
display.queue_free()
|
||||
cardDisplays.clear()
|
||||
selectedCard = null
|
||||
selectedCard = null
|
||||
|
|
@ -1 +0,0 @@
|
|||
uid://dgsj76xih6r3y
|
||||
|
|
@ -6,55 +6,88 @@ const PREVIEW_HEIGHT = 200
|
|||
const SCREEN_MARGIN = 20
|
||||
|
||||
var currentCard: Card = null
|
||||
var previewPanel: CardPreviewPanel
|
||||
var previewPanel: PanelContainer
|
||||
|
||||
var movesRemainingLabel: Label
|
||||
func _init() -> void:
|
||||
|
||||
|
||||
previewPanel = preload("res://card_preview_panel.tscn").instantiate()
|
||||
previewPanel = PanelContainer.new()
|
||||
previewPanel.custom_minimum_size = Vector2(PREVIEW_WIDTH, PREVIEW_HEIGHT)
|
||||
|
||||
|
||||
position = Vector2(0, 0)
|
||||
|
||||
var vbox = VBoxContainer.new()
|
||||
previewPanel.add_child(vbox)
|
||||
|
||||
var style = StyleBoxFlat.new()
|
||||
style.bg_color = Color(0.2, 0.2, 0.2, 0.9)
|
||||
style.corner_radius_top_left = 10
|
||||
style.corner_radius_top_right = 10
|
||||
style.corner_radius_bottom_left = 10
|
||||
style.corner_radius_bottom_right = 10
|
||||
previewPanel.add_theme_stylebox_override("panel", style)
|
||||
|
||||
previewPanel.hide()
|
||||
|
||||
# Add the preview panel as a child
|
||||
add_child(previewPanel)
|
||||
|
||||
# Add a custom label for moves remaining that we'll show/hide as needed
|
||||
movesRemainingLabel = Label.new()
|
||||
movesRemainingLabel.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
movesRemainingLabel.add_theme_font_size_override("font_size", 14)
|
||||
movesRemainingLabel.visible = false
|
||||
|
||||
# Add the label to the preview panel's content
|
||||
var content = previewPanel.get_node("CardContent")
|
||||
if content:
|
||||
content.add_child(movesRemainingLabel)
|
||||
|
||||
func show_card_preview(card: Card) -> void:
|
||||
if card == null:
|
||||
hide_preview()
|
||||
previewPanel.hide()
|
||||
currentCard = null
|
||||
return
|
||||
|
||||
currentCard = card
|
||||
var vbox = previewPanel.get_child(0)
|
||||
|
||||
# Use the preview_card method from CardPreviewPanel
|
||||
previewPanel.preview_card(card)
|
||||
for child in vbox.get_children():
|
||||
child.queue_free()
|
||||
|
||||
# Since we're showing a new card, hide the moves remaining label
|
||||
movesRemainingLabel.visible = false
|
||||
var nameLabel = Label.new()
|
||||
nameLabel.text = card.cardName
|
||||
nameLabel.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
nameLabel.add_theme_font_size_override("font_size", 18)
|
||||
vbox.add_child(nameLabel)
|
||||
|
||||
var rankLabel = Label.new()
|
||||
rankLabel.text = "Rank " + str(card.rank)
|
||||
rankLabel.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
vbox.add_child(rankLabel)
|
||||
|
||||
var idLabel = Label.new()
|
||||
idLabel.text = "ID: " + card.id.substr(card.id.length() - 8, -1)
|
||||
idLabel.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
vbox.add_child(idLabel)
|
||||
|
||||
var durationLabel = Label.new()
|
||||
durationLabel.text = str(card.remaining_turns) + " turns remaining"
|
||||
durationLabel.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
vbox.add_child(durationLabel)
|
||||
|
||||
var descLabel = Label.new()
|
||||
descLabel.text = card.description
|
||||
descLabel.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
descLabel.autowrap_mode = TextServer.AUTOWRAP_WORD
|
||||
descLabel.custom_minimum_size = Vector2(PREVIEW_WIDTH - 20, 0)
|
||||
vbox.add_child(descLabel)
|
||||
|
||||
previewPanel.show()
|
||||
|
||||
func hide_preview() -> void:
|
||||
previewPanel.hide_preview()
|
||||
previewPanel.hide()
|
||||
currentCard = null
|
||||
movesRemainingLabel.visible = false
|
||||
|
||||
func update_moves_remaining(moves: int) -> void:
|
||||
if currentCard == null:
|
||||
return
|
||||
|
||||
var vbox = previewPanel.get_child(0)
|
||||
var movesLabel = Label.new()
|
||||
movesLabel.text = "Moves remaining this turn: " + str(moves)
|
||||
movesLabel.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
|
||||
# Update and show the moves remaining label
|
||||
movesRemainingLabel.text = "Moves remaining this turn: " + str(moves)
|
||||
movesRemainingLabel.visible = true
|
||||
# Remove old moves label if it exists
|
||||
for child in vbox.get_children():
|
||||
if child.text.begins_with("Moves remaining this turn:"):
|
||||
child.queue_free()
|
||||
|
||||
vbox.add_child(movesLabel)
|
||||
|
|
@ -1 +0,0 @@
|
|||
uid://p4ahefeutov0
|
||||
|
|
@ -1,84 +0,0 @@
|
|||
extends Panel
|
||||
class_name CardPreviewPanel
|
||||
|
||||
# Node references
|
||||
@onready var card_name_label = $CardContent/CardNameLabel
|
||||
@onready var rank_label = $CardContent/RankLabel
|
||||
@onready var description_label = $CardContent/DescriptionLabel
|
||||
@onready var unit_list_label = $CardContent/UnitListLabel
|
||||
@onready var effect_type_label = $CardContent/EffectTypeLabel
|
||||
@onready var duration_label = $CardContent/DurationLabel
|
||||
|
||||
# Card rank colors (same as other card components)
|
||||
var rank_colors = {
|
||||
Card.Rank.RANK_0: Color(0.9, 0.1, 0.1, 1.0), # Red
|
||||
Card.Rank.RANK_1: Color(0.9, 0.6, 0.1, 1.0), # Orange
|
||||
Card.Rank.RANK_2: Color(0.1, 0.7, 0.1, 1.0), # Green
|
||||
Card.Rank.RANK_3: Color(0.1, 0.7, 0.9, 1.0) # Blue
|
||||
}
|
||||
|
||||
# Effect type descriptions
|
||||
var effect_type_names = {
|
||||
Card.EffectType.MOVEMENT_MODIFIER: "Movement Modifier",
|
||||
Card.EffectType.BOARD_EFFECT: "Board Effect",
|
||||
Card.EffectType.PIECE_EFFECT: "Piece Effect",
|
||||
Card.EffectType.SPECIAL_ACTION: "Special Action"
|
||||
}
|
||||
|
||||
func _ready():
|
||||
# Hide the preview initially
|
||||
visible = false
|
||||
|
||||
func preview_card(card: Card):
|
||||
if !card:
|
||||
hide_preview()
|
||||
return
|
||||
# if !is_inside_tree():
|
||||
# await ready
|
||||
# Update all card information
|
||||
card_name_label.text = card.cardName
|
||||
|
||||
# Set rank information
|
||||
var rank_text = "Rank " + str(card.rank)
|
||||
match card.rank:
|
||||
Card.Rank.RANK_0:
|
||||
rank_text += " (One-time use, removed from deck)"
|
||||
Card.Rank.RANK_1:
|
||||
rank_text += " (Once per match)"
|
||||
Card.Rank.RANK_2:
|
||||
rank_text += " (Discarded after use)"
|
||||
Card.Rank.RANK_3:
|
||||
rank_text += " (Reused)"
|
||||
rank_label.text = rank_text
|
||||
|
||||
# Set rank color
|
||||
if card.rank in rank_colors:
|
||||
rank_label.add_theme_color_override("font_color", rank_colors[card.rank])
|
||||
card_name_label.add_theme_color_override("font_color", rank_colors[card.rank])
|
||||
|
||||
# Set description
|
||||
description_label.text = card.description
|
||||
|
||||
# Set unit whitelist
|
||||
if card.unitWhitelist.is_empty():
|
||||
unit_list_label.text = "Can be applied to: Any piece"
|
||||
else:
|
||||
unit_list_label.text = "Can be applied to: " + ", ".join(card.unitWhitelist)
|
||||
|
||||
# Set effect type
|
||||
if card.effectType in effect_type_names:
|
||||
effect_type_label.text = "Effect Type: " + effect_type_names[card.effectType]
|
||||
else:
|
||||
effect_type_label.text = "Effect Type: Unknown"
|
||||
|
||||
# Set duration
|
||||
if card.duration > 0:
|
||||
duration_label.text = "Duration: " + str(card.duration) + " turns"
|
||||
else:
|
||||
duration_label.text = "Duration: Instant"
|
||||
|
||||
# Show the preview
|
||||
visible = true
|
||||
|
||||
func hide_preview():
|
||||
visible = false
|
||||
|
|
@ -1 +0,0 @@
|
|||
uid://2xcreiq6lhe2
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
extends Control
|
||||
class_name CardBankItem
|
||||
|
||||
signal card_selected(card_item, card)
|
||||
|
||||
var current_card = null
|
||||
|
||||
@onready var rank_label = $RankLabel
|
||||
@onready var card_name_label = $CardNameLabel
|
||||
@onready var card_frame = $CardFrame
|
||||
|
||||
# Card rank colors (matching CardSlot colors)
|
||||
var rank_colors = {
|
||||
Card.Rank.RANK_0: Color(0.9, 0.1, 0.1, 1.0), # Red
|
||||
Card.Rank.RANK_1: Color(0.9, 0.6, 0.1, 1.0), # Orange
|
||||
Card.Rank.RANK_2: Color(0.1, 0.7, 0.1, 1.0), # Green
|
||||
Card.Rank.RANK_3: Color(0.1, 0.7, 0.9, 1.0) # Blue
|
||||
}
|
||||
|
||||
func _ready():
|
||||
# Connect signals for interaction
|
||||
custom_minimum_size = Vector2(0, 40) # Ensure it has enough height
|
||||
size_flags_horizontal = SIZE_EXPAND_FILL
|
||||
mouse_filter = Control.MOUSE_FILTER_STOP
|
||||
if card_frame:
|
||||
card_frame.mouse_filter = Control.MOUSE_FILTER_PASS
|
||||
if rank_label:
|
||||
rank_label.mouse_filter = Control.MOUSE_FILTER_PASS
|
||||
if card_name_label:
|
||||
card_name_label.mouse_filter = Control.MOUSE_FILTER_PASS
|
||||
|
||||
# Connect GUI input signal
|
||||
gui_input.connect(_on_gui_input)
|
||||
|
||||
func set_card(card):
|
||||
current_card = card
|
||||
update_appearance()
|
||||
|
||||
func update_appearance():
|
||||
if current_card:
|
||||
# Update card details
|
||||
card_name_label.text = current_card.cardName
|
||||
rank_label.text = str(current_card.rank)
|
||||
|
||||
# Set color based on rank
|
||||
if current_card.rank in rank_colors:
|
||||
rank_label.modulate = rank_colors[current_card.rank]
|
||||
card_frame.modulate = rank_colors[current_card.rank]
|
||||
|
||||
func _on_gui_input(event):
|
||||
if event is InputEventMouseButton:
|
||||
if event.button_index == MOUSE_BUTTON_LEFT and event.pressed:
|
||||
if current_card:
|
||||
emit_signal("card_selected", self, current_card)
|
||||
|
|
@ -1 +0,0 @@
|
|||
uid://b1d17jfxagdc7
|
||||
|
|
@ -1,93 +0,0 @@
|
|||
extends Panel
|
||||
class_name CardSlot
|
||||
|
||||
signal card_selected(card_slot, card)
|
||||
|
||||
var current_card = null
|
||||
var dragging = false
|
||||
var drag_offset = Vector2()
|
||||
|
||||
@onready var card_name_label = $CardNameLabel
|
||||
@onready var card_rank_label = $RankLabel
|
||||
@onready var card_border = $CardBorder
|
||||
@onready var card_bg = $CardBackground
|
||||
|
||||
# Card rank colors
|
||||
var rank_colors = {
|
||||
Card.Rank.RANK_0: Color(0.9, 0.1, 0.1, 1.0), # Red
|
||||
Card.Rank.RANK_1: Color(0.9, 0.6, 0.1, 1.0), # Orange
|
||||
Card.Rank.RANK_2: Color(0.1, 0.7, 0.1, 1.0), # Green
|
||||
Card.Rank.RANK_3: Color(0.1, 0.7, 0.9, 1.0) # Blue
|
||||
}
|
||||
|
||||
func _ready():
|
||||
# Set up card appearance
|
||||
update_appearance()
|
||||
|
||||
# Connect signals for interaction
|
||||
mouse_filter = Control.MOUSE_FILTER_STOP
|
||||
gui_input.connect(_on_gui_input)
|
||||
if card_border:
|
||||
card_border.mouse_filter = Control.MOUSE_FILTER_PASS
|
||||
for child in get_children():
|
||||
if child is Control:
|
||||
child.mouse_filter = Control.MOUSE_FILTER_PASS
|
||||
|
||||
|
||||
func set_card(card):
|
||||
current_card = card
|
||||
update_appearance()
|
||||
|
||||
func clear():
|
||||
current_card = null
|
||||
update_appearance()
|
||||
|
||||
func has_card():
|
||||
return current_card != null
|
||||
|
||||
func update_appearance():
|
||||
if current_card:
|
||||
# Show card details
|
||||
card_name_label.text = current_card.cardName
|
||||
card_name_label.visible = true
|
||||
|
||||
# Set rank display
|
||||
card_rank_label.text = str(current_card.rank)
|
||||
card_rank_label.visible = true
|
||||
|
||||
# Set color based on rank
|
||||
if current_card.rank in rank_colors:
|
||||
card_rank_label.add_theme_color_override("font_color", rank_colors[current_card.rank])
|
||||
card_border.modulate = rank_colors[current_card.rank]
|
||||
|
||||
# Show card background
|
||||
card_bg.modulate = Color(0.2, 0.2, 0.25, 1.0)
|
||||
else:
|
||||
# Empty slot
|
||||
card_name_label.visible = false
|
||||
card_rank_label.visible = false
|
||||
card_border.modulate = Color(0.4, 0.4, 0.4, 0.5)
|
||||
card_bg.modulate = Color(0.15, 0.15, 0.15, 0.8)
|
||||
|
||||
func _on_gui_input(event):
|
||||
if event is InputEventMouseButton:
|
||||
if event.button_index == MOUSE_BUTTON_LEFT:
|
||||
if event.pressed:
|
||||
# Start drag
|
||||
if current_card:
|
||||
emit_signal("card_selected", self, current_card)
|
||||
# dragging = true
|
||||
# drag_offset = get_global_mouse_position() - global_position
|
||||
# else:
|
||||
# # End drag
|
||||
# if dragging:
|
||||
# dragging = false
|
||||
# emit_signal("card_selected", self, current_card)
|
||||
# elif current_card:
|
||||
# # Just a click, still select the card
|
||||
# emit_signal("card_selected", self, current_card)
|
||||
|
||||
func _process(delta):
|
||||
if dragging:
|
||||
# Update position while dragging
|
||||
global_position = get_global_mouse_position() - drag_offset
|
||||
|
|
@ -1 +0,0 @@
|
|||
uid://b5888fqblsco5
|
||||
|
|
@ -4,12 +4,12 @@ func _init():
|
|||
super._init()
|
||||
# id = Utils.generate_guid()
|
||||
cardName = "Double Time"
|
||||
rank = Rank.RANK_3
|
||||
rank = Rank.RANK_2
|
||||
effectType = EffectType.MOVEMENT_MODIFIER
|
||||
description = "Piece can move twice in one turn"
|
||||
duration = 2 # Lasts for 2 turns
|
||||
unitWhitelist = []
|
||||
is_default = true
|
||||
|
||||
func modify_moves() -> Dictionary:
|
||||
return {
|
||||
"extra_moves": 1,
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
uid://o16wgudelf8x
|
||||
|
|
@ -22,7 +22,7 @@ func apply_effect(target_piece = null, board_flow = null, game_state = null):
|
|||
var current_y = int(current_pos[1])
|
||||
|
||||
# Calculate target position
|
||||
var target_y = game_state.boardYSize - 1 if target_piece.Item_Color == 1 else 0
|
||||
var target_y = 7 if target_piece.Item_Color == 1 else 0
|
||||
var y_direction = 1 if target_y > current_y else -1
|
||||
|
||||
# Generate tiles to check
|
||||
|
|
@ -32,52 +32,27 @@ func apply_effect(target_piece = null, board_flow = null, game_state = null):
|
|||
tiles_to_check.append(str(current_x) + "-" + str(y))
|
||||
y += y_direction
|
||||
|
||||
if game_state.isPlayerTurn():
|
||||
print("")
|
||||
else:
|
||||
print("REVERSING TILES TO CHECK")
|
||||
tiles_to_check.reverse()
|
||||
|
||||
# Find final position (stop before any impassable tile)
|
||||
var final_y = target_y
|
||||
for tile_name in tiles_to_check:
|
||||
var tile = game_state.tileManager.get_tile(tile_name)
|
||||
if tile && !tile.passable:
|
||||
# Stop at the tile before this one
|
||||
var tile_y = int(tile_name.split("-")[1])
|
||||
final_y = tile_y - y_direction
|
||||
break
|
||||
# Process pieces in path
|
||||
for tile_name in tiles_to_check:
|
||||
var tile = game_state.tileManager.get_tile(tile_name)
|
||||
if tile && !tile.passable:
|
||||
# print("CHECKING TILES ", tile_name, " passable ", tile.passable)
|
||||
break
|
||||
# elif !tile:
|
||||
# print("CHECKING TILES ", tile_name, " no tile")
|
||||
# Only process tiles up to where we're stopping
|
||||
|
||||
var container = board_flow.get_node(tile_name) as PieceContainer
|
||||
if container.has_piece():
|
||||
var piece_to_capture = container.get_piece()
|
||||
game_state.updatePointsAndCapture(piece_to_capture)
|
||||
game_state.updatePoints(piece_to_capture)
|
||||
container.remove_piece()
|
||||
|
||||
# Move piece to final position
|
||||
print("FINAL TILES ", str(current_x) + "-" + str(final_y))
|
||||
var final_container = board_flow.get_node(str(current_x) + "-" + str(final_y)) as PieceContainer
|
||||
# print("FINAL TILES ", final_container.get_piece().Item_Color)
|
||||
var final_container = board_flow.get_node(str(current_x) + "-" + str(target_y)) as PieceContainer
|
||||
|
||||
# Important: Need to remove the piece from its current container first
|
||||
# AND keep a reference to it
|
||||
target_piece = current_container.get_piece() # Get reference before removing
|
||||
final_container.animate_movement(current_container, target_piece)
|
||||
current_container.remove_piece(true) # Remove from current container
|
||||
final_container.set_piece(target_piece) # Set in new container
|
||||
|
||||
game_state.currentlyMovingPiece = target_piece
|
||||
burned = true
|
||||
|
||||
game_state.resolveMoveEffects()
|
||||
game_state.clearSelection()
|
||||
game_state.stateMachine.transitionToNextState(Constants.POST_MOVE)
|
||||
# Breaks Stockfish for some reason
|
||||
|
||||
return true
|
||||
return true
|
||||
|
|
@ -1 +0,0 @@
|
|||
uid://bayyvtfwpshsh
|
||||
|
|
@ -6,29 +6,20 @@ func _init():
|
|||
duration = 1
|
||||
super._init()
|
||||
cardName = "Explosive Boots"
|
||||
rank = Rank.RANK_3
|
||||
rank = Rank.RANK_2
|
||||
effectType = EffectType.PIECE_EFFECT
|
||||
description = "All enemy units within 1 radius of the moving peice at the end of the turn are captured"
|
||||
description = "All enemy units within 1 radius of the moving peice are captured"
|
||||
unitWhitelist = ["Pawn", "Knight", "King"]
|
||||
remaining_turns = duration
|
||||
is_default = true
|
||||
|
||||
func reset():
|
||||
remaining_turns = duration
|
||||
valid = false
|
||||
|
||||
func apply_effect(target_piece = null, board_flow = null, game_state = null):
|
||||
print("EXPLOSIVE apply_effect", target_piece)
|
||||
attached_piece = target_piece
|
||||
stored_board_flow = board_flow
|
||||
stored_game_state = game_state
|
||||
if !target_piece or !board_flow or !game_state:
|
||||
print(cardName, " missing input param ")
|
||||
return false
|
||||
setup_persistent_effect(game_state)
|
||||
# dont apply on card attachment
|
||||
if !valid:
|
||||
print("EXPLOSIVE apply_effect INVALID", )
|
||||
return true
|
||||
var piece_pos = target_piece.get_parent().name.split("-")
|
||||
var piece_x = int(piece_pos[0])
|
||||
|
|
@ -43,7 +34,7 @@ func apply_effect(target_piece = null, board_flow = null, game_state = null):
|
|||
var target_x = piece_x + dx
|
||||
var target_y = piece_y + dy
|
||||
|
||||
if target_x < 0 or target_x >= game_state.boardXSize or target_y < 0 or target_y >= game_state.boardYSize:
|
||||
if target_x < 0 or target_x >= 8 or target_y < 0 or target_y >= 8:
|
||||
continue
|
||||
|
||||
if dx == 0 and dy == 0:
|
||||
|
|
@ -72,7 +63,8 @@ func apply_effect(target_piece = null, board_flow = null, game_state = null):
|
|||
# Check for pieces to affect
|
||||
var piece = container.get_piece()
|
||||
if piece != null and piece.Item_Color != target_piece.Item_Color:
|
||||
game_state.updatePointsAndCapture(piece)
|
||||
game_state.updatePoints(piece)
|
||||
container.remove_piece()
|
||||
|
||||
# Setup timer to remove overlays
|
||||
var timer = Timer.new()
|
||||
|
|
@ -101,7 +93,7 @@ func reset_tile_styles(board_flow, tiles_to_check):
|
|||
var coords = tile_name.split("-")
|
||||
var isWhite = (int(coords[0]) + int(coords[1])) % 2 == 0
|
||||
var style = StyleBoxFlat.new()
|
||||
style.bg_color = Utils.LIGHT_CELL if isWhite else Utils.DARK_CELL
|
||||
style.bg_color = Color(0.8, 0.8, 0.8, 1) if isWhite else Color(0.2, 0.2, 0.2, 1)
|
||||
tile.add_theme_stylebox_override("normal", style)
|
||||
|
||||
|
||||
|
|
@ -111,14 +103,13 @@ func setup_persistent_effect(game_state):
|
|||
game_state.connect("turn_changed", on_turn_changed)
|
||||
|
||||
func on_turn_changed():
|
||||
print("TURN CHANGED ==================", stored_game_state.isPlayerTurn() )
|
||||
if stored_game_state.stateMachine.disabled:
|
||||
return
|
||||
|
||||
if stored_game_state.isPlayerTurn() and attached_piece and remaining_turns > 0:
|
||||
# print("TURN CHANGED ==================", attached_piece, " | ", remaining_turns, )
|
||||
|
||||
if attached_piece.get_parent().get_parent().owner.isPlayerTurn() and attached_piece and remaining_turns > 0:
|
||||
# print(attached_piece.get_parent().get_parent(), attached_piece.get_parent().get_parent().owner)
|
||||
var piece = attached_piece
|
||||
var flow = stored_board_flow
|
||||
var state = stored_game_state
|
||||
var flow = attached_piece.get_parent().get_parent()
|
||||
var state = attached_piece.get_parent().get_parent().owner
|
||||
|
||||
# print("Debug values:")
|
||||
# print("- Piece: ", piece, " is null? ", piece == null)
|
||||
|
|
@ -131,7 +122,7 @@ func on_turn_changed():
|
|||
|
||||
func remove_effect():
|
||||
if attached_piece:
|
||||
var game_state = stored_game_state
|
||||
var game_state = attached_piece.get_parent().get_parent().owner
|
||||
if game_state.is_connected("turn_changed", on_turn_changed):
|
||||
game_state.disconnect("turn_changed", on_turn_changed)
|
||||
super.remove_effect()
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
uid://64bdg86r6t4o
|
||||
|
|
@ -8,9 +8,9 @@ func _init():
|
|||
cardName = "Fiery Cape"
|
||||
rank = Rank.RANK_2
|
||||
effectType = EffectType.PIECE_EFFECT
|
||||
description = "The tile behind the attached peice is on fire, all peices on the tile are captured"
|
||||
duration = 5 # Card effect lasts 3 turns
|
||||
unitWhitelist = ["Pawn", "Bishop", "Queen", "King", "Rook", "Knight"]
|
||||
description = "Leaves a trail of fire that lasts 4 turns. Owner's pieces can pass safely."
|
||||
duration = 3 # Card effect lasts 3 turns
|
||||
unitWhitelist = ["Pawn", "Bishop", "Queen", "King", "Rook"] # Any piece can wear the cape
|
||||
|
||||
func apply_effect(target_piece = null, board_flow = null, game_state = null):
|
||||
if !super.apply_effect(target_piece, board_flow, game_state):
|
||||
|
|
@ -30,7 +30,7 @@ func on_turn_changed():
|
|||
return
|
||||
|
||||
var piece = attached_piece
|
||||
var board_flow = stored_board_flow
|
||||
var board_flow = piece.get_parent().get_parent()
|
||||
var current_pos = piece.get_parent().name
|
||||
|
||||
# If the piece has moved, place fire tiles
|
||||
|
|
@ -52,7 +52,7 @@ func on_turn_changed():
|
|||
# Start with the previous position
|
||||
positions_to_mark.append(previous_position)
|
||||
|
||||
# Add intermediate positions
|
||||
# Add intermediate positions (but not the final position)
|
||||
while (x + dx != current_x || y + dy != current_y):
|
||||
x += dx
|
||||
y += dy
|
||||
|
|
@ -60,20 +60,19 @@ func on_turn_changed():
|
|||
|
||||
# Place fire tiles
|
||||
var tile_owner = Tile.TileOwner.PLAYER if piece.Item_Color == 0 else Tile.TileOwner.ENEMY
|
||||
# only place on last tile
|
||||
var pos = positions_to_mark[positions_to_mark.size() - 1]
|
||||
var container = board_flow.get_node(pos) as PieceContainer
|
||||
if container:
|
||||
var is_white = (int(pos.split("-")[0]) + int(pos.split("-")[1])) % 2 == 0
|
||||
var fire_tile = FireWallTile.new(container, is_white, 2, tile_owner)
|
||||
board_flow.get_parent().tileManager.add_tile(pos, fire_tile)
|
||||
fire_tiles.append(pos)
|
||||
for pos in positions_to_mark:
|
||||
var container = board_flow.get_node(pos) as PieceContainer
|
||||
if container:
|
||||
var is_white = (int(pos.split("-")[0]) + int(pos.split("-")[1])) % 2 == 0
|
||||
var fire_tile = FireWallTile.new(container, is_white, 4, tile_owner)
|
||||
board_flow.get_parent().tileManager.add_tile(pos, fire_tile)
|
||||
fire_tiles.append(pos)
|
||||
|
||||
previous_position = current_pos
|
||||
|
||||
func remove_effect():
|
||||
if attached_piece:
|
||||
var game_state = stored_game_state
|
||||
var game_state = attached_piece.get_parent().get_parent().owner
|
||||
if game_state.is_connected("turn_changed", on_turn_changed):
|
||||
game_state.disconnect("turn_changed", on_turn_changed)
|
||||
super.remove_effect()
|
||||
super.remove_effect()
|
||||
|
|
@ -1 +0,0 @@
|
|||
uid://cpgq5itsb2l8j
|
||||
|
|
@ -1,78 +0,0 @@
|
|||
class_name FieryTrailCard extends Card
|
||||
|
||||
var previous_position: String = ""
|
||||
var fire_tiles: Array[String] = []
|
||||
|
||||
func _init():
|
||||
super._init()
|
||||
cardName = "Fiery Trail"
|
||||
rank = Rank.RANK_1
|
||||
effectType = EffectType.PIECE_EFFECT
|
||||
description = "Leaves a trail of fire that lasts 4 turns. Player's pieces can pass safely."
|
||||
duration = 3 # Card effect lasts 3 turns
|
||||
unitWhitelist = ["Pawn", "Bishop", "Queen", "King", "Rook"]
|
||||
|
||||
func apply_effect(target_piece = null, board_flow = null, game_state = null):
|
||||
if !super.apply_effect(target_piece, board_flow, game_state):
|
||||
return false
|
||||
|
||||
# Store the current position as previous position
|
||||
previous_position = target_piece.get_parent().name
|
||||
setup_persistent_effect(game_state)
|
||||
return true
|
||||
|
||||
func setup_persistent_effect(game_state):
|
||||
if !game_state.is_connected("turn_changed", on_turn_changed):
|
||||
game_state.connect("turn_changed", on_turn_changed)
|
||||
|
||||
func on_turn_changed():
|
||||
if !attached_piece || remaining_turns <= 0:
|
||||
return
|
||||
|
||||
var piece = attached_piece
|
||||
var board_flow = stored_board_flow
|
||||
var current_pos = piece.get_parent().name
|
||||
|
||||
# If the piece has moved, place fire tiles
|
||||
if current_pos != previous_position:
|
||||
var current_coords = current_pos.split("-")
|
||||
var prev_coords = previous_position.split("-")
|
||||
var current_x = int(current_coords[0])
|
||||
var current_y = int(current_coords[1])
|
||||
var prev_x = int(prev_coords[0])
|
||||
var prev_y = int(prev_coords[1])
|
||||
|
||||
# Calculate all positions between previous and current (excluding current)
|
||||
var positions_to_mark = []
|
||||
var dx = sign(current_x - prev_x)
|
||||
var dy = sign(current_y - prev_y)
|
||||
var x = prev_x
|
||||
var y = prev_y
|
||||
|
||||
# Start with the previous position
|
||||
positions_to_mark.append(previous_position)
|
||||
|
||||
# Add intermediate positions (but not the final position)
|
||||
while (x + dx != current_x || y + dy != current_y):
|
||||
x += dx
|
||||
y += dy
|
||||
positions_to_mark.append(str(x) + "-" + str(y))
|
||||
|
||||
# Place fire tiles
|
||||
var tile_owner = Tile.TileOwner.PLAYER if piece.Item_Color == 0 else Tile.TileOwner.ENEMY
|
||||
for pos in positions_to_mark:
|
||||
var container = board_flow.get_node(pos) as PieceContainer
|
||||
if container:
|
||||
var is_white = (int(pos.split("-")[0]) + int(pos.split("-")[1])) % 2 == 0
|
||||
var fire_tile = FireWallTile.new(container, is_white, 4, tile_owner)
|
||||
board_flow.get_parent().tileManager.add_tile(pos, fire_tile)
|
||||
fire_tiles.append(pos)
|
||||
|
||||
previous_position = current_pos
|
||||
|
||||
func remove_effect():
|
||||
if attached_piece:
|
||||
var game_state = stored_game_state
|
||||
if game_state.is_connected("turn_changed", on_turn_changed):
|
||||
game_state.disconnect("turn_changed", on_turn_changed)
|
||||
super.remove_effect()
|
||||
|
|
@ -1 +0,0 @@
|
|||
uid://ca4j0d6hrcm4x
|
||||
|
|
@ -1,143 +0,0 @@
|
|||
class_name HalitosisCard extends Card
|
||||
|
||||
const EFFECT_RADIUS = 1
|
||||
var valid = false;
|
||||
func _init():
|
||||
duration = 3
|
||||
super._init()
|
||||
cardName = "Halitosis"
|
||||
rank = Rank.RANK_2
|
||||
effectType = EffectType.PIECE_EFFECT
|
||||
description = "All enemy units within 1 radius of the moving piece at the end of the turn are pushed back"
|
||||
unitWhitelist = ["Pawn", "Knight", "King"]
|
||||
remaining_turns = duration
|
||||
is_default = false
|
||||
|
||||
func reset():
|
||||
remaining_turns = duration
|
||||
valid = false
|
||||
|
||||
func apply_effect(target_piece = null, board_flow = null, game_state = null):
|
||||
print("HALITOSIS apply_effect", target_piece)
|
||||
attached_piece = target_piece
|
||||
stored_board_flow = board_flow
|
||||
stored_game_state = game_state
|
||||
if !target_piece or !board_flow or !game_state:
|
||||
print(cardName, " missing input param ")
|
||||
return false
|
||||
setup_persistent_effect(game_state)
|
||||
# dont apply on card attachment
|
||||
if !valid:
|
||||
print("HALITOSIS apply_effect INVALID", )
|
||||
return true
|
||||
var piece_pos = target_piece.get_parent().name.split("-")
|
||||
var piece_x = int(piece_pos[0])
|
||||
var piece_y = int(piece_pos[1])
|
||||
|
||||
var tiles_to_check = []
|
||||
for dx in range(-EFFECT_RADIUS, EFFECT_RADIUS + 1):
|
||||
for dy in range(-EFFECT_RADIUS, EFFECT_RADIUS + 1):
|
||||
if max(abs(dx), abs(dy)) > EFFECT_RADIUS:
|
||||
continue
|
||||
|
||||
var target_x = piece_x + dx
|
||||
var target_y = piece_y + dy
|
||||
|
||||
if target_x < 0 or target_x >= game_state.boardXSize or target_y < 0 or target_y >= game_state.boardYSize:
|
||||
continue
|
||||
|
||||
if dx == 0 and dy == 0:
|
||||
continue
|
||||
|
||||
tiles_to_check.append(str(target_x) + "-" + str(target_y))
|
||||
|
||||
# Process tiles and add overlay
|
||||
for tile_name in tiles_to_check:
|
||||
var container = board_flow.get_node(tile_name) as PieceContainer
|
||||
|
||||
# Handle overlay through container's overlay management
|
||||
container.remove_overlay("HalitosisOverlay")
|
||||
|
||||
var overlay = ColorRect.new()
|
||||
overlay.name = "HalitosisOverlay"
|
||||
overlay.color = Color(0.5, 0.7, 1, 0.3) # Blue-ish
|
||||
overlay.size = container.size
|
||||
overlay.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
container.add_overlay(overlay)
|
||||
overlay.show()
|
||||
|
||||
# Check for pieces to affect
|
||||
var piece = container.get_piece()
|
||||
if piece != null and piece.Item_Color != target_piece.Item_Color:
|
||||
# Calculate the direction to push
|
||||
var push_dir_x = piece_x - int(tile_name.split("-")[0])
|
||||
var push_dir_y = piece_y - int(tile_name.split("-")[1])
|
||||
|
||||
# Normalize the direction vector
|
||||
var length = sqrt(push_dir_x * push_dir_x + push_dir_y * push_dir_y)
|
||||
if length > 0:
|
||||
push_dir_x = round(push_dir_x / length)
|
||||
push_dir_y = round(push_dir_y / length)
|
||||
|
||||
# Calculate target cell to push to
|
||||
var push_target_x = int(int(tile_name.split("-")[0]) - push_dir_x)
|
||||
var push_target_y = int(int(tile_name.split("-")[1]) - push_dir_y)
|
||||
var push_target = str(push_target_x) + "-" + str(push_target_y)
|
||||
print("HALITSOSES ", tile_name, " ", push_target)
|
||||
# Check if target is valid and empty
|
||||
if push_target_x >= 0 and push_target_x < game_state.boardXSize and push_target_y >= 0 and push_target_y < game_state.boardYSize:
|
||||
var target_container = board_flow.get_node(push_target) as PieceContainer
|
||||
if target_container and !target_container.has_piece():
|
||||
# Animate the push
|
||||
target_container.animate_movement(container, piece)
|
||||
else:
|
||||
# If we can't push (blocked), capture the piece instead
|
||||
# game_state.updatePointsAndCapture(piece)
|
||||
print("Blocked should we capture?")
|
||||
else:
|
||||
print("Off Board should we capture?")
|
||||
# If push would be off the board, capture the piece
|
||||
# game_state.updatePointsAndCapture(piece)
|
||||
|
||||
# Setup timer to remove overlays
|
||||
var timer = Timer.new()
|
||||
board_flow.add_child(timer)
|
||||
timer.wait_time = 1
|
||||
timer.one_shot = true
|
||||
timer.timeout.connect(func():
|
||||
for tile_name in tiles_to_check:
|
||||
var tile = board_flow.get_node(tile_name)
|
||||
var overlay = tile.get_node_or_null("HalitosisOverlay")
|
||||
if overlay:
|
||||
overlay.queue_free()
|
||||
timer.queue_free()
|
||||
)
|
||||
timer.start()
|
||||
|
||||
return true
|
||||
|
||||
func setup_persistent_effect(game_state):
|
||||
# Connect to the turn change signal if not already connected
|
||||
if !game_state.is_connected("turn_changed", on_turn_changed):
|
||||
game_state.connect("turn_changed", on_turn_changed)
|
||||
|
||||
func on_turn_changed():
|
||||
print("TURN CHANGED ==================", stored_game_state.isPlayerTurn())
|
||||
if stored_game_state.stateMachine.disabled:
|
||||
return
|
||||
|
||||
if stored_game_state.isPlayerTurn() and attached_piece and remaining_turns > 0:
|
||||
var piece = attached_piece
|
||||
var flow = stored_board_flow
|
||||
var state = stored_game_state
|
||||
|
||||
# Now try apply_effect with these values
|
||||
valid = true
|
||||
apply_effect(piece, flow, state)
|
||||
|
||||
func remove_effect():
|
||||
if attached_piece:
|
||||
var game_state = stored_game_state
|
||||
if game_state.is_connected("turn_changed", on_turn_changed):
|
||||
game_state.disconnect("turn_changed", on_turn_changed)
|
||||
super.remove_effect()
|
||||
|
|
@ -1 +0,0 @@
|
|||
uid://b8f17yg2854vs
|
||||
|
|
@ -1 +0,0 @@
|
|||
uid://5w8amumsdpto
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
class_name HorseCostumeCard extends Card
|
||||
|
||||
func _init():
|
||||
super._init()
|
||||
# id = Utils.generate_guid()
|
||||
cardName = "Horse Costume"
|
||||
rank = Rank.RANK_2
|
||||
effectType = EffectType.MOVEMENT_MODIFIER
|
||||
description = "Attached Unit can move and capture in any direction"
|
||||
duration = 3
|
||||
unitWhitelist = ["Pawn", "Bishop", "Queen", "Rook", "King"]
|
||||
is_default = false
|
||||
# current_movement_string = "mWmFcfF"
|
||||
|
||||
|
||||
func apply_effect(target_piece = null, board_flow = null, game_state = null):
|
||||
if !super.apply_effect(target_piece, board_flow, game_state):
|
||||
return false
|
||||
|
||||
attached_piece = target_piece
|
||||
attached_piece.current_movement_string = "N"
|
||||
return true
|
||||
|
||||
|
||||
func reset():
|
||||
super.reset()
|
||||
remaining_turns = duration
|
||||
attached_piece.reset_current_movement_string()
|
||||
|
|
@ -1 +0,0 @@
|
|||
uid://nw1oesh7pr48
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
class_name KingsSquireCard extends Card
|
||||
|
||||
func _init():
|
||||
super._init()
|
||||
# id = Utils.generate_guid()
|
||||
cardName = "Kings' Squire"
|
||||
rank = Rank.RANK_2
|
||||
effectType = EffectType.MOVEMENT_MODIFIER
|
||||
description = "Attached Pawn can move and capture in any direction"
|
||||
duration = 3
|
||||
unitWhitelist = ["Pawn"]
|
||||
is_default = false
|
||||
# current_movement_string = "mWmFcfF"
|
||||
|
||||
|
||||
func apply_effect(target_piece = null, board_flow = null, game_state = null):
|
||||
if !super.apply_effect(target_piece, board_flow, game_state):
|
||||
return false
|
||||
|
||||
attached_piece = target_piece
|
||||
attached_piece.current_movement_string = "WF"
|
||||
return true
|
||||
|
||||
|
||||
func reset():
|
||||
super.reset()
|
||||
remaining_turns = duration
|
||||
attached_piece.reset_current_movement_string()
|
||||
|
|
@ -1 +0,0 @@
|
|||
uid://ms7wi6dgoqnr
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
class_name QueensSquireCard extends Card
|
||||
|
||||
func _init():
|
||||
super._init()
|
||||
# id = Utils.generate_guid()
|
||||
cardName = "Queens' Squire"
|
||||
rank = Rank.RANK_3
|
||||
effectType = EffectType.MOVEMENT_MODIFIER
|
||||
description = "Attached Pawn can move in any direction, can only capture diagonally forward"
|
||||
duration = 4
|
||||
unitWhitelist = ["Pawn"]
|
||||
is_default = true
|
||||
# current_movement_string = "mWmFcfF"
|
||||
|
||||
|
||||
func apply_effect(target_piece = null, board_flow = null, game_state = null):
|
||||
if !super.apply_effect(target_piece, board_flow, game_state):
|
||||
return false
|
||||
|
||||
attached_piece = target_piece
|
||||
attached_piece.current_movement_string = "mWmFcfF"
|
||||
return true
|
||||
|
||||
|
||||
func reset():
|
||||
super.reset()
|
||||
remaining_turns = duration
|
||||
attached_piece.reset_current_movement_string()
|
||||
|
|
@ -1 +0,0 @@
|
|||
uid://8vpac2cx4e0u
|
||||
|
|
@ -8,19 +8,13 @@ func _init():
|
|||
cardName = "Supernova"
|
||||
rank = Rank.RANK_0
|
||||
effectType = EffectType.PIECE_EFFECT
|
||||
duration = 1
|
||||
duration = 5
|
||||
description = "All enemy units within 4 radius are captured"
|
||||
unitWhitelist = ["King"]
|
||||
remaining_turns = duration
|
||||
|
||||
func reset():
|
||||
remaining_turns = duration
|
||||
valid = false
|
||||
|
||||
func apply_effect(target_piece = null, board_flow = null, game_state = null):
|
||||
attached_piece = target_piece
|
||||
stored_board_flow = board_flow
|
||||
stored_game_state = game_state
|
||||
if !target_piece or !board_flow or !game_state:
|
||||
print(cardName, " missing input param ")
|
||||
return false
|
||||
|
|
@ -32,10 +26,10 @@ func apply_effect(target_piece = null, board_flow = null, game_state = null):
|
|||
var king_pos = target_piece.get_parent().name.split("-")
|
||||
var king_x = int(king_pos[0])
|
||||
var king_y = int(king_pos[1])
|
||||
|
||||
|
||||
# Collect all tiles within radius to check
|
||||
var tiles_to_check = []
|
||||
|
||||
|
||||
# Check in all directions (up, down, left, right and diagonals)
|
||||
for dx in range(-CAPTURE_RADIUS, CAPTURE_RADIUS + 1):
|
||||
for dy in range(-CAPTURE_RADIUS, CAPTURE_RADIUS + 1):
|
||||
|
|
@ -48,7 +42,7 @@ func apply_effect(target_piece = null, board_flow = null, game_state = null):
|
|||
var target_y = king_y + dy
|
||||
|
||||
# Skip if outside board bounds
|
||||
if target_x < 0 or target_x >= game_state.boardXSize or target_y < 0 or target_y >= game_state.boardYSize:
|
||||
if target_x < 0 or target_x >= 8 or target_y < 0 or target_y >= 8:
|
||||
continue
|
||||
|
||||
# Skip king's own position
|
||||
|
|
@ -63,7 +57,8 @@ func apply_effect(target_piece = null, board_flow = null, game_state = null):
|
|||
if tile.get_piece() != null:
|
||||
var piece = tile.get_piece()
|
||||
if piece.Item_Color != target_piece.Item_Color: # Only capture enemy pieces
|
||||
game_state.updatePointsAndCapture(piece)
|
||||
game_state.updatePoints(piece)
|
||||
tile.remove_piece()
|
||||
# Process tiles and add overlay
|
||||
for tile_name in tiles_to_check:
|
||||
var container = board_flow.get_node(tile_name) as PieceContainer
|
||||
|
|
@ -82,8 +77,9 @@ func apply_effect(target_piece = null, board_flow = null, game_state = null):
|
|||
# Check for pieces to affect
|
||||
var piece = container.get_piece()
|
||||
if piece != null and piece.Item_Color != target_piece.Item_Color:
|
||||
game_state.updatePointsAndCapture(piece)
|
||||
|
||||
game_state.updatePoints(piece)
|
||||
container.remove_piece()
|
||||
|
||||
# Setup timer to remove overlays
|
||||
var timer = Timer.new()
|
||||
board_flow.add_child(timer)
|
||||
|
|
@ -98,7 +94,7 @@ func apply_effect(target_piece = null, board_flow = null, game_state = null):
|
|||
timer.queue_free()
|
||||
)
|
||||
timer.start()
|
||||
|
||||
|
||||
return true
|
||||
|
||||
|
||||
|
|
@ -109,10 +105,11 @@ func setup_persistent_effect(game_state):
|
|||
|
||||
func on_turn_changed():
|
||||
|
||||
if stored_game_state.isPlayerTurn() and attached_piece and remaining_turns > 0:
|
||||
if attached_piece.get_parent().get_parent().owner.isPlayerTurn() and attached_piece and remaining_turns > 0:
|
||||
# print(attached_piece.get_parent().get_parent(), attached_piece.get_parent().get_parent().owner)
|
||||
var piece = attached_piece
|
||||
var flow = stored_board_flow
|
||||
var state = stored_game_state
|
||||
var flow = attached_piece.get_parent().get_parent()
|
||||
var state = attached_piece.get_parent().get_parent().owner
|
||||
|
||||
# print("Debug values:")
|
||||
# print("- Piece: ", piece, " is null? ", piece == null)
|
||||
|
|
@ -125,7 +122,37 @@ func on_turn_changed():
|
|||
|
||||
func remove_effect():
|
||||
if attached_piece:
|
||||
var game_state = stored_game_state
|
||||
var game_state = attached_piece.get_parent().get_parent().owner
|
||||
if game_state.is_connected("turn_changed", Callable(self, "on_turn_changed")):
|
||||
game_state.disconnect("turn_changed", Callable(self, "on_turn_changed"))
|
||||
super.remove_effect()
|
||||
|
||||
func update_visual_effect(king, board_flow):
|
||||
# Could be extended with visual feedback like particles or highlighting
|
||||
var radius_tiles = []
|
||||
var king_pos = king.get_parent().name.split("-")
|
||||
var king_x = int(king_pos[0])
|
||||
var king_y = int(king_pos[1])
|
||||
|
||||
for x in range(max(0, king_x - CAPTURE_RADIUS), min(8, king_x + CAPTURE_RADIUS + 1)):
|
||||
for y in range(max(0, king_y - CAPTURE_RADIUS), min(8, king_y + CAPTURE_RADIUS + 1)):
|
||||
if abs(x - king_x) + abs(y - king_y) <= CAPTURE_RADIUS:
|
||||
var tile_name = str(x) + "-" + str(y)
|
||||
radius_tiles.append(board_flow.get_node(tile_name))
|
||||
|
||||
# Highlight affected tiles briefly
|
||||
for tile in radius_tiles:
|
||||
var style = StyleBoxFlat.new()
|
||||
style.bg_color = Color(1, 0, 0, 0.3) # Red tint
|
||||
tile.add_theme_stylebox_override("normal", style)
|
||||
|
||||
# Remove highlight after a short delay
|
||||
var timer = Timer.new()
|
||||
board_flow.add_child(timer)
|
||||
timer.wait_time = 0.5
|
||||
timer.one_shot = true
|
||||
timer.timeout.connect(func():
|
||||
tile.remove_theme_stylebox_override("normal")
|
||||
timer.queue_free()
|
||||
)
|
||||
timer.start()
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
uid://cvldhayf5ket0
|
||||
|
|
@ -1,109 +0,0 @@
|
|||
extends Panel
|
||||
class_name CompactCardDisplay
|
||||
|
||||
# Node references (will be created programmatically)
|
||||
var card_name_label: Label
|
||||
var rank_label: Label
|
||||
var description_label: Label
|
||||
var effect_type_label: Label
|
||||
|
||||
# Card rank colors (same as other card components)
|
||||
var rank_colors = {
|
||||
Card.Rank.RANK_0: Color(0.9, 0.1, 0.1, 1.0), # Red
|
||||
Card.Rank.RANK_1: Color(0.9, 0.6, 0.1, 1.0), # Orange
|
||||
Card.Rank.RANK_2: Color(0.1, 0.7, 0.1, 1.0), # Green
|
||||
Card.Rank.RANK_3: Color(0.1, 0.7, 0.9, 1.0) # Blue
|
||||
}
|
||||
|
||||
# Effect type descriptions
|
||||
var effect_type_names = {
|
||||
Card.EffectType.MOVEMENT_MODIFIER: "Movement Modifier",
|
||||
Card.EffectType.BOARD_EFFECT: "Board Effect",
|
||||
Card.EffectType.PIECE_EFFECT: "Piece Effect",
|
||||
Card.EffectType.SPECIAL_ACTION: "Special Action"
|
||||
}
|
||||
|
||||
var current_card = null
|
||||
|
||||
func _ready():
|
||||
# Create a stylish background
|
||||
var style = StyleBoxFlat.new()
|
||||
style.bg_color = Color(0.15, 0.15, 0.15, 1.0)
|
||||
style.corner_radius_top_left = 5
|
||||
style.corner_radius_top_right = 5
|
||||
style.corner_radius_bottom_left = 5
|
||||
style.corner_radius_bottom_right = 5
|
||||
style.border_width_left = 2
|
||||
style.border_width_top = 2
|
||||
style.border_width_right = 2
|
||||
style.border_width_bottom = 2
|
||||
style.border_color = Color(0.3, 0.3, 0.3)
|
||||
add_theme_stylebox_override("panel", style)
|
||||
|
||||
# Create a content container
|
||||
var content = VBoxContainer.new()
|
||||
content.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
content.add_theme_constant_override("separation", 2) # Tight spacing
|
||||
add_child(content)
|
||||
|
||||
card_name_label = Label.new()
|
||||
card_name_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
card_name_label.add_theme_font_size_override("font_size", 12)
|
||||
card_name_label.clip_text = true # Prevent overflow
|
||||
content.add_child(card_name_label)
|
||||
|
||||
rank_label = Label.new()
|
||||
rank_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
rank_label.add_theme_font_size_override("font_size", 10)
|
||||
content.add_child(rank_label)
|
||||
|
||||
# Create description label (limited to 2-3 lines)
|
||||
description_label = Label.new()
|
||||
description_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
description_label.add_theme_font_size_override("font_size", 9)
|
||||
description_label.autowrap_mode = TextServer.AUTOWRAP_WORD
|
||||
description_label.max_lines_visible = 3
|
||||
description_label.size_flags_vertical = SIZE_EXPAND_FILL
|
||||
content.add_child(description_label)
|
||||
|
||||
effect_type_label = Label.new()
|
||||
effect_type_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
effect_type_label.add_theme_font_size_override("font_size", 8)
|
||||
effect_type_label.clip_text = true
|
||||
content.add_child(effect_type_label)
|
||||
|
||||
|
||||
func set_selected(is_selected: bool) -> void:
|
||||
var style = get_theme_stylebox("panel").duplicate()
|
||||
|
||||
if is_selected:
|
||||
style.bg_color = Utils.GREEN_CELL # Selected color
|
||||
else:
|
||||
style.bg_color = Utils.DARK_CELL
|
||||
|
||||
add_theme_stylebox_override("panel", style)
|
||||
|
||||
func set_card(card: Card):
|
||||
if !card:
|
||||
return
|
||||
|
||||
if !is_inside_tree():
|
||||
await ready
|
||||
|
||||
current_card = card
|
||||
|
||||
card_name_label.text = card.cardName
|
||||
|
||||
rank_label.text = "Rank " + str(card.rank)
|
||||
if card.rank in rank_colors:
|
||||
rank_label.add_theme_color_override("font_color", rank_colors[card.rank])
|
||||
var style = get_theme_stylebox("panel").duplicate()
|
||||
style.border_color = rank_colors[card.rank]
|
||||
add_theme_stylebox_override("panel", style)
|
||||
|
||||
description_label.text = card.description
|
||||
|
||||
if card.effectType in effect_type_names:
|
||||
effect_type_label.text = "Effect: " + effect_type_names[card.effectType]
|
||||
else:
|
||||
effect_type_label.text = "Effect: Unknown"
|
||||
|
|
@ -1 +0,0 @@
|
|||
uid://dm6uv77n0roen
|
||||
|
|
@ -3,243 +3,129 @@ class_name DeckManager
|
|||
|
||||
var deck: Array = []
|
||||
var hand: Array = []
|
||||
var bank: Array = []
|
||||
var discard: Array = []
|
||||
var attached_cards: Dictionary = {} # piece_id: card
|
||||
var attached_effects: Dictionary = {} # piece_id: [card]
|
||||
var hand_size: int = 5
|
||||
var preloaded_hand_cards: Array = [] # array of string ids
|
||||
|
||||
# Card costs for shop
|
||||
const CARD_BASE_COSTS = {
|
||||
Card.Rank.RANK_0: 100,
|
||||
Card.Rank.RANK_1: 75,
|
||||
Card.Rank.RANK_2: 50,
|
||||
Card.Rank.RANK_3: 25
|
||||
}
|
||||
|
||||
func _init():
|
||||
print("************************DECK INIT*****************")
|
||||
|
||||
|
||||
|
||||
|
||||
func preload_cards(card_array = []):
|
||||
var cnt = 0
|
||||
preloaded_hand_cards.clear()
|
||||
for card in card_array:
|
||||
preloaded_hand_cards.append(card.id)
|
||||
|
||||
|
||||
func set_hand_size(size: int):
|
||||
hand_size = size
|
||||
|
||||
func initializeStartingDeck(starting_cards: Array = []):
|
||||
deck.clear();
|
||||
# addCardToDeck(ExplosiveBootsCard.new())
|
||||
# addCardToDeck(DoubleTimeCard.new())
|
||||
# # addCardToDeck(DrunkDrivingCard.new())
|
||||
# addCardToDeck(SupernovaCard.new())
|
||||
print("Initializing deck with " + str(starting_cards.size()) + " cards")
|
||||
for template_card in starting_cards:
|
||||
# Create a new instance of the same card type
|
||||
var new_card = create_new_card_instance(template_card)
|
||||
if new_card:
|
||||
addCardToDeck(new_card)
|
||||
else:
|
||||
print("Failed to create card: " + template_card.cardName)
|
||||
|
||||
|
||||
func initializeStartingBank():
|
||||
bank.append(HopscotchCard.new())
|
||||
bank.append(FieryCapeCard.new())
|
||||
bank.append(FieryTrailCard.new())
|
||||
bank.append(ExplosiveBootsCard.new())
|
||||
bank.append(DoubleTimeCard.new())
|
||||
bank.append(DrunkDrivingCard.new())
|
||||
bank.append(SupernovaCard.new())
|
||||
|
||||
for i in range(3):
|
||||
bank.append(HopscotchCard.new())
|
||||
bank.append(FieryCapeCard.new())
|
||||
initializeStartingDeck()
|
||||
|
||||
func initializeStartingDeck():
|
||||
deck.clear();
|
||||
# for i in range(2):
|
||||
# deck.append(DoubleTimeCard.new())
|
||||
deck.append(FieryCapeCard.new())
|
||||
deck.append(ExplosiveBootsCard.new())
|
||||
deck.append(DoubleTimeCard.new())
|
||||
deck.append(DrunkDrivingCard.new())
|
||||
deck.append(HopscotchCard.new())
|
||||
deck.append(SupernovaCard.new())
|
||||
shuffleDeck()
|
||||
drawStartingHand()
|
||||
|
||||
func shuffleDeck():
|
||||
deck.shuffle()
|
||||
deck.shuffle()
|
||||
|
||||
func drawStartingHand(skip_preload: bool = false):
|
||||
if !skip_preload:
|
||||
for id in preloaded_hand_cards:
|
||||
var matchCard = func matchCardId(card):
|
||||
return "id" in card and card.id == id
|
||||
var cardIndex = deck.find_custom(matchCard.bind())
|
||||
if cardIndex != -1:
|
||||
hand.append(deck.pop_at(cardIndex))
|
||||
|
||||
for i in range(min(hand_size, deck.size())):
|
||||
drawCard()
|
||||
func drawStartingHand():
|
||||
for i in range(min(hand_size, deck.size())):
|
||||
drawCard()
|
||||
|
||||
func drawCard():
|
||||
if deck.is_empty() && !discard.is_empty():
|
||||
deck = discard.duplicate()
|
||||
discard.clear()
|
||||
shuffleDeck()
|
||||
|
||||
if !deck.is_empty() && hand.size() < hand_size:
|
||||
hand.append(deck.pop_back())
|
||||
emit_signal("hand_updated", hand)
|
||||
|
||||
if deck.is_empty() && !discard.is_empty():
|
||||
deck = discard.duplicate()
|
||||
discard.clear()
|
||||
shuffleDeck()
|
||||
|
||||
if !deck.is_empty() && hand.size() < hand_size:
|
||||
hand.append(deck.pop_back())
|
||||
|
||||
emit_signal("hand_updated", hand)
|
||||
|
||||
|
||||
signal hand_updated
|
||||
|
||||
|
||||
func addCardToDeck(card: Card):
|
||||
deck.insert(0, card)
|
||||
|
||||
|
||||
func playCard(card: Card, target_piece: Pawn, board_flow = null, game_state = null, avoidHand = false):
|
||||
if !avoidHand and !hand.has(card):
|
||||
# print("Failed Play Card 1")
|
||||
return false
|
||||
if card.duration > 0:
|
||||
attached_cards[target_piece.get_instance_id()] = card
|
||||
|
||||
if card.apply_effect(target_piece, board_flow, game_state):
|
||||
if card.duration > 0:
|
||||
attached_cards[target_piece.get_instance_id()] = card
|
||||
hand.erase(card)
|
||||
emit_signal("hand_updated", hand)
|
||||
|
||||
|
||||
return true
|
||||
# print("Failed Play Card 2")
|
||||
return false
|
||||
|
||||
|
||||
func playEffect(card: Card, target_piece: Pawn, board_flow = null, game_state = null):
|
||||
|
||||
var key = target_piece.get_instance_id();
|
||||
if !attached_effects.has(key):
|
||||
attached_effects[key] = []
|
||||
else:
|
||||
for existing_card in attached_effects[key]:
|
||||
if existing_card.cardName == card.cardName:
|
||||
# Card already exists, don't add it again
|
||||
return false
|
||||
|
||||
if card.apply_effect(target_piece, board_flow, game_state):
|
||||
if card.duration > 0:
|
||||
attached_effects[key].append(card)
|
||||
|
||||
|
||||
return true
|
||||
# print("Failed Play Card 2")
|
||||
return false
|
||||
|
||||
func cleanup():
|
||||
if hand.size() > 0:
|
||||
for card in hand:
|
||||
deck.push_back(card)
|
||||
hand.clear()
|
||||
|
||||
func updateEffectDurations():
|
||||
var expired_entries = [] # Store [piece_id, card_id] pairs to remove
|
||||
|
||||
for piece_id in attached_effects:
|
||||
var cards_to_remove = [] # Track which cards to remove from this piece's array
|
||||
|
||||
for card in attached_effects[piece_id]:
|
||||
card.update_duration()
|
||||
if card.remaining_turns <= 0:
|
||||
cards_to_remove.append(card)
|
||||
else:
|
||||
var piece_node = instance_from_id(piece_id)
|
||||
if is_instance_valid(piece_node):
|
||||
piece_node.update_appearance()
|
||||
|
||||
# Remove expired cards from this piece's array
|
||||
for card in cards_to_remove:
|
||||
attached_effects[piece_id].erase(card)
|
||||
|
||||
# If no cards left for this piece, mark the piece_id for removal
|
||||
if attached_effects[piece_id].is_empty():
|
||||
expired_entries.append(piece_id)
|
||||
var piece_node = instance_from_id(piece_id)
|
||||
if is_instance_valid(piece_node):
|
||||
piece_node.update_appearance()
|
||||
|
||||
# Remove empty entries
|
||||
for piece_id in expired_entries:
|
||||
attached_effects.erase(piece_id)
|
||||
func playCard(card: Card, target_piece: Pawn, board_flow = null, game_state = null):
|
||||
if !hand.has(card):
|
||||
# print("Failed Play Card 1")
|
||||
return false
|
||||
if card.duration > 0:
|
||||
attached_cards[target_piece.get_instance_id()] = card
|
||||
|
||||
if card.apply_effect(target_piece, board_flow, game_state):
|
||||
if card.duration > 0:
|
||||
attached_cards[target_piece.get_instance_id()] = card
|
||||
hand.erase(card)
|
||||
emit_signal("hand_updated", hand)
|
||||
|
||||
|
||||
return true
|
||||
# print("Failed Play Card 2")
|
||||
return false
|
||||
|
||||
func updateCardDurations():
|
||||
var expired_cards = []
|
||||
for piece_id in attached_cards:
|
||||
var card = attached_cards[piece_id]
|
||||
card.update_duration()
|
||||
# print("Card: ", card.id, " | ", card.cardName, " | ", card.remaining_turns)
|
||||
if card.remaining_turns <= 0:
|
||||
expired_cards.append(piece_id)
|
||||
var expired_cards = []
|
||||
for piece_id in attached_cards:
|
||||
var card = attached_cards[piece_id]
|
||||
card.update_duration()
|
||||
# print("Card: ", card.id, " | ", card.cardName, " | ", card.remaining_turns)
|
||||
if card.remaining_turns <= 0:
|
||||
expired_cards.append(piece_id)
|
||||
|
||||
card.reset()
|
||||
match card.rank:
|
||||
Card.Rank.RANK_0:
|
||||
print("Rank 3 Burned permanently")
|
||||
pass
|
||||
Card.Rank.RANK_1:
|
||||
print("Rank 3 Burned until next match")
|
||||
pass
|
||||
Card.Rank.RANK_2:
|
||||
print("Rank 2 add to Discard")
|
||||
discard.append(card)
|
||||
pass
|
||||
Card.Rank.RANK_3:
|
||||
addCardToDeck(card)
|
||||
print("Rank 3 add to Deck")
|
||||
pass
|
||||
else:
|
||||
var piece_node = instance_from_id(piece_id)
|
||||
if is_instance_valid(piece_node):
|
||||
piece_node.update_appearance()
|
||||
for piece_id in expired_cards:
|
||||
attached_cards.erase(piece_id)
|
||||
var piece_node = instance_from_id(piece_id)
|
||||
if is_instance_valid(piece_node):
|
||||
piece_node.update_appearance()
|
||||
|
||||
# print("updateCardDurations", attached_cards)
|
||||
|
||||
match card.rank:
|
||||
Card.Rank.RANK_0:
|
||||
print("Rank 3 Burned permanently")
|
||||
pass
|
||||
Card.Rank.RANK_1:
|
||||
print("Rank 3 Burned until next match")
|
||||
pass
|
||||
Card.Rank.RANK_2:
|
||||
print("Rank 2 add to Discard")
|
||||
discard.append(card)
|
||||
pass
|
||||
Card.Rank.RANK_3:
|
||||
deck.append(card)
|
||||
print("Rank 3 add to Deck")
|
||||
pass
|
||||
else:
|
||||
var piece_node = instance_from_id(piece_id)
|
||||
if is_instance_valid(piece_node):
|
||||
piece_node.update_appearance()
|
||||
for piece_id in expired_cards:
|
||||
attached_cards.erase(piece_id)
|
||||
var piece_node = instance_from_id(piece_id)
|
||||
if is_instance_valid(piece_node):
|
||||
piece_node.update_appearance()
|
||||
|
||||
# print("updateCardDurations", attached_cards)
|
||||
|
||||
# Shop functionality
|
||||
func getShopCards(num_cards: int = 3) -> Array:
|
||||
var shop_cards = []
|
||||
# Generate random shop selection
|
||||
#for i in range(num_cards):
|
||||
#var card = generate_random_card()
|
||||
#shop_cards.append({
|
||||
#"card": card,
|
||||
#"cost": calculateCardCost(card)
|
||||
#})
|
||||
return shop_cards
|
||||
var shop_cards = []
|
||||
# Generate random shop selection
|
||||
#for i in range(num_cards):
|
||||
#var card = generate_random_card()
|
||||
#shop_cards.append({
|
||||
#"card": card,
|
||||
#"cost": calculateCardCost(card)
|
||||
#})
|
||||
return shop_cards
|
||||
|
||||
func calculateCardCost(card: Card) -> int:
|
||||
var base_cost = Utils.CardPrices[card.rank]
|
||||
# Add modifiers based on card effects
|
||||
return base_cost
|
||||
var base_cost = CARD_BASE_COSTS[card.rank]
|
||||
# Add modifiers based on card effects
|
||||
return base_cost
|
||||
|
||||
func upgradeCard(card: Card) -> bool:
|
||||
# Implement card upgrading logic
|
||||
return false
|
||||
|
||||
|
||||
func create_new_card_instance(template_card: Card) -> Card:
|
||||
var new_card = null
|
||||
|
||||
var script = template_card.get_script()
|
||||
|
||||
if script:
|
||||
new_card = script.new()
|
||||
else:
|
||||
print("Warning: Could not get script from card: " + template_card.cardName)
|
||||
|
||||
var class_list = ProjectSettings.get_global_class_list()
|
||||
var card_class_name = template_card.get_class()
|
||||
|
||||
for class_info in class_list:
|
||||
if class_info["class"] == card_class_name:
|
||||
var card_script = load(class_info["path"])
|
||||
if card_script:
|
||||
new_card = card_script.new()
|
||||
break
|
||||
|
||||
return new_card
|
||||
# Implement card upgrading logic
|
||||
return false
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
uid://bdwsolgdmo51y
|
||||
|
|
@ -1,286 +0,0 @@
|
|||
extends Node
|
||||
class_name FairyStockfishVariantGenerator
|
||||
|
||||
const STANDARD_PIECE_CHARS = "PNBRQK"
|
||||
const files = "abcdefghijkl"
|
||||
const VARIANT_CHARS = "ACDEFGHIJLMOSTVWXYZ"
|
||||
|
||||
func generate_variant_string(chess_game: ChessGame) -> String:
|
||||
var width = chess_game.boardXSize
|
||||
var height = chess_game.boardYSize
|
||||
var prefix = "chessbuilder" + str(width) + "x" + str(height)
|
||||
|
||||
var piece_data = gather_piece_data(chess_game)
|
||||
var piece_definitions = piece_data.definitions
|
||||
var piece_mappings = piece_data.mappings
|
||||
var used_variant_chars = piece_data.used_chars
|
||||
|
||||
var variant_string = "[" + prefix + ":chess]\n"
|
||||
|
||||
# Build the pieceToCharTable with all used variant characters
|
||||
var char_table = build_piece_char_table(used_variant_chars)
|
||||
variant_string += "pieceToCharTable = " + char_table + "\n"
|
||||
|
||||
variant_string += "maxRank = " + str(height) + "\n"
|
||||
variant_string += "maxFile = " + files[width - 1] + "\n"
|
||||
|
||||
var current_fen = chess_game.getCurrentFen()
|
||||
variant_string += "startFen = " + current_fen + "\n\n"
|
||||
|
||||
# Add wall types
|
||||
variant_string += "walltype = * # Duck wall\n"
|
||||
variant_string += "walltype = @ # Stone wall\n\n"
|
||||
|
||||
# Add walling rules
|
||||
variant_string += "wallingRule = static\n"
|
||||
variant_string += "wallOrMove = false\n"
|
||||
variant_string += "mobilityRegion = *:@\n"
|
||||
variant_string += "prohibitedMove = *@\n\n"
|
||||
|
||||
# Add the piece definitions
|
||||
variant_string += "# Custom piece movement definitions\n"
|
||||
for piece_key in piece_definitions:
|
||||
variant_string += "piece " + piece_key + " = " + piece_definitions[piece_key] + "\n"
|
||||
|
||||
# Add pieceDrops if needed for capturing
|
||||
variant_string += "\n# Allow capturing with custom pieces\n"
|
||||
variant_string += "pieceDrops = true\n"
|
||||
|
||||
return variant_string
|
||||
|
||||
# Build a complete pieceToCharTable that includes all variant characters
|
||||
func build_piece_char_table(used_variant_chars: Array) -> String:
|
||||
var char_table = STANDARD_PIECE_CHARS
|
||||
|
||||
for variant_char in used_variant_chars:
|
||||
char_table += variant_char.to_upper()
|
||||
|
||||
char_table += "..*@..........."
|
||||
|
||||
# Add lowercase versions
|
||||
char_table += STANDARD_PIECE_CHARS.to_lower()
|
||||
|
||||
for variant_char in used_variant_chars:
|
||||
char_table += variant_char.to_lower()
|
||||
|
||||
char_table += "..*@..........."
|
||||
|
||||
return char_table
|
||||
|
||||
# Gather all piece data from the current game state
|
||||
func gather_piece_data(chess_game: ChessGame) -> Dictionary:
|
||||
# Dictionary to track unique movement patterns
|
||||
# Key format: "piece_char:color:movement_string"
|
||||
var unique_movements = {}
|
||||
|
||||
var modified_piece_types = {}
|
||||
|
||||
# First pass: collect all unique movement patterns
|
||||
for y in range(chess_game.boardYSize):
|
||||
for x in range(chess_game.boardXSize):
|
||||
var container = chess_game.boardContainer.get_node(str(x) + "-" + str(y)) as PieceContainer
|
||||
if container && container.has_piece():
|
||||
var piece = container.get_piece() as Pawn
|
||||
|
||||
if piece.current_movement_string != piece.original_movement_string:
|
||||
var piece_char = get_piece_char(piece)
|
||||
var color_key = "white" if piece.Item_Color == 0 else "black"
|
||||
var piece_key = piece_char + ":" + color_key
|
||||
|
||||
if !modified_piece_types.has(piece_key):
|
||||
modified_piece_types[piece_key] = true
|
||||
|
||||
var movement_key = piece_key + ":" + piece.current_movement_string
|
||||
|
||||
if !unique_movements.has(movement_key):
|
||||
unique_movements[movement_key] = {
|
||||
"piece_char": piece_char,
|
||||
"color": color_key,
|
||||
"betza_notation": piece.current_movement_string,
|
||||
"fairy_notation": convert_betza_to_fairy_stockfish(piece.current_movement_string),
|
||||
"positions": []
|
||||
}
|
||||
|
||||
unique_movements[movement_key].positions.append(str(x) + "-" + str(y))
|
||||
|
||||
var variant_char_index = 0
|
||||
var piece_definitions = {}
|
||||
var piece_mappings = {}
|
||||
var used_variant_chars = []
|
||||
|
||||
for movement_key in unique_movements:
|
||||
var movement_data = unique_movements[movement_key]
|
||||
var piece_char = movement_data.piece_char
|
||||
var color = movement_data.color
|
||||
var fs_notation = movement_data.fairy_notation
|
||||
|
||||
var variant_char
|
||||
if variant_char_index < VARIANT_CHARS.length():
|
||||
variant_char = VARIANT_CHARS[variant_char_index]
|
||||
used_variant_chars.append(variant_char)
|
||||
variant_char_index += 1
|
||||
else:
|
||||
# If we run out of variant chars, we'll need to add more or implement another solution
|
||||
print("Warning: Ran out of variant characters for piece definitions")
|
||||
break
|
||||
|
||||
# Store the piece definition
|
||||
var definition_key = variant_char.to_upper() if color == "white" else variant_char.to_lower()
|
||||
piece_definitions[definition_key] = fs_notation
|
||||
|
||||
for pos in movement_data.positions:
|
||||
piece_mappings[pos] = definition_key
|
||||
|
||||
return {
|
||||
"definitions": piece_definitions,
|
||||
"mappings": piece_mappings,
|
||||
"used_chars": used_variant_chars
|
||||
}
|
||||
|
||||
func get_piece_char(piece: Pawn) -> String:
|
||||
match piece.name:
|
||||
"Pawn": return "P"
|
||||
"Knight": return "N"
|
||||
"Bishop": return "B"
|
||||
"Rook": return "R"
|
||||
"Queen": return "Q"
|
||||
"King": return "K"
|
||||
_: return "P" # Default to pawn
|
||||
|
||||
func convert_betza_to_fairy_stockfish(betza_notation: String) -> String:
|
||||
var atoms = parse_betza_notation(betza_notation)
|
||||
var fairy_stockfish_moves = []
|
||||
|
||||
for atom in atoms:
|
||||
var move_type = atom.type
|
||||
var modifiers = atom.modifiers
|
||||
var range_limit = atom.range
|
||||
|
||||
var move_only = "m" in modifiers
|
||||
var capture_only = "c" in modifiers
|
||||
|
||||
# Process direction modifiers
|
||||
var direction_modifiers = []
|
||||
for modifier in modifiers:
|
||||
if modifier.length() > 1 || ["f", "b", "l", "r"].has(modifier):
|
||||
for char in modifier:
|
||||
if ["f", "b", "l", "r"].has(char):
|
||||
direction_modifiers.append(char)
|
||||
|
||||
var fs_move = ""
|
||||
|
||||
match move_type:
|
||||
"W": fs_move += "W" # Wazir (orthogonal step)
|
||||
"F": fs_move += "F" # Ferz (diagonal step)
|
||||
"R": fs_move += "R" # Rook (orthogonal slider)
|
||||
"B": fs_move += "B" # Bishop (diagonal slider)
|
||||
"N": fs_move += "N" # Knight
|
||||
"K": fs_move += "K" # King (one step in any direction)
|
||||
"Q": fs_move += "Q" # Queen (combination of R and B)
|
||||
|
||||
if range_limit > 0:
|
||||
fs_move += str(range_limit)
|
||||
|
||||
if direction_modifiers.size() > 0:
|
||||
fs_move += "/"
|
||||
for dir in direction_modifiers:
|
||||
match dir:
|
||||
"f": fs_move += "f"
|
||||
"b": fs_move += "b"
|
||||
"l": fs_move += "l"
|
||||
"r": fs_move += "r"
|
||||
|
||||
if move_only:
|
||||
fs_move += "m"
|
||||
elif capture_only:
|
||||
fs_move += "c"
|
||||
|
||||
fairy_stockfish_moves.append(fs_move)
|
||||
var ret_str = ""
|
||||
for moves in fairy_stockfish_moves:
|
||||
ret_str += str(moves) + " "
|
||||
|
||||
return ret_str
|
||||
|
||||
# Parse Betza notation into atoms
|
||||
func parse_betza_notation(notation: String) -> Array:
|
||||
var atoms = []
|
||||
var i = 0
|
||||
|
||||
while i < notation.length():
|
||||
var atom = {
|
||||
"type": "",
|
||||
"modifiers": [],
|
||||
"range": -1 # -1 means unlimited
|
||||
}
|
||||
|
||||
while i < notation.length() && ["m", "c"].has(notation[i]):
|
||||
atom.modifiers.append(notation[i])
|
||||
i += 1
|
||||
|
||||
var directions = ""
|
||||
while i < notation.length() && ["f", "b", "l", "r"].has(notation[i]):
|
||||
directions += notation[i]
|
||||
i += 1
|
||||
|
||||
if directions:
|
||||
atom.modifiers.append(directions)
|
||||
|
||||
# Get the atom type
|
||||
if i < notation.length():
|
||||
atom.type = notation[i]
|
||||
i += 1
|
||||
|
||||
# Check for range specification
|
||||
var range_str = ""
|
||||
while i < notation.length() && notation[i].is_valid_int():
|
||||
range_str += notation[i]
|
||||
i += 1
|
||||
|
||||
if range_str:
|
||||
atom.range = int(range_str)
|
||||
|
||||
atoms.append(atom)
|
||||
|
||||
return atoms
|
||||
|
||||
# Generate a modified FEN string with custom piece characters
|
||||
func generate_modified_fen(chess_game: ChessGame, piece_mappings: Dictionary) -> String:
|
||||
var fen_parts = chess_game.getCurrentFen().split(" ")
|
||||
var board_part = fen_parts[0]
|
||||
var rows = board_part.split("/")
|
||||
var new_rows = []
|
||||
|
||||
for y in range(rows.size()):
|
||||
var new_row = ""
|
||||
var x = 0
|
||||
|
||||
for c in rows[y]:
|
||||
if c.is_valid_int():
|
||||
# Handle empty squares
|
||||
new_row += c
|
||||
x += int(c)
|
||||
else:
|
||||
var pos = str(x) + "-" + str(y)
|
||||
if piece_mappings.has(pos):
|
||||
new_row += piece_mappings[pos]
|
||||
else:
|
||||
new_row += c
|
||||
x += 1
|
||||
|
||||
new_rows.append(new_row)
|
||||
|
||||
fen_parts[0] = new_rows.join("/")
|
||||
return fen_parts.join(" ")
|
||||
|
||||
# Save the variant definition to a file
|
||||
func save_variant_to_file(variant_string: String) -> bool:
|
||||
print("VARIANT ", variant_string)
|
||||
ServerManager.write_variant(variant_string)
|
||||
return true
|
||||
|
||||
# Helper function to get a complete variant definition for the current game state
|
||||
func generate_and_save_variant(chess_game: ChessGame) -> String:
|
||||
var variant_string = generate_variant_string(chess_game)
|
||||
save_variant_to_file(variant_string)
|
||||
return variant_string
|
||||
|
|
@ -1 +0,0 @@
|
|||
uid://boryt6vnd0icx
|
||||
|
|
@ -1,279 +0,0 @@
|
|||
# ServerManager.gd
|
||||
extends Node
|
||||
|
||||
var server_path: String = ""
|
||||
var log_dir: String = ""
|
||||
var variant_dir : String = ""
|
||||
var log_string: String = ""
|
||||
var running := false
|
||||
var server_process_id: int = -50
|
||||
var request_timer: Timer
|
||||
var server_pinger: Timer
|
||||
var ping_http: HTTPRequest
|
||||
|
||||
var server_url = "http://localhost:27531"
|
||||
|
||||
|
||||
func write_variant(variant_string: String):
|
||||
|
||||
var file = FileAccess.open(variant_dir, FileAccess.WRITE_READ)
|
||||
var error = FileAccess.get_open_error()
|
||||
|
||||
if error != OK:
|
||||
# print("Failed to open log file. Error code: ", error)
|
||||
return
|
||||
|
||||
if file == null:
|
||||
# print("File handle is null")
|
||||
return
|
||||
|
||||
file.store_string(variant_string)
|
||||
|
||||
var write_error = FileAccess.get_open_error()
|
||||
if write_error != OK:
|
||||
print("Failed to write to log file. Error code: ", write_error)
|
||||
|
||||
file.close()
|
||||
|
||||
|
||||
func write_log(message: String):
|
||||
# First check if path is valid
|
||||
# print("Attempting to write to: ", log_dir)
|
||||
|
||||
var file = FileAccess.open(log_dir, FileAccess.WRITE_READ)
|
||||
var error = FileAccess.get_open_error()
|
||||
|
||||
if error != OK:
|
||||
# print("Failed to open log file. Error code: ", error)
|
||||
return
|
||||
|
||||
if file == null:
|
||||
# print("File handle is null")
|
||||
return
|
||||
|
||||
var timestamp = Time.get_datetime_string_from_system()
|
||||
var log_message = "[" + timestamp + "] " + message + "\n"
|
||||
log_string += log_message
|
||||
print("Writing message: ", log_message)
|
||||
file.store_string(log_string)
|
||||
|
||||
var write_error = FileAccess.get_open_error()
|
||||
if write_error != OK:
|
||||
print("Failed to write to log file. Error code: ", write_error)
|
||||
|
||||
file.close()
|
||||
func _ready():
|
||||
server_path = extract_server_files() + "/ChessEngines/fairy-chess-server"
|
||||
setup_logging()
|
||||
setup_variant()
|
||||
check_server_files(server_path)
|
||||
start_server()
|
||||
get_tree().set_auto_accept_quit(false)
|
||||
setup_server_pinger()
|
||||
|
||||
func setup_server_pinger():
|
||||
server_pinger = Timer.new()
|
||||
add_child(server_pinger)
|
||||
server_pinger.wait_time = 60.0
|
||||
server_pinger.one_shot = false
|
||||
server_pinger.timeout.connect(self._on_ping_timer_timeout)
|
||||
ping_http = HTTPRequest.new()
|
||||
add_child(ping_http)
|
||||
ping_http.request_completed.connect(self._on_ping_completed)
|
||||
|
||||
server_pinger.start()
|
||||
|
||||
|
||||
func _on_ping_timer_timeout():
|
||||
write_log("Pinging server health endpoint...")
|
||||
var error = ping_http.request(server_url + "/health")
|
||||
if error != OK:
|
||||
write_log("Error sending ping request: " + str(error))
|
||||
|
||||
|
||||
func _on_ping_completed(result: int, response_code: int, headers: PackedStringArray, body: PackedByteArray):
|
||||
if result == HTTPRequest.RESULT_SUCCESS:
|
||||
write_log("Server ping successful")
|
||||
else:
|
||||
write_log("Server ping failed - may need to restart server")
|
||||
|
||||
|
||||
func write_log_dir_contents(path: String):
|
||||
var dir = DirAccess.open(path)
|
||||
if dir:
|
||||
write_log("Directory contents of: " + path)
|
||||
dir.list_dir_begin()
|
||||
var file_name = dir.get_next()
|
||||
while file_name != "":
|
||||
write_log(" - " + file_name)
|
||||
file_name = dir.get_next()
|
||||
else:
|
||||
write_log("ERROR: Could not open directory: " + path)
|
||||
func check_server_files(path: String):
|
||||
write_log("Checking server files...")
|
||||
write_log(path)
|
||||
|
||||
var index_path = path.path_join("index.js")
|
||||
if FileAccess.file_exists(index_path):
|
||||
write_log("index.js exists")
|
||||
var file = FileAccess.open(index_path, FileAccess.READ)
|
||||
if file:
|
||||
write_log("index.js contents:")
|
||||
# write_log(file.get_as_text())
|
||||
else:
|
||||
write_log("ERROR: Could not read index.js")
|
||||
else:
|
||||
write_log("ERROR: index.js does not exist at path: " + index_path)
|
||||
|
||||
func get_globalDir() -> String:
|
||||
|
||||
if OS.get_name() == "Linux":
|
||||
return OS.get_environment("HOME").path_join(".local/share/ChessBuilder")
|
||||
elif OS.get_name() == "Windows":
|
||||
return OS.get_environment("APPDATA").path_join("Roaming/ChessBuilder")
|
||||
else: # macOS
|
||||
return OS.get_environment("HOME").path_join("Library/ChessBuilder")
|
||||
|
||||
func setup_logging():
|
||||
var l_dir = get_globalDir() + "/logs"
|
||||
|
||||
# Create directory if it doesn't exist
|
||||
DirAccess.make_dir_recursive_absolute(l_dir)
|
||||
|
||||
log_dir = l_dir.path_join("godot-chess.log")
|
||||
write_log("ServerManager initialized")
|
||||
func setup_variant():
|
||||
var l_dir = get_globalDir() + "/variant"
|
||||
|
||||
# Create directory if it doesn't exist
|
||||
DirAccess.make_dir_recursive_absolute(l_dir)
|
||||
|
||||
variant_dir = l_dir.path_join("custom_variant.ini")
|
||||
write_log("ServerManager Variants initialized")
|
||||
|
||||
func _exit_tree():
|
||||
stop_server()
|
||||
get_tree().quit()
|
||||
|
||||
func _notification(what):
|
||||
if what == NOTIFICATION_WM_CLOSE_REQUEST:
|
||||
stop_server()
|
||||
get_tree().quit()
|
||||
|
||||
|
||||
func start_server() -> bool:
|
||||
if running:
|
||||
return true
|
||||
|
||||
write_log("Starting chess server... " + server_path)
|
||||
var http_request = HTTPRequest.new()
|
||||
add_child(http_request)
|
||||
http_request.request_completed.connect(self._on_init_request_completed)
|
||||
|
||||
request_timer = Timer.new()
|
||||
add_child(request_timer)
|
||||
request_timer.wait_time = 2.0 # 2 seconds
|
||||
request_timer.one_shot = true
|
||||
request_timer.timeout.connect(self._on_request_timeout)
|
||||
request_timer.start()
|
||||
http_request.request(server_url + "/health")
|
||||
|
||||
|
||||
return true
|
||||
|
||||
|
||||
func _on_request_timeout():
|
||||
write_log("Request timed out, starting server manually")
|
||||
# Clean up timer
|
||||
request_timer.queue_free()
|
||||
request_timer = null
|
||||
write_log("HTTP Request failed, starting server process")
|
||||
write_log(server_path + "/index.js")
|
||||
server_process_id = OS.create_process("node", [server_path + "/index.js"])
|
||||
write_log("SERVER PATH " + server_path)
|
||||
if server_process_id <= 0:
|
||||
printerr("Failed to start server")
|
||||
write_log("ERROR: Failed to start server, process ID: " + str(server_process_id))
|
||||
|
||||
return false
|
||||
|
||||
running = true
|
||||
write_log("Chess server started with PID: " + str(server_process_id))
|
||||
return
|
||||
|
||||
func _on_init_request_completed(result: int, response_code: int, headers: PackedStringArray, body: PackedByteArray):
|
||||
write_log("*****************_on_init_request_completed************")
|
||||
if request_timer:
|
||||
request_timer.stop()
|
||||
request_timer.queue_free()
|
||||
request_timer = null
|
||||
var json = JSON.new()
|
||||
json.parse(body.get_string_from_utf8())
|
||||
var response = json.get_data()
|
||||
write_log("Init request completed")
|
||||
write_log("Result: " + str(response))
|
||||
if response == null:
|
||||
write_log("HTTP Request failed, starting server process")
|
||||
write_log(server_path + "/index.js")
|
||||
server_process_id = OS.create_process("node", [server_path + "/index.js"])
|
||||
write_log("SERVER PATH " + server_path)
|
||||
if server_process_id <= 0:
|
||||
printerr("Failed to start server")
|
||||
write_log("ERROR: Failed to start server, process ID: " + str(server_process_id))
|
||||
|
||||
return false
|
||||
|
||||
running = true
|
||||
write_log("Chess server started with PID: " + str(server_process_id))
|
||||
return
|
||||
running = true
|
||||
if response and response.status != "ok":
|
||||
print("Server error : ", response_code, json.parse(body.get_string_from_utf8()),)
|
||||
return
|
||||
|
||||
static func copy_directory_recursively(p_from: String, p_to: String) -> void:
|
||||
# Create target directory if it doesn't exist
|
||||
if not DirAccess.dir_exists_absolute(p_to):
|
||||
DirAccess.make_dir_recursive_absolute(p_to)
|
||||
|
||||
# Open source directory
|
||||
var dir = DirAccess.open(p_from)
|
||||
if dir:
|
||||
# Begin listing directory contents
|
||||
dir.list_dir_begin()
|
||||
var file_name = dir.get_next()
|
||||
|
||||
# Iterate through directory contents
|
||||
while file_name != "":
|
||||
# write_log(file_name + " isDIr? " + str(dir.current_is_dir()))
|
||||
if dir.current_is_dir():
|
||||
# Recursively copy subdirectories
|
||||
copy_directory_recursively(p_from.path_join(file_name), p_to.path_join(file_name))
|
||||
else:
|
||||
# Copy files
|
||||
dir.copy(p_from.path_join(file_name), p_to.path_join(file_name))
|
||||
file_name = dir.get_next()
|
||||
|
||||
func extract_server_files() -> String:
|
||||
write_log("Extracting server files from PCK...")
|
||||
var dir = get_globalDir() + "/Assets";
|
||||
copy_directory_recursively("res://Assets/", dir)
|
||||
return dir
|
||||
|
||||
|
||||
func stop_server():
|
||||
if not running:
|
||||
return
|
||||
|
||||
write_log("Stopping chess server...")
|
||||
# Send a stop request to the server
|
||||
var headers = ["Content-Type: application/json"]
|
||||
var http = HTTPClient.new()
|
||||
var data = JSON.stringify({"command": "shutdown"})
|
||||
http.request(HTTPClient.METHOD_POST, "/shutdown", headers, data)
|
||||
|
||||
running = false
|
||||
write_log("Chess server stopped")
|
||||
|
||||
func is_server_running() -> bool:
|
||||
return running
|
||||
|
|
@ -1 +0,0 @@
|
|||
uid://dae6g5472sh26
|
||||
|
|
@ -1,306 +0,0 @@
|
|||
# StockfishClient.gd
|
||||
extends Node
|
||||
|
||||
var server_url = "http://localhost:27531"
|
||||
var http_request: HTTPRequest
|
||||
var running := false
|
||||
var generated_move: Dictionary = {}
|
||||
var move_time: int = 1000
|
||||
var game: ChessGame
|
||||
|
||||
var symbol_from_piece_type := {
|
||||
"PAWN": "p", "KNIGHT": "n", "BISHOP": "b",
|
||||
"ROOK": "r", "QUEEN": "q", "KING": "k"
|
||||
}
|
||||
|
||||
var piece_type_from_symbol := {
|
||||
"p": "PAWN", "n": "KNIGHT", "b": "BISHOP",
|
||||
"r": "ROOK", "q": "QUEEN", "k": "KING"
|
||||
}
|
||||
|
||||
func _init():
|
||||
print("STARTING SERVER CLIENT")
|
||||
http_request = HTTPRequest.new()
|
||||
add_child(http_request)
|
||||
http_request.request_completed.connect(self._on_request_completed)
|
||||
|
||||
func connect_to_engine(_path: String, g: ChessGame) -> bool:
|
||||
game = g
|
||||
# Wait for server to be ready
|
||||
var retries = 5
|
||||
var delay = 1.0 # seconds
|
||||
|
||||
while retries > 0:
|
||||
if ServerManager.is_server_running():
|
||||
print("**************SERVER RUNNING ****************")
|
||||
running = true
|
||||
# start_game(2100)
|
||||
return true
|
||||
|
||||
await get_tree().create_timer(delay).timeout
|
||||
retries -= 1
|
||||
print("**************ATTEMPTING SERVER CONNECTION****************")
|
||||
|
||||
return false
|
||||
|
||||
|
||||
func start_game(elo: int) -> void:
|
||||
start_board(elo)
|
||||
load_fen(game.getCurrentFen())
|
||||
|
||||
|
||||
|
||||
func disconnect_engine():
|
||||
running = false
|
||||
|
||||
func stop_calculating():
|
||||
if not running:
|
||||
return
|
||||
# Send stop command to server
|
||||
http_request.request(server_url + "/stop", [], HTTPClient.METHOD_POST)
|
||||
|
||||
func load_fen(fen: String):
|
||||
if not running:
|
||||
return
|
||||
# var http_request = HTTPRequest.new()
|
||||
# add_child(http_request)
|
||||
# http_request.request_completed.connect(self._on_request_completed)
|
||||
var headers = ["Content-Type: application/json"]
|
||||
var body = JSON.new().stringify({
|
||||
"fen": fen
|
||||
})
|
||||
print(server_url + "/position")
|
||||
print(body)
|
||||
|
||||
http_request.request(server_url + "/position", headers, HTTPClient.METHOD_POST, body)
|
||||
await http_request.request_completed
|
||||
func start_board(elo: int, variant: String = "8x8"):
|
||||
if not running:
|
||||
return
|
||||
var headers = ["Content-Type: application/json"]
|
||||
var body = JSON.new().stringify({
|
||||
"variant": 'chess'
|
||||
})
|
||||
print(server_url + "/new")
|
||||
print(body)
|
||||
http_request.request(server_url + "/new", headers, HTTPClient.METHOD_POST, body)
|
||||
await http_request.request_completed
|
||||
setElo(elo, variant)
|
||||
|
||||
func clear_game( ):
|
||||
if not running:
|
||||
return
|
||||
var headers = ["Content-Type: application/json"]
|
||||
var body = JSON.new().stringify({
|
||||
"variant": 'chess'
|
||||
})
|
||||
print(server_url + "/new")
|
||||
print(body)
|
||||
|
||||
var request: HTTPRequest = HTTPRequest.new()
|
||||
request.request(server_url + "/new", headers, HTTPClient.METHOD_POST, body)
|
||||
await request.request_completed
|
||||
func _exit_tree():
|
||||
ServerManager.stop_server()
|
||||
disconnect_engine();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
func get_globalDir() -> String:
|
||||
|
||||
if OS.get_name() == "Linux":
|
||||
return OS.get_environment("HOME").path_join(".local/share/ChessBuilder")
|
||||
elif OS.get_name() == "Windows":
|
||||
return OS.get_environment("APPDATA").path_join("Roaming/ChessBuilder")
|
||||
else: # macOS
|
||||
return OS.get_environment("HOME").path_join("Library/ChessBuilder")
|
||||
|
||||
|
||||
func setVariant(variant: String = "8x8"):
|
||||
if not running:
|
||||
return
|
||||
print("####################################")
|
||||
print("####################################")
|
||||
print("chessbuilder" + variant)
|
||||
print("####################################")
|
||||
print("####################################")
|
||||
print("####################################")
|
||||
var headers = ["Content-Type: application/json"]
|
||||
var data = {
|
||||
"options": [
|
||||
{
|
||||
"name": "VariantPath",
|
||||
"value": get_globalDir() + "/variant" + "/custom_variant.ini"
|
||||
},
|
||||
{
|
||||
"name": "UCI_Variant",
|
||||
"value": "chessbuilder" + variant
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
var body = JSON.new().stringify(data)
|
||||
|
||||
# Request engine move
|
||||
http_request.request(
|
||||
server_url + "/setoptions",
|
||||
headers,
|
||||
HTTPClient.METHOD_POST,
|
||||
body
|
||||
)
|
||||
|
||||
func setElo(elo: int = 1350, variant: String = "8x8") -> void:
|
||||
if not running:
|
||||
return
|
||||
var headers = ["Content-Type: application/json"]
|
||||
var data = {
|
||||
"options": [
|
||||
{
|
||||
"name": "VariantPath",
|
||||
"value": get_globalDir() + "/variant" + "/custom_variant.ini"
|
||||
},
|
||||
{
|
||||
"name": "UCI_LimitStrength",
|
||||
"value": "true"
|
||||
},
|
||||
{
|
||||
"name": "UCI_Elo",
|
||||
"value": str(elo) # Convert int to string
|
||||
},
|
||||
{
|
||||
"name": "UCI_Variant",
|
||||
"value": "chessbuilder" + variant
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
var body = JSON.new().stringify(data)
|
||||
|
||||
# Request engine move
|
||||
var elo_req = HTTPRequest.new()
|
||||
add_child(elo_req)
|
||||
elo_req.request_completed.connect(self._on_request_completed)
|
||||
elo_req.request(
|
||||
server_url + "/setoptions",
|
||||
headers,
|
||||
HTTPClient.METHOD_POST,
|
||||
body
|
||||
)
|
||||
await elo_req.request_completed
|
||||
http_request.request(
|
||||
server_url + "/position",
|
||||
headers,
|
||||
HTTPClient.METHOD_POST,
|
||||
JSON.new().stringify({"start": true})
|
||||
)
|
||||
|
||||
func generateMove(think_time_ms: int = 1000) -> void:
|
||||
if not running:
|
||||
return
|
||||
print("&&&&&&&&&&&&&&&GENERATING MOVE&&&&&&&&&&&&&&&&&&&&&&", str(game.generate_variant_aware_fen()))
|
||||
|
||||
move_time = think_time_ms
|
||||
|
||||
var headers = ["Content-Type: application/json"]
|
||||
var body = JSON.stringify({
|
||||
"movetime": think_time_ms,
|
||||
"depth": 15,
|
||||
"fen": str(game.generate_variant_aware_fen())
|
||||
})
|
||||
|
||||
# Request engine move
|
||||
var move_request = HTTPRequest.new()
|
||||
add_child(move_request)
|
||||
move_request.request_completed.connect(self._on_bestmove_completed)
|
||||
var error = move_request.request(
|
||||
server_url + "/enginemove",
|
||||
headers,
|
||||
HTTPClient.METHOD_POST,
|
||||
body
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
func getGeneratedMove() -> Dictionary:
|
||||
var move = generated_move.duplicate()
|
||||
generated_move.clear()
|
||||
return move
|
||||
|
||||
func send_move(move_data: Dictionary):
|
||||
if not running:
|
||||
return
|
||||
|
||||
var headers = ["Content-Type: application/json"]
|
||||
var move_str = from_move_to_string(move_data)
|
||||
var body = JSON.stringify({
|
||||
"move": move_str
|
||||
})
|
||||
|
||||
http_request.request(server_url + "/move", headers, HTTPClient.METHOD_POST, body)
|
||||
|
||||
# Helper functions
|
||||
func from_move_to_string(move_data: Dictionary) -> String:
|
||||
# Same implementation as original
|
||||
var board_size = 8 # Standard chess board
|
||||
|
||||
var source_i = move_data.source % board_size
|
||||
var source_j = move_data.source / board_size
|
||||
var target_i = move_data.target % board_size
|
||||
var target_j = move_data.target / board_size
|
||||
|
||||
var letters = "abcdefgh"
|
||||
var str_move = "%s%d%s%d" % [
|
||||
letters[source_i],
|
||||
board_size - source_j,
|
||||
letters[target_i],
|
||||
board_size - target_j
|
||||
]
|
||||
|
||||
if move_data.get("flags", "") == "PROMOTION":
|
||||
str_move += symbol_from_piece_type[move_data.promotion_piece].to_lower()
|
||||
|
||||
return str_move
|
||||
|
||||
|
||||
|
||||
func _on_bestmove_completed(result: int, response_code: int, headers: PackedStringArray, body: PackedByteArray):
|
||||
print("^^^^^^^^^^^^^^^_on_bestmove_completed^^^^^^^^^^^^^^^^^")
|
||||
var json = JSON.new()
|
||||
json.parse(body.get_string_from_utf8())
|
||||
var response = json.get_data()
|
||||
# Will print the user agent string used by the HTTPRequest node (as recognized by httpbin.org).
|
||||
print(response)
|
||||
if response == null or result != HTTPRequest.RESULT_SUCCESS:
|
||||
print("HTTP Request failed")
|
||||
return
|
||||
|
||||
if response.status != "ok":
|
||||
print("Server error:", response_code, response,)
|
||||
return
|
||||
|
||||
|
||||
generated_move = {
|
||||
"move": response.move,
|
||||
"ponder": ""
|
||||
}
|
||||
|
||||
|
||||
|
||||
func _on_request_completed(result: int, response_code: int, headers: PackedStringArray, body: PackedByteArray):
|
||||
print("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^")
|
||||
var json = JSON.new()
|
||||
json.parse(body.get_string_from_utf8())
|
||||
var response = json.get_data()
|
||||
# Will print the user agent string used by the HTTPRequest node (as recognized by httpbin.org).
|
||||
print(response)
|
||||
if result != HTTPRequest.RESULT_SUCCESS:
|
||||
print("HTTP Request failed")
|
||||
return
|
||||
|
||||
if response.status != "ok":
|
||||
print("Server error:", response_code, json.parse(body.get_string_from_utf8()),)
|
||||
return
|
||||
|
|
@ -1 +0,0 @@
|
|||
uid://bnxxex87ax7hc
|
||||
|
|
@ -1,84 +0,0 @@
|
|||
class_name CameraController
|
||||
extends Node2D
|
||||
|
||||
signal zoom_changed(zoom_level)
|
||||
|
||||
# Camera movement properties
|
||||
@export var min_zoom: float = 0.5
|
||||
@export var max_zoom: float = 2.0
|
||||
@export var zoom_step: float = 0.1
|
||||
@export var pan_speed: float = 1.0
|
||||
|
||||
var board_container: Node
|
||||
var initial_board_position: Vector2
|
||||
var initial_board_size: Vector2
|
||||
|
||||
var current_zoom: float = 1.0
|
||||
var dragging: bool = false
|
||||
var drag_start_position: Vector2
|
||||
|
||||
func _init(target_board: Node) -> void:
|
||||
board_container = target_board
|
||||
|
||||
func _ready() -> void:
|
||||
initial_board_position = board_container.position
|
||||
if board_container.has_method("get_combined_minimum_size"):
|
||||
initial_board_size = board_container.get_combined_minimum_size()
|
||||
else:
|
||||
initial_board_size = board_container.size
|
||||
|
||||
current_zoom = 1.0
|
||||
board_container.scale = Vector2(current_zoom, current_zoom)
|
||||
|
||||
adjust_pivot()
|
||||
|
||||
func adjust_pivot() -> void:
|
||||
var center = initial_board_size / 2
|
||||
board_container.pivot_offset = center
|
||||
|
||||
func _input(event: InputEvent) -> void:
|
||||
if event is InputEventMouseButton:
|
||||
if event.button_index == MOUSE_BUTTON_WHEEL_UP:
|
||||
zoom_in(event.position)
|
||||
elif event.button_index == MOUSE_BUTTON_WHEEL_DOWN:
|
||||
zoom_out(event.position)
|
||||
elif event.button_index == MOUSE_BUTTON_MIDDLE:
|
||||
if event.pressed:
|
||||
start_drag(event.position)
|
||||
else:
|
||||
end_drag()
|
||||
|
||||
if event is InputEventMouseMotion and dragging:
|
||||
update_drag(event.position)
|
||||
|
||||
func zoom_in(mouse_pos: Vector2) -> void:
|
||||
if current_zoom < max_zoom:
|
||||
apply_zoom(current_zoom + zoom_step)
|
||||
|
||||
func zoom_out(mouse_pos: Vector2) -> void:
|
||||
if current_zoom > min_zoom:
|
||||
apply_zoom(current_zoom - zoom_step)
|
||||
|
||||
func apply_zoom(new_zoom: float) -> void:
|
||||
current_zoom = new_zoom
|
||||
board_container.scale = Vector2(current_zoom, current_zoom)
|
||||
|
||||
emit_signal("zoom_changed", current_zoom)
|
||||
|
||||
func start_drag(position: Vector2) -> void:
|
||||
dragging = true
|
||||
drag_start_position = position
|
||||
|
||||
func end_drag() -> void:
|
||||
dragging = false
|
||||
|
||||
func update_drag(position: Vector2) -> void:
|
||||
if dragging:
|
||||
var delta = position - drag_start_position
|
||||
board_container.position += delta * pan_speed
|
||||
drag_start_position = position
|
||||
|
||||
func reset_view() -> void:
|
||||
current_zoom = 1.0
|
||||
board_container.scale = Vector2(current_zoom, current_zoom)
|
||||
board_container.position = initial_board_position
|
||||
|
|
@ -1 +0,0 @@
|
|||
uid://bdbap6f4c4d5w
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1 +0,0 @@
|
|||
uid://cbcu68o863pfp
|
||||
|
|
@ -1,226 +0,0 @@
|
|||
extends Control
|
||||
class_name DeckManagerScreen
|
||||
|
||||
signal back_pressed
|
||||
signal deck_manager_visibility_changed(isvisible)
|
||||
|
||||
# Node references
|
||||
@onready var deckGrid = $MainContainer/GridScrollContainer/GridContainer
|
||||
@onready var bankContainer = $MainContainer/BankContainer/ScrollContainer/VBoxContainer
|
||||
@onready var backButton = $BackButton
|
||||
@onready var card_preview = $CardPreviewPanel
|
||||
|
||||
# Default deck size (can be overridden via options)
|
||||
var maxDeckSize = 40
|
||||
var deckManager = null
|
||||
var bankCards = [] # Cards available but not in deck
|
||||
|
||||
# The current deck
|
||||
var currentDeck = []
|
||||
var current_preview_card = null
|
||||
|
||||
func _ready():
|
||||
# Connect back button
|
||||
if backButton:
|
||||
backButton.connect("pressed", Callable(self, "_on_backButton_pressed"))
|
||||
|
||||
if card_preview:
|
||||
card_preview.visible = false
|
||||
|
||||
|
||||
func initialize(options = null):
|
||||
# Process options if provided
|
||||
if options:
|
||||
if options.has("max_deck_size") and options.max_deck_size is int:
|
||||
maxDeckSize = options.max_deck_size
|
||||
|
||||
|
||||
# Find the DeckManager instance
|
||||
var board = get_node_or_null("/root/Board") as ChessGame
|
||||
if board and "deckManager" in board:
|
||||
deckManager = board.deckManager
|
||||
print("Found deck manager:", deckManager)
|
||||
# if(!deckManager.deck):
|
||||
# deckManager.initializeStartingDeck()
|
||||
else:
|
||||
print("DeckManager not found on Board node")
|
||||
print("DECK MANAGER")
|
||||
|
||||
# Load cards from deck and bank
|
||||
loadCards()
|
||||
|
||||
# Set up the grid with empty card containers
|
||||
setupDeckGrid()
|
||||
|
||||
# Populate the bank with available cards
|
||||
populateBank()
|
||||
|
||||
func loadCards():
|
||||
var board = get_node_or_null("/root/Board") as ChessGame
|
||||
if board and "deckManager" in board:
|
||||
deckManager = board.deckManager
|
||||
print("Found deck manager:", deckManager)
|
||||
if deckManager:
|
||||
# Clone the deck to work with
|
||||
currentDeck = deckManager.deck.duplicate()
|
||||
bankCards = deckManager.bank.duplicate()
|
||||
else:
|
||||
# Fallback with empty collections if deck manager not found
|
||||
currentDeck = []
|
||||
bankCards = []
|
||||
print("Warning: DeckManager not found")
|
||||
|
||||
|
||||
func setupDeckGrid():
|
||||
# Clear existing children
|
||||
for child in deckGrid.get_children():
|
||||
child.queue_free()
|
||||
|
||||
# Calculate grid dimensions
|
||||
var cols = 4 #check screen to deteremine
|
||||
var rows = maxDeckSize / cols
|
||||
deckGrid.columns = cols
|
||||
|
||||
# Create card slots
|
||||
for i in range(maxDeckSize):
|
||||
var card_slot = preload("res://card_slot.tscn").instantiate()
|
||||
deckGrid.add_child(card_slot)
|
||||
|
||||
# Connect signals
|
||||
card_slot.connect("card_selected", Callable(self, "_on_deck_card_selected"))
|
||||
_connect_hover_signals(card_slot)
|
||||
|
||||
# Set card if available
|
||||
if i < currentDeck.size():
|
||||
card_slot.set_card(currentDeck[i])
|
||||
else:
|
||||
card_slot.clear()
|
||||
|
||||
func _connect_hover_signals(node):
|
||||
# Add hover signals for preview functionality
|
||||
if node.is_class("Control"):
|
||||
node.mouse_entered.connect(Callable(self, "_on_card_mouse_entered").bind(node))
|
||||
# node.mouse_exited.connect(Callable(self, "_on_card_mouse_exited").bind(node))
|
||||
|
||||
|
||||
func populateBank():
|
||||
# Clear existing children
|
||||
for child in bankContainer.get_children():
|
||||
child.queue_free()
|
||||
|
||||
# Add each bank card to the list
|
||||
for card in bankCards:
|
||||
var card_item = preload("res://card_bank_item.tscn").instantiate()
|
||||
bankContainer.add_child(card_item)
|
||||
card_item.set_card(card)
|
||||
card_item.connect("card_selected", Callable(self, "_on_bank_card_selected"))
|
||||
_connect_hover_signals(card_item)
|
||||
|
||||
func _on_deck_card_selected(card_slot, card):
|
||||
if card:
|
||||
# Remove card from deck
|
||||
var index = currentDeck.find(card)
|
||||
if index >= 0:
|
||||
currentDeck.remove_at(index)
|
||||
|
||||
# Add to bank
|
||||
bankCards.append(card)
|
||||
|
||||
# Update UI
|
||||
card_slot.clear()
|
||||
populateBank()
|
||||
if current_preview_card == card:
|
||||
hide_card_preview()
|
||||
|
||||
func _on_bank_card_selected(card_item, card):
|
||||
print("_on_bank_card_selected")
|
||||
# Find first empty slot in deck
|
||||
var empty_slot_index = -1
|
||||
for i in range(deckGrid.get_child_count()):
|
||||
var slot = deckGrid.get_child(i)
|
||||
if !slot.has_card():
|
||||
empty_slot_index = i
|
||||
break
|
||||
|
||||
if empty_slot_index >= 0 and currentDeck.size() < maxDeckSize:
|
||||
# Remove from bank
|
||||
var bank_index = bankCards.find(card)
|
||||
if bank_index >= 0:
|
||||
bankCards.remove_at(bank_index)
|
||||
|
||||
# Add to deck
|
||||
currentDeck.append(card)
|
||||
|
||||
# Update UI
|
||||
deckGrid.get_child(empty_slot_index).set_card(card)
|
||||
populateBank()
|
||||
|
||||
func _on_card_mouse_entered(node):
|
||||
var card = null
|
||||
|
||||
# Get the card from the node
|
||||
if node.has_method("has_card") and node.has_card():
|
||||
# This is a CardSlot
|
||||
card = node.current_card
|
||||
elif node.has_method("set_card") and node.current_card:
|
||||
# This is a CardBankItem
|
||||
card = node.current_card
|
||||
|
||||
if card:
|
||||
show_card_preview(card)
|
||||
|
||||
# func _on_card_mouse_exited(node):
|
||||
# # Add a short delay before hiding the preview
|
||||
# # This prevents flickering when moving between cards
|
||||
# await get_tree().create_timer(0.1).timeout
|
||||
|
||||
# # Only hide if we're not hovering over another card that shows the same preview
|
||||
# if current_preview_card:
|
||||
# hide_card_preview()
|
||||
|
||||
func show_card_preview(card):
|
||||
if card_preview and card:
|
||||
current_preview_card = card
|
||||
card_preview.preview_card(card)
|
||||
|
||||
func hide_card_preview():
|
||||
if card_preview:
|
||||
current_preview_card = null
|
||||
card_preview.hide_preview()
|
||||
|
||||
func saveDeck():
|
||||
if deckManager:
|
||||
# Save the current deck to the deck manager
|
||||
deckManager.deck = currentDeck.duplicate()
|
||||
deckManager.bank = bankCards.duplicate()
|
||||
|
||||
print("Deck saved with ", currentDeck.size(), " cards")
|
||||
|
||||
func _on_backButton_pressed():
|
||||
# Save changes before returning
|
||||
saveDeck()
|
||||
|
||||
# Emit signal to go back
|
||||
emit_signal("back_pressed")
|
||||
|
||||
# Hide this screen
|
||||
visible = false
|
||||
|
||||
|
||||
|
||||
|
||||
# Notification
|
||||
func _notification(what):
|
||||
if what == NOTIFICATION_VISIBILITY_CHANGED:
|
||||
_on_visibility_changed(visible)
|
||||
|
||||
func _on_visibility_changed(is_visible):
|
||||
|
||||
print("DeckManager visibility changed to: ", is_visible)
|
||||
if is_visible:
|
||||
loadCards()
|
||||
setupDeckGrid()
|
||||
else:
|
||||
print("DeckManager is now invisible")
|
||||
|
||||
emit_signal("deck_manager_visibility_changed", is_visible)
|
||||
|
|
@ -1 +0,0 @@
|
|||
uid://vxufsih5pgeu
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
class_name Game extends Node
|
||||
|
||||
|
||||
@onready var menuContainer = $MenuContainer
|
||||
@onready var chessGame = $ChessGame
|
||||
@onready var stateMachine = $StateMachine
|
||||
|
||||
func _ready():
|
||||
if menuContainer:
|
||||
menuContainer.visible = true
|
||||
|
||||
if chessGame:
|
||||
chessGame.visible = false
|
||||
|
||||
if menuContainer and menuContainer.has_signal("new_game_requested"):
|
||||
menuContainer.connect("new_game_requested", Callable(self, "_on_new_game_started"))
|
||||
|
||||
func _on_new_game_started():
|
||||
print("Starting new game...")
|
||||
|
||||
if chessGame:
|
||||
chessGame.visible = true
|
||||
|
||||
|
||||
if !chessGame.is_inside_tree():
|
||||
await chessGame.ready
|
||||
|
||||
_start_game_logic()
|
||||
|
||||
func _start_game_logic():
|
||||
if stateMachine and stateMachine.has_method("transitionToNextState"):
|
||||
stateMachine.transitionToNextState(Constants.WHITE_TURN)
|
||||
|
||||
|
||||
func _unhandled_input(event):
|
||||
if event is InputEventKey:
|
||||
if event.pressed and event.keycode == KEY_ESCAPE:
|
||||
_toggle_menu()
|
||||
|
||||
func _toggle_menu():
|
||||
if menuContainer:
|
||||
menuContainer.visible = !menuContainer.visible
|
||||
|
||||
if menuContainer.visible and chessGame:
|
||||
pass
|
||||
|
|
@ -1 +0,0 @@
|
|||
uid://cbaoxhgtk4td8
|
||||
|
|
@ -1,66 +0,0 @@
|
|||
extends RichTextLabel
|
||||
class_name GameMenuButton
|
||||
|
||||
signal pressed
|
||||
|
||||
# Style properties
|
||||
var normal_color: Color = Color(1, 1, 1, 1) # White
|
||||
var hover_color: Color = Color(1, 1, 0, 1) # Yellow
|
||||
var font_size: int = 28
|
||||
|
||||
func _ready():
|
||||
# Make this label clickable
|
||||
mouse_filter = Control.MOUSE_FILTER_STOP
|
||||
|
||||
# Prevent text wrapping
|
||||
autowrap_mode = TextServer.AUTOWRAP_OFF
|
||||
fit_content = true
|
||||
|
||||
# Set size flags to expand horizontally
|
||||
size_flags_horizontal = SIZE_EXPAND_FILL
|
||||
|
||||
# Remove default padding/margin
|
||||
add_theme_constant_override("margin_top", 0)
|
||||
add_theme_constant_override("margin_bottom", 0)
|
||||
add_theme_constant_override("margin_left", 0)
|
||||
add_theme_constant_override("margin_right", 0)
|
||||
|
||||
# Remove line spacing
|
||||
add_theme_constant_override("line_separation", 0)
|
||||
|
||||
# Set up base styling
|
||||
add_theme_font_size_override("normal_font_size", font_size)
|
||||
add_theme_color_override("default_color", normal_color)
|
||||
|
||||
# Make text bold
|
||||
bbcode_enabled = true
|
||||
text = "[b]" + text + "[/b]"
|
||||
|
||||
# Connect the gui_input signal to our own handler
|
||||
connect("gui_input", Callable(self, "_on_gui_input"))
|
||||
|
||||
# Connect hover signals for better user experience
|
||||
connect("mouse_entered", Callable(self, "_on_mouse_entered"))
|
||||
connect("mouse_exited", Callable(self, "_on_mouse_exited"))
|
||||
|
||||
func _on_gui_input(event):
|
||||
if event is InputEventMouseButton:
|
||||
if event.button_index == MOUSE_BUTTON_LEFT and event.pressed:
|
||||
emit_signal("pressed")
|
||||
get_viewport().set_input_as_handled()
|
||||
|
||||
func _on_mouse_entered():
|
||||
# Change appearance when mouse hovers
|
||||
add_theme_color_override("default_color", hover_color)
|
||||
|
||||
# Scale up slightly on hover (optional)
|
||||
var tween = create_tween()
|
||||
tween.tween_property(self, "scale", Vector2(1.05, 1.05), 0.1)
|
||||
|
||||
func _on_mouse_exited():
|
||||
# Restore original appearance
|
||||
add_theme_color_override("default_color", normal_color)
|
||||
|
||||
# Scale back to normal (optional)
|
||||
var tween = create_tween()
|
||||
tween.tween_property(self, "scale", Vector2(1.0, 1.0), 0.1)
|
||||
|
|
@ -1 +0,0 @@
|
|||
uid://bfjmon81nckns
|
||||
|
|
@ -1,100 +0,0 @@
|
|||
extends Control
|
||||
class_name GameMenuScreen
|
||||
|
||||
# Signals with optional parameters
|
||||
signal shop_open_requested(options)
|
||||
signal deckmanager_open_requested(options)
|
||||
signal map_open_requested(options)
|
||||
signal new_game_requested(options)
|
||||
signal lobby_open_requested(options)
|
||||
|
||||
# @onready var newGameButton = $HBoxContainer/VBoxContainer/MenuOptions/NewGameText
|
||||
# @onready var continueButton = $HBoxContainer/VBoxContainer/MenuOptions/Continue
|
||||
# @onready var optionsButton = $HBoxContainer/VBoxContainer/MenuOptions/Options
|
||||
# @onready var versionText = $HBoxContainer/VBoxContainer/VersionText
|
||||
# @onready var titleText = $HBoxContainer/VBoxContainer/TitleText
|
||||
# @onready var developerText = $HBoxContainer/VBoxContainer/DeveloperText
|
||||
|
||||
# Node references
|
||||
@onready var shopButton = $HBoxContainer/VBoxContainer/GameOptions/ShopText
|
||||
@onready var lobbyButton = $HBoxContainer/VBoxContainer/GameOptions/LobbyText
|
||||
@onready var mapButton = $HBoxContainer/VBoxContainer/GameOptions/MapText
|
||||
@onready var startButton = $HBoxContainer/VBoxContainer/GameOptions/StartText
|
||||
@onready var backButton = $HBoxContainer/VBoxContainer/GameOptions/BackText
|
||||
|
||||
# Reference to main menu container
|
||||
var main_menu_container = null
|
||||
|
||||
func _ready():
|
||||
# Setup button signals
|
||||
if shopButton:
|
||||
shopButton.visible = false;
|
||||
shopButton.connect("pressed", Callable(self, "_on_shop_button_pressed"))
|
||||
|
||||
if lobbyButton:
|
||||
lobbyButton.connect("pressed", Callable(self, "_on_lobby_button_pressed"))
|
||||
|
||||
if mapButton:
|
||||
mapButton.visible = false;
|
||||
mapButton.connect("pressed", Callable(self, "_on_map_button_pressed"))
|
||||
|
||||
if startButton:
|
||||
startButton.connect("pressed", Callable(self, "_on_start_button_pressed"))
|
||||
startButton.visible = false;
|
||||
|
||||
if backButton:
|
||||
backButton.connect("pressed", Callable(self, "_on_back_button_pressed"))
|
||||
|
||||
connect("map_open_requested", Callable(self, "_on_map_button_pressed"))
|
||||
|
||||
|
||||
|
||||
func setup(menu_container):
|
||||
main_menu_container = menu_container
|
||||
|
||||
func _on_shop_button_pressed():
|
||||
print("Shop button pressed")
|
||||
# Emit signal with null options
|
||||
emit_signal("shop_open_requested", null)
|
||||
|
||||
func _on_deck_button_pressed():
|
||||
print("Manage Deck button pressed")
|
||||
# Emit signal with null options
|
||||
emit_signal("deckmanager_open_requested", null)
|
||||
|
||||
func _on_map_button_pressed():
|
||||
print("Map button pressed")
|
||||
# ignore error, keeping the null prevents infinit recursion
|
||||
emit_signal("map_open_requested", null)
|
||||
visible = false
|
||||
|
||||
func _on_start_button_pressed():
|
||||
print("Start button pressed")
|
||||
# Emit signal with empty options dictionary
|
||||
emit_signal("new_game_requested", {})
|
||||
|
||||
# emit_signal("new_game_requested")
|
||||
# Hide this menu
|
||||
self.visible = false
|
||||
|
||||
func _on_back_button_pressed():
|
||||
print("Back button pressed")
|
||||
# Return to main menu
|
||||
if main_menu_container:
|
||||
main_menu_container.visible = true
|
||||
# Hide this menu
|
||||
self.visible = false
|
||||
|
||||
func show_menu():
|
||||
self.visible = true
|
||||
|
||||
func _on_lobby_open_requested(options):
|
||||
print("Lobby button pressed")
|
||||
emit_signal("lobby_open_requested", options)
|
||||
visible = false
|
||||
|
||||
|
||||
func _on_lobby_button_pressed():
|
||||
print("Lobby button pressed")
|
||||
emit_signal("lobby_open_requested", null)
|
||||
visible = false
|
||||
|
|
@ -1 +0,0 @@
|
|||
uid://j0m4rwr86oi6
|
||||
|
|
@ -1,258 +0,0 @@
|
|||
extends Control
|
||||
class_name HandPreloadScreen
|
||||
|
||||
signal back_pressed
|
||||
signal preload_visibility_changed(isvisible)
|
||||
|
||||
# Node references
|
||||
@onready var deckGrid = $MainContainer/GridScrollContainer/GridContainer
|
||||
@onready var bankContainer = $MainContainer/BankContainer/ScrollContainer/VBoxContainer
|
||||
@onready var backButton = $BackButton
|
||||
@onready var card_preview = $CardPreviewPanel
|
||||
@onready var token_label = $TokenLabel
|
||||
@onready var cost_label = $CostLabel
|
||||
|
||||
var handSize = 2
|
||||
var deckManager = null
|
||||
var player = null
|
||||
var bankCards = [] # Cards available
|
||||
var tokens = 0
|
||||
|
||||
var usedSlots = []
|
||||
var currentHand = []
|
||||
var current_preview_card = null
|
||||
|
||||
func _ready():
|
||||
# Connect back button
|
||||
if backButton:
|
||||
backButton.connect("pressed", Callable(self, "_on_backButton_pressed"))
|
||||
|
||||
if card_preview:
|
||||
card_preview.visible = false
|
||||
if cost_label:
|
||||
cost_label.visible = false
|
||||
|
||||
|
||||
func initialize(options = null):
|
||||
# Find the DeckManager instance
|
||||
var board = get_node_or_null("/root/Board") as ChessGame
|
||||
if board and "deckManager" in board:
|
||||
deckManager = board.deckManager
|
||||
print("Found deck manager:", deckManager)
|
||||
else:
|
||||
print("DeckManager not found on Board node")
|
||||
if board and "player" in board:
|
||||
player = board.player
|
||||
handSize = player.hand_size
|
||||
tokens = player.tokens
|
||||
updateTokenLabel()
|
||||
else:
|
||||
print("Player not found on Board node")
|
||||
|
||||
# Load cards from deck and bank
|
||||
loadCards()
|
||||
|
||||
# Set up the grid with empty card containers
|
||||
setupDeckGrid()
|
||||
|
||||
# Populate the bank with available cards
|
||||
populateBank()
|
||||
|
||||
|
||||
func updateTokenLabel():
|
||||
if token_label:
|
||||
token_label.text = str(tokens) + " TOKENS"
|
||||
|
||||
func loadCards():
|
||||
if deckManager:
|
||||
currentHand.clear()
|
||||
usedSlots.clear()
|
||||
# Clone the deck to work with
|
||||
bankCards = deckManager.deck.duplicate()
|
||||
for id in deckManager.preloaded_hand_cards:
|
||||
var matchCard = func matchCardId(card):
|
||||
return "id" in card and card.id == id
|
||||
var cardIndex = bankCards.find_custom(matchCard.bind())
|
||||
if cardIndex != -1:
|
||||
currentHand.append(bankCards[cardIndex].id)
|
||||
usedSlots.append(true)
|
||||
|
||||
else:
|
||||
# Fallback with empty collections if deck manager not found
|
||||
currentHand = []
|
||||
bankCards = []
|
||||
print("Warning: DeckManager not found")
|
||||
|
||||
|
||||
func setupDeckGrid():
|
||||
# Clear existing children
|
||||
for child in deckGrid.get_children():
|
||||
child.queue_free()
|
||||
|
||||
# Calculate grid dimensions
|
||||
var cols = 4 #check screen to deteremine
|
||||
var rows = handSize / cols
|
||||
deckGrid.columns = cols
|
||||
|
||||
# Create card slots
|
||||
for i in range(handSize):
|
||||
var card_slot = preload("res://card_slot.tscn").instantiate()
|
||||
deckGrid.add_child(card_slot)
|
||||
|
||||
# Connect signals
|
||||
card_slot.connect("card_selected", Callable(self, "_on_hand_card_selected"))
|
||||
_connect_hover_signals(card_slot)
|
||||
|
||||
# Set card if available
|
||||
if i < currentHand.size():
|
||||
var matchCard = func matchCardId(card):
|
||||
return "id" in card and card.id == currentHand[i]
|
||||
var cardIndex = bankCards.find_custom(matchCard.bind())
|
||||
if cardIndex != -1:
|
||||
card_slot.set_card(bankCards[cardIndex])
|
||||
else:
|
||||
card_slot.clear()
|
||||
|
||||
func _connect_hover_signals(node):
|
||||
# Add hover signals for preview functionality
|
||||
if node.is_class("Control"):
|
||||
node.mouse_entered.connect(Callable(self, "_on_card_mouse_entered").bind(node))
|
||||
# node.mouse_exited.connect(Callable(self, "_on_card_mouse_exited").bind(node))
|
||||
|
||||
|
||||
func populateBank():
|
||||
for child in bankContainer.get_children():
|
||||
child.queue_free()
|
||||
|
||||
# Add each bank card to the list
|
||||
for card in bankCards:
|
||||
var card_item = preload("res://card_bank_item.tscn").instantiate()
|
||||
bankContainer.add_child(card_item)
|
||||
card_item.set_card(card)
|
||||
card_item.connect("card_selected", Callable(self, "_on_bank_card_selected"))
|
||||
_connect_hover_signals(card_item)
|
||||
|
||||
|
||||
static var TokenCosts = {
|
||||
Card.Rank.RANK_0: 15, # Most expensive (one-time use)
|
||||
Card.Rank.RANK_1: 10, # Expensive (once per match)
|
||||
Card.Rank.RANK_2: 5, # Medium (multiple uses)
|
||||
Card.Rank.RANK_3: 3 # Cheapest (basic cards)
|
||||
}
|
||||
|
||||
func _on_hand_card_selected(card_slot, card):
|
||||
if card:
|
||||
# Remove card from deck
|
||||
var index = currentHand.find(card.id)
|
||||
if index >= 0:
|
||||
currentHand.remove_at(index)
|
||||
if index < usedSlots.size() and usedSlots[index] != true:
|
||||
tokens += Utils.TokenCosts[card.rank]
|
||||
usedSlots.remove_at(index)
|
||||
updateTokenLabel()
|
||||
|
||||
|
||||
# Update UI
|
||||
card_slot.clear()
|
||||
populateBank()
|
||||
if current_preview_card == card:
|
||||
hide_card_preview()
|
||||
|
||||
func _on_bank_card_selected(card_item, card):
|
||||
print("_on_bank_card_selected ", card.id)
|
||||
# Find first empty slot in deck
|
||||
var empty_slot_index = -1
|
||||
if card and (Utils.HandRankWhiteList.has(card.rank)):
|
||||
for i in range(deckGrid.get_child_count()):
|
||||
var slot = deckGrid.get_child(i)
|
||||
if !slot.has_card():
|
||||
empty_slot_index = i
|
||||
break
|
||||
|
||||
# print("_on_bank_card_selected ", currentHand, " ", handSize, " ", empty_slot_index, )
|
||||
# print("_on_bank_card_selected ", empty_slot_index >= 0, " ", currentHand.size() < handSize, " ", currentHand.find(card.id))
|
||||
if empty_slot_index >= 0 and currentHand.size() < handSize and currentHand.find(card.id) == -1 and tokens - Utils.TokenCosts[card.rank] > 0:
|
||||
# print("currentHand append ", card.id)
|
||||
|
||||
tokens -= Utils.TokenCosts[card.rank]
|
||||
updateTokenLabel()
|
||||
currentHand.append(card.id)
|
||||
usedSlots.append(false)
|
||||
|
||||
deckGrid.get_child(empty_slot_index).set_card(card)
|
||||
populateBank()
|
||||
|
||||
func _on_card_mouse_entered(node):
|
||||
var card = null
|
||||
|
||||
# Get the card from the node
|
||||
if node.has_method("has_card") and node.has_card():
|
||||
# This is a CardSlot
|
||||
card = node.current_card
|
||||
elif node.has_method("set_card") and node.current_card:
|
||||
# This is a CardBankItem
|
||||
card = node.current_card
|
||||
|
||||
if card:
|
||||
show_card_preview(card)
|
||||
|
||||
# func _on_card_mouse_exited(node):
|
||||
# # Add a short delay before hiding the preview
|
||||
# # This prevents flickering when moving between cards
|
||||
# await get_tree().create_timer(0.1).timeout
|
||||
|
||||
# # Only hide if we're not hovering over another card that shows the same preview
|
||||
# if current_preview_card:
|
||||
# hide_card_preview()
|
||||
|
||||
func show_card_preview(card):
|
||||
if card_preview and card:
|
||||
current_preview_card = card
|
||||
card_preview.preview_card(card)
|
||||
if cost_label:
|
||||
cost_label.visible = true
|
||||
cost_label.text = "Cost: " + str(Utils.TokenCosts[card.rank]) + " tokens"
|
||||
|
||||
func hide_card_preview():
|
||||
if cost_label:
|
||||
cost_label.visible = false
|
||||
if card_preview:
|
||||
current_preview_card = null
|
||||
card_preview.hide_preview()
|
||||
|
||||
func savePreloadCards():
|
||||
if deckManager:
|
||||
# Save the current deck to the deck manager
|
||||
deckManager.preloaded_hand_cards = currentHand.duplicate()
|
||||
|
||||
print("Preloads saved with ", currentHand.size(), " cards")
|
||||
player.tokens = tokens
|
||||
|
||||
func _on_backButton_pressed():
|
||||
# Save changes before returning
|
||||
savePreloadCards()
|
||||
|
||||
# Emit signal to go back
|
||||
emit_signal("back_pressed")
|
||||
|
||||
# Hide this screen
|
||||
visible = false
|
||||
|
||||
|
||||
|
||||
|
||||
# Notification
|
||||
func _notification(what):
|
||||
if what == NOTIFICATION_VISIBILITY_CHANGED:
|
||||
_on_visibility_changed(visible)
|
||||
|
||||
func _on_visibility_changed(is_visible):
|
||||
|
||||
print("Preload Screen visibility changed to: ", is_visible)
|
||||
if is_visible:
|
||||
loadCards()
|
||||
setupDeckGrid()
|
||||
else:
|
||||
print("Preload Screen is now invisible")
|
||||
|
||||
emit_signal("preload_visibility_changed", is_visible)
|
||||
|
|
@ -1 +0,0 @@
|
|||
uid://b7b2xlfvhgipb
|
||||
|
|
@ -1,101 +0,0 @@
|
|||
extends Control
|
||||
class_name LobbyScreen
|
||||
|
||||
signal vanilla_selected
|
||||
signal shop_selected(options)
|
||||
signal preload_selected(options)
|
||||
signal deeper_selected
|
||||
signal back_pressed
|
||||
signal lobby_screen_visibility_changed(isvisible)
|
||||
|
||||
# Node references
|
||||
@onready var vanilla_button = $BottomContainer/VanillaButton
|
||||
@onready var shop_button = $CenterContainer/ShopButton
|
||||
@onready var deeper_button = $BottomContainer/DeeperButton
|
||||
@onready var back_button = $BackButton
|
||||
@onready var preload_button = $PreloadButton
|
||||
@onready var run_count_label = $TopBar/RunCountLabel
|
||||
@onready var token_label = $TopBar/TokenContainer/TokenLabel
|
||||
|
||||
var game: ChessGame
|
||||
var player: Player
|
||||
|
||||
func _ready():
|
||||
# Connect button signals
|
||||
if vanilla_button:
|
||||
vanilla_button.connect("pressed", Callable(self, "_on_vanilla_button_pressed"))
|
||||
|
||||
if shop_button:
|
||||
shop_button.connect("pressed", Callable(self, "_on_shop_button_pressed"))
|
||||
|
||||
if deeper_button:
|
||||
deeper_button.connect("pressed", Callable(self, "_on_deeper_button_pressed"))
|
||||
|
||||
if back_button:
|
||||
back_button.connect("pressed", Callable(self, "_on_back_button_pressed"))
|
||||
|
||||
if preload_button:
|
||||
preload_button.connect("pressed", Callable(self, "_on_preload_button_pressed"))
|
||||
|
||||
func initialize(options = null):
|
||||
game = get_node_or_null("/root/Board") as ChessGame
|
||||
if game and "player" in game:
|
||||
player = game.player
|
||||
update_display()
|
||||
|
||||
if deeper_button:
|
||||
deeper_button.disabled = player.get_run_count() == 0
|
||||
|
||||
func update_display():
|
||||
if player:
|
||||
if run_count_label:
|
||||
run_count_label.text = "RUN #" + str(player.get_run_count() + 1)
|
||||
|
||||
|
||||
if deeper_button:
|
||||
deeper_button.disabled = player.run_count == 0
|
||||
|
||||
if token_label:
|
||||
token_label.text = str(player.tokens) + " TOKENS"
|
||||
|
||||
func _on_preload_button_pressed():
|
||||
emit_signal("preload_selected")
|
||||
self.visible = false
|
||||
|
||||
func _on_vanilla_button_pressed():
|
||||
emit_signal("vanilla_selected")
|
||||
self.visible = false
|
||||
|
||||
func _on_shop_button_pressed():
|
||||
emit_signal("shop_selected", {})
|
||||
self.visible = false
|
||||
|
||||
func _on_deeper_button_pressed():
|
||||
emit_signal("deeper_selected")
|
||||
self.visible = false
|
||||
|
||||
func _on_back_button_pressed():
|
||||
emit_signal("back_pressed")
|
||||
# self.visible = false
|
||||
|
||||
func show_screen():
|
||||
initialize()
|
||||
self.visible = true
|
||||
|
||||
|
||||
|
||||
# Notification
|
||||
func _notification(what):
|
||||
if what == NOTIFICATION_VISIBILITY_CHANGED:
|
||||
_on_visibility_changed(visible)
|
||||
|
||||
func _on_visibility_changed(is_visible):
|
||||
|
||||
print("Lobby Screen visibility changed to: ", is_visible)
|
||||
if is_visible:
|
||||
update_display()
|
||||
else:
|
||||
print("Lobby Screen is now invisible")
|
||||
|
||||
emit_signal("lobby_screen_visibility_changed", is_visible)
|
||||
|
||||
|
|
@ -1 +0,0 @@
|
|||
uid://dkk0eu4fj7q5j
|
||||
|
|
@ -1,387 +0,0 @@
|
|||
extends Control
|
||||
class_name LobbyShopScreen
|
||||
|
||||
signal back_pressed
|
||||
signal card_unlocked(card, token_cost)
|
||||
signal lobby_shop_visibility_changed(isvisible)
|
||||
signal hand_size_increased
|
||||
|
||||
# Node references
|
||||
@onready var card_carousel = $MainContainer/CardCarouselContainer/CardCarousel
|
||||
@onready var token_label = $TopBar/TokenContainer/TokenLabel
|
||||
@onready var buy_button = $BuyButton
|
||||
@onready var back_button = $BackButton
|
||||
@onready var card_preview = $CardPreviewPanel
|
||||
@onready var left_button = $MainContainer/CardCarouselContainer/LeftButton
|
||||
@onready var right_button = $MainContainer/CardCarouselContainer/RightButton
|
||||
@onready var hand_size_label = $HandSizeContainer/HandSizeLabel
|
||||
@onready var hand_size_cost_label = $HandSizeContainer/CostLabel
|
||||
@onready var increase_hand_size_button = $HandSizeContainer/IncreaseButton
|
||||
|
||||
var available_cards = []
|
||||
var player_tokens = 0
|
||||
var selected_card = null
|
||||
var selected_index = 0
|
||||
var carousel_page = 0
|
||||
var cards_per_page = 3
|
||||
var card_instance_map = {}
|
||||
var hovering_card_index = -1
|
||||
var mouse_over_any_card = false
|
||||
var game: ChessGame
|
||||
var player: Player
|
||||
|
||||
var card_costs = Utils.TokenCosts
|
||||
|
||||
var fibonacci = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144]
|
||||
|
||||
func _ready():
|
||||
if back_button:
|
||||
back_button.connect("pressed", Callable(self, "_on_back_button_pressed"))
|
||||
|
||||
if buy_button:
|
||||
buy_button.connect("pressed", Callable(self, "_on_buy_button_pressed"))
|
||||
|
||||
if increase_hand_size_button:
|
||||
increase_hand_size_button.connect("pressed", Callable(self, "_on_increase_hand_size_pressed"))
|
||||
|
||||
if left_button:
|
||||
left_button.pressed.connect(_on_left_button_pressed)
|
||||
|
||||
if right_button:
|
||||
right_button.pressed.connect(_on_right_button_pressed)
|
||||
|
||||
# Initialize with empty shop
|
||||
if card_preview:
|
||||
card_preview.visible = false
|
||||
|
||||
update_token_display()
|
||||
update_buy_button()
|
||||
update_navigation_buttons()
|
||||
update_hand_size_display()
|
||||
|
||||
func initialize(options = null):
|
||||
carousel_page = 0
|
||||
selected_index = 0
|
||||
|
||||
game = get_node_or_null("/root/Board") as ChessGame
|
||||
if game and "player" in game:
|
||||
player = game.player
|
||||
player_tokens = player.tokens
|
||||
|
||||
# Generate shop cards if not provided
|
||||
if available_cards.is_empty() and game and "deckManager" in game:
|
||||
var deck_manager = game.deckManager
|
||||
available_cards = generate_shop_cards(deck_manager)
|
||||
|
||||
# Update display
|
||||
update_token_display()
|
||||
update_hand_size_display()
|
||||
populate_carousel()
|
||||
update_navigation_buttons()
|
||||
|
||||
if not available_cards.is_empty():
|
||||
select_card(0) # Select the first card by default
|
||||
|
||||
func generate_shop_cards(deck_manager):
|
||||
var shop_cards = []
|
||||
var all_available_cards = []
|
||||
|
||||
for rank in player.cards_by_rank:
|
||||
for card in player.cards_by_rank[rank]:
|
||||
var is_unlocked = false
|
||||
for unlocked_card in player.unlocked_cards:
|
||||
if unlocked_card.cardName == card.cardName:
|
||||
is_unlocked = true
|
||||
break
|
||||
|
||||
if not is_unlocked:
|
||||
var card_instance = card.duplicate()
|
||||
all_available_cards.append(card_instance)
|
||||
|
||||
if all_available_cards.is_empty():
|
||||
print("No new cards available for the shop")
|
||||
return []
|
||||
|
||||
all_available_cards.shuffle()
|
||||
|
||||
var num_shop_cards = min(randi_range(5, 7), all_available_cards.size())
|
||||
|
||||
for i in range(num_shop_cards):
|
||||
shop_cards.append(all_available_cards[i])
|
||||
|
||||
print("Generated shop with " + str(shop_cards.size()) + " cards")
|
||||
return shop_cards
|
||||
|
||||
func populate_carousel():
|
||||
if card_carousel:
|
||||
for child in card_carousel.get_children():
|
||||
child.queue_free()
|
||||
|
||||
card_instance_map.clear()
|
||||
|
||||
var start_index = carousel_page * cards_per_page
|
||||
var end_index = min(start_index + cards_per_page, available_cards.size())
|
||||
|
||||
card_carousel.add_theme_constant_override("separation", 20)
|
||||
|
||||
for i in range(start_index, end_index):
|
||||
var card = available_cards[i]
|
||||
var card_visual = preload("res://card_visual.tscn").instantiate()
|
||||
|
||||
card_carousel.add_child(card_visual)
|
||||
card_visual.set_card(card)
|
||||
|
||||
var token_cost = get_card_cost(card)
|
||||
card_visual.set_price(token_cost)
|
||||
card_visual.set_string("tokens")
|
||||
|
||||
var instance_id = card_visual.get_instance_id()
|
||||
|
||||
card_instance_map[instance_id] = {
|
||||
"card_index": i,
|
||||
"card": card
|
||||
}
|
||||
|
||||
update_navigation_buttons()
|
||||
|
||||
func update_navigation_buttons():
|
||||
if left_button:
|
||||
left_button.disabled = (carousel_page <= 0)
|
||||
|
||||
if right_button:
|
||||
var max_page = ceil(float(available_cards.size()) / cards_per_page) - 1
|
||||
right_button.disabled = (carousel_page >= max_page or available_cards.size() <= cards_per_page)
|
||||
|
||||
func select_card(index):
|
||||
if index < 0 or index >= available_cards.size():
|
||||
return
|
||||
|
||||
var page_for_index = int(index / cards_per_page)
|
||||
if page_for_index != carousel_page:
|
||||
carousel_page = page_for_index
|
||||
populate_carousel()
|
||||
|
||||
selected_index = index
|
||||
selected_card = available_cards[index]
|
||||
|
||||
var visual_index = index % cards_per_page
|
||||
|
||||
for i in range(card_carousel.get_child_count()):
|
||||
var card_visual = card_carousel.get_child(i)
|
||||
card_visual.set_selected(i == visual_index)
|
||||
|
||||
if card_preview and selected_card:
|
||||
card_preview.preview_card(selected_card)
|
||||
card_preview.visible = true
|
||||
|
||||
update_buy_button()
|
||||
|
||||
func update_buy_button():
|
||||
if not buy_button or not selected_card:
|
||||
return
|
||||
|
||||
var cost = get_card_cost(selected_card)
|
||||
buy_button.text = "BUY (" + str(cost) + " tokens)"
|
||||
buy_button.disabled = cost > player_tokens
|
||||
|
||||
func get_card_cost(card):
|
||||
if not card:
|
||||
return 0
|
||||
if card.rank in card_costs:
|
||||
return card_costs[card.rank]
|
||||
return 10
|
||||
|
||||
func update_token_display():
|
||||
if token_label:
|
||||
token_label.text = str(player_tokens) + " TOKENS"
|
||||
|
||||
func update_hand_size_display():
|
||||
if not player or not hand_size_label or not hand_size_cost_label:
|
||||
return
|
||||
|
||||
hand_size_label.text = "HAND SIZE: " + str(player.hand_size)
|
||||
|
||||
# Calculate cost using Fibonacci sequence
|
||||
var fib_index = max(player.hand_size - 2, 1) + 2 # Start at index 2 (which is 1)
|
||||
if fib_index >= fibonacci.size():
|
||||
fib_index = fibonacci.size() - 1
|
||||
|
||||
var cost = fibonacci[fib_index]
|
||||
hand_size_cost_label.text = "Cost: " + str(cost) + " tokens"
|
||||
|
||||
if increase_hand_size_button:
|
||||
increase_hand_size_button.disabled = cost > player_tokens
|
||||
|
||||
func purchase_selected_card():
|
||||
if not selected_card or not player:
|
||||
return false
|
||||
|
||||
var cost = get_card_cost(selected_card)
|
||||
|
||||
if player_tokens < cost:
|
||||
print("Not enough tokens")
|
||||
return false
|
||||
|
||||
player_tokens -= cost
|
||||
player.tokens = player_tokens
|
||||
|
||||
var purchased_card = selected_card.duplicate()
|
||||
available_cards.remove_at(selected_index)
|
||||
|
||||
emit_signal("card_unlocked", purchased_card, cost)
|
||||
|
||||
update_token_display()
|
||||
|
||||
var max_page = max(0, ceil(float(available_cards.size()) / cards_per_page) - 1)
|
||||
|
||||
if carousel_page > max_page:
|
||||
carousel_page = max_page
|
||||
|
||||
populate_carousel()
|
||||
|
||||
if not available_cards.is_empty():
|
||||
var new_index = min(selected_index, available_cards.size() - 1)
|
||||
select_card(new_index)
|
||||
else:
|
||||
selected_card = null
|
||||
selected_index = -1
|
||||
if card_preview:
|
||||
card_preview.hide_preview()
|
||||
|
||||
buy_button.disabled = true
|
||||
|
||||
update_buy_button()
|
||||
return true
|
||||
|
||||
func increase_hand_size():
|
||||
if not player:
|
||||
return false
|
||||
|
||||
# Calculate cost using Fibonacci sequence
|
||||
var fib_index = max(player.hand_size - 2, 1) + 2
|
||||
if fib_index >= fibonacci.size():
|
||||
fib_index = fibonacci.size() - 1
|
||||
|
||||
var cost = fibonacci[fib_index]
|
||||
|
||||
if player_tokens < cost:
|
||||
print("Not enough tokens for hand size increase")
|
||||
return false
|
||||
if player.hand_size >= player.MAX_HAND_SIZE:
|
||||
print("Hand reached max size")
|
||||
return false
|
||||
player_tokens -= cost
|
||||
player.tokens = player_tokens
|
||||
player.set_hand_size(player.hand_size + 1)
|
||||
|
||||
update_token_display()
|
||||
update_hand_size_display()
|
||||
update_buy_button()
|
||||
|
||||
emit_signal("hand_size_increased")
|
||||
return true
|
||||
|
||||
# Signal handlers
|
||||
func _on_back_button_pressed():
|
||||
emit_signal("back_pressed")
|
||||
visible = false
|
||||
|
||||
func _on_buy_button_pressed():
|
||||
purchase_selected_card()
|
||||
|
||||
func _on_increase_hand_size_pressed():
|
||||
increase_hand_size()
|
||||
|
||||
func _on_left_button_pressed():
|
||||
if carousel_page > 0:
|
||||
carousel_page -= 1
|
||||
var new_index = carousel_page * cards_per_page
|
||||
populate_carousel()
|
||||
select_card(new_index)
|
||||
|
||||
func _on_right_button_pressed():
|
||||
var max_page = ceil(float(available_cards.size()) / cards_per_page) - 1
|
||||
if carousel_page < max_page:
|
||||
carousel_page += 1
|
||||
var new_index = carousel_page * cards_per_page
|
||||
populate_carousel()
|
||||
select_card(new_index)
|
||||
|
||||
func _on_card_visual_pressed(index):
|
||||
select_card(index)
|
||||
|
||||
func _on_card_visual_hover(card, index):
|
||||
if card_preview and card:
|
||||
card_preview.preview_card(card)
|
||||
card_preview.visible = true
|
||||
select_card(index)
|
||||
|
||||
func _on_card_visual_exit():
|
||||
if card_preview and selected_card:
|
||||
card_preview.preview_card(selected_card)
|
||||
elif card_preview:
|
||||
card_preview.hide_preview()
|
||||
|
||||
func _process(delta):
|
||||
if not visible:
|
||||
return
|
||||
|
||||
var mouse_pos = get_viewport().get_mouse_position()
|
||||
|
||||
# Reset tracking
|
||||
var old_hovering_index = hovering_card_index
|
||||
hovering_card_index = -1
|
||||
mouse_over_any_card = false
|
||||
var mouse_clicked = Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT)
|
||||
|
||||
for i in range(card_carousel.get_child_count()):
|
||||
var card_visual = card_carousel.get_child(i)
|
||||
var card_rect = card_visual.get_global_rect()
|
||||
|
||||
if card_rect.has_point(mouse_pos) and mouse_clicked:
|
||||
# Get data from instance map
|
||||
var instance_id = card_visual.get_instance_id()
|
||||
if card_instance_map.has(instance_id):
|
||||
var data = card_instance_map[instance_id]
|
||||
hovering_card_index = data.card_index
|
||||
mouse_over_any_card = true
|
||||
|
||||
# If we just started hovering this card
|
||||
if old_hovering_index != hovering_card_index:
|
||||
_on_card_visual_hover(data.card, data.card_index)
|
||||
|
||||
# Also trigger the card's own hover effect
|
||||
card_visual._on_mouse_entered()
|
||||
|
||||
break
|
||||
|
||||
# If we were hovering a card before but not now, trigger exit
|
||||
if old_hovering_index != -1 and hovering_card_index == -1:
|
||||
_on_card_visual_exit()
|
||||
|
||||
# Find the previously hovered card by index
|
||||
for i in range(card_carousel.get_child_count()):
|
||||
var card_visual = card_carousel.get_child(i)
|
||||
var instance_id = card_visual.get_instance_id()
|
||||
if card_instance_map.has(instance_id) and card_instance_map[instance_id].card_index == old_hovering_index:
|
||||
card_visual._on_mouse_exited()
|
||||
break
|
||||
|
||||
|
||||
|
||||
# Notification
|
||||
func _notification(what):
|
||||
if what == NOTIFICATION_VISIBILITY_CHANGED:
|
||||
_on_visibility_changed(visible)
|
||||
|
||||
func _on_visibility_changed(is_visible):
|
||||
|
||||
print("LobbyShop visibility changed to: ", is_visible)
|
||||
if is_visible:
|
||||
update_token_display()
|
||||
update_hand_size_display()
|
||||
update_buy_button()
|
||||
else:
|
||||
print("LobbyShop is now invisible")
|
||||
|
||||
emit_signal("lobby_shop_visibility_changed", is_visible)
|
||||
|
|
@ -1 +0,0 @@
|
|||
uid://nrd5mq0tfmur
|
||||
|
|
@ -1,108 +0,0 @@
|
|||
extends Control
|
||||
class_name DotPatternGenerator
|
||||
# Dot pattern settings
|
||||
const DOT_SPACING = 30
|
||||
const DOT_SIZE = 2
|
||||
const DOT_COLOR = Color(0.3, 0.3, 0.3, 0.5)
|
||||
const PATH_DOT_COLOR = Color(0.4, 0.4, 0.6, 0.8)
|
||||
const PATH_DOT_SIZE = 3
|
||||
const HIGHLIGHT_RADIUS = 150
|
||||
|
||||
# For path highlights
|
||||
var map_screen = null
|
||||
var highlight_dots = false
|
||||
|
||||
func _init(highlight: bool = false):
|
||||
highlight_dots = highlight
|
||||
# Don't process input
|
||||
mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
|
||||
func _ready():
|
||||
# Apply the pattern
|
||||
generate_dots()
|
||||
|
||||
# Find map screen if we're highlighting paths
|
||||
if highlight_dots:
|
||||
map_screen = find_map_screen()
|
||||
|
||||
func find_map_screen():
|
||||
var parent = get_parent()
|
||||
while parent != null:
|
||||
if parent is MapScreen:
|
||||
return parent
|
||||
parent = parent.get_parent()
|
||||
return null
|
||||
|
||||
func generate_dots():
|
||||
# Clear existing dots
|
||||
for child in get_children():
|
||||
child.queue_free()
|
||||
|
||||
# Get the size of this control
|
||||
var control_size = size
|
||||
|
||||
# Calculate how many dots we need
|
||||
var cols = int(control_size.x / DOT_SPACING) + 1
|
||||
var rows = int(control_size.y / DOT_SPACING) + 1
|
||||
|
||||
# Create dots
|
||||
for i in range(rows):
|
||||
for j in range(cols):
|
||||
var dot_position = Vector2(j * DOT_SPACING, i * DOT_SPACING)
|
||||
var on_path = false
|
||||
|
||||
# If highlighting, check if dot is near a path
|
||||
if highlight_dots and map_screen != null:
|
||||
on_path = is_near_path(dot_position)
|
||||
|
||||
# Create the dot with appropriate style
|
||||
var dot = ColorRect.new()
|
||||
dot.size = Vector2(DOT_SIZE, DOT_SIZE) if not on_path else Vector2(PATH_DOT_SIZE, PATH_DOT_SIZE)
|
||||
dot.color = DOT_COLOR if not on_path else PATH_DOT_COLOR
|
||||
dot.position = dot_position - (dot.size / 2)
|
||||
add_child(dot)
|
||||
|
||||
func is_near_path(position: Vector2) -> bool:
|
||||
# If no map screen, can't check paths
|
||||
if map_screen == null:
|
||||
return false
|
||||
|
||||
# Get connections from map screen
|
||||
for line in map_screen.connection_lines:
|
||||
if line.get_point_count() < 2:
|
||||
continue
|
||||
|
||||
# Check distance to line segment
|
||||
var start = line.get_point_position(0)
|
||||
var end = line.get_point_position(1)
|
||||
|
||||
# Adjust for potential offset in the map container
|
||||
start += map_screen.map_container.position
|
||||
end += map_screen.map_container.position
|
||||
|
||||
# Check if point is near the line
|
||||
var distance = distance_to_line_segment(position, start, end)
|
||||
if distance < HIGHLIGHT_RADIUS:
|
||||
return true
|
||||
|
||||
return false
|
||||
|
||||
# Calculate distance from point to line segment
|
||||
func distance_to_line_segment(point: Vector2, line_start: Vector2, line_end: Vector2) -> float:
|
||||
var line_vec = line_end - line_start
|
||||
var point_vec = point - line_start
|
||||
|
||||
var line_length_squared = line_vec.length_squared()
|
||||
if line_length_squared == 0:
|
||||
return point_vec.length() # Line segment is a point
|
||||
|
||||
# Calculate projection of point onto line
|
||||
var t = max(0, min(1, point_vec.dot(line_vec) / line_length_squared))
|
||||
var projection = line_start + t * line_vec
|
||||
|
||||
# Return distance from point to projection
|
||||
return (point - projection).length()
|
||||
|
||||
func update_dots():
|
||||
if highlight_dots and map_screen != null:
|
||||
generate_dots()
|
||||
|
|
@ -1 +0,0 @@
|
|||
uid://6cmhvsug8nbv
|
||||
|
|
@ -1,795 +0,0 @@
|
|||
extends RefCounted
|
||||
class_name ChessMapGenerator
|
||||
|
||||
|
||||
|
||||
var min_levels = 5
|
||||
var max_levels = 6
|
||||
var max_connections_per_node = 4
|
||||
|
||||
var min_nodes_per_level = 1
|
||||
var max_nodes_per_level = 4
|
||||
var positions_per_level = 4
|
||||
var starting_elo = 1000
|
||||
var final_elo = 2100
|
||||
var current_max_level = 0;
|
||||
# var level_unit_distribution = ["", "", "", "nkr", "rnkr", "rnkbr", "rnqkbr", "rnqkbnr", "rnbqkbnr", "rbnqknbnr", "rnbnqknbnr", "rnbnqknbnbr", "rbnbnqknbnbr"]
|
||||
# var level_unit_distribution = ["", "nk", "nkr", "rnkr", "rnkr", "rnkbr", "rnqkbr", "rnqkbnr", "rnbqkbnr"]
|
||||
|
||||
var level_unit_distribution = ["", "nk", "nkr", "rnkr", "rnkbr", "rnqkbr", "rnqkbnr", "rnbqkbnr"]
|
||||
# ["", "nk", "nkr", "rnkr", "rnkbr", "rnqkbr", "rnqkbnr", "rnbqkbnr", "rbnqknbnr", "rnbnqknbnr", "rnbnqknbnnr", "rnnbnqknbnnr"]
|
||||
# ["", "", "nkr", "rnkr", "rnkr", "rnkbr", "rnqkbr", "rnqkbnr", "rnbqkbnr", "rbnqknbnr", "rnbnqknbnr", "rnbnqknbnbr", "rbnbnqknbnbr"]
|
||||
var _rng = RandomNumberGenerator.new()
|
||||
var _next_id = 0
|
||||
|
||||
func _init(seed_value = null):
|
||||
# Set seed for reproducible maps
|
||||
if seed_value != null:
|
||||
_rng.seed = seed_value
|
||||
else:
|
||||
_rng.randomize()
|
||||
|
||||
func change_preset_mode(mode, player):
|
||||
# var game = get_node_or_null("/root/Board") as ChessGame
|
||||
# var player = game.player
|
||||
var run_count = player.run_count
|
||||
|
||||
match mode:
|
||||
"vanilla":
|
||||
min_levels = 5
|
||||
max_levels = 6
|
||||
var elo_step = Utils.VANILLA_ELO_STEP
|
||||
var base_elo = Utils.MIN_ELO + run_count * elo_step
|
||||
starting_elo = clamp(base_elo, Utils.MIN_ELO, Utils.MAX_ELO)
|
||||
final_elo = starting_elo + elo_step
|
||||
"deeper":
|
||||
min_levels = 10
|
||||
max_levels = 16
|
||||
var elo_step = Utils.DEEPER_ELO_STEP
|
||||
var base_elo = Utils.MIN_ELO + run_count * elo_step
|
||||
starting_elo = clamp(base_elo, Utils.MIN_ELO, Utils.MAX_ELO)
|
||||
final_elo = starting_elo + elo_step
|
||||
_:
|
||||
min_levels = 5
|
||||
max_levels = 6
|
||||
var elo_step = Utils.VANILLA_ELO_STEP
|
||||
var base_elo = Utils.MIN_ELO + run_count * elo_step
|
||||
starting_elo = clamp(base_elo, Utils.MIN_ELO, Utils.MAX_ELO)
|
||||
final_elo = starting_elo + elo_step
|
||||
|
||||
|
||||
|
||||
func generate_map(mode, player):
|
||||
var isVanilla = mode == "vanilla"
|
||||
change_preset_mode(mode, player)
|
||||
var nodes = []
|
||||
var connections = []
|
||||
_next_id = 0
|
||||
|
||||
var num_levels = _rng.randi_range(min_levels, max_levels)
|
||||
current_max_level = num_levels;
|
||||
var elo_step = float(final_elo - starting_elo) / (num_levels - 1)
|
||||
|
||||
var start_node = {
|
||||
"id": _get_next_id(),
|
||||
"type": Utils.RoomType.STARTING,
|
||||
"level": 0,
|
||||
"position": Vector2(3, 0),
|
||||
"metadata": {
|
||||
"is_escape": false,
|
||||
},
|
||||
"elo": starting_elo
|
||||
}
|
||||
nodes.append(start_node)
|
||||
|
||||
# Create final boss node
|
||||
var final_node = {
|
||||
"id": _get_next_id(),
|
||||
"type": Utils.RoomType.FINAL,
|
||||
"level": num_levels - 1,
|
||||
"position": Vector2(3, num_levels - 1),
|
||||
"metadata": {},
|
||||
"elo": final_elo
|
||||
}
|
||||
# final_node.metadata = generate_final_data(final_node)
|
||||
final_node = generate_node_data(final_node, player)
|
||||
# print("final_node ====", final_node)
|
||||
nodes.append(final_node)
|
||||
|
||||
var levels_nodes = {0: [start_node], (num_levels - 1): [final_node]}
|
||||
|
||||
for level in range(1, num_levels - 1):
|
||||
var level_nodes = []
|
||||
var level_elo = starting_elo + (elo_step * level)
|
||||
# Change this so that its more weighted towards the lower end with rare occurances of max_nodes_per_level rather than an even split
|
||||
var num_nodes = get_weighted_node_count(min_nodes_per_level, max_nodes_per_level)
|
||||
var available_positions = []
|
||||
for pos in range(positions_per_level):
|
||||
available_positions.append(pos)
|
||||
available_positions.shuffle()
|
||||
|
||||
|
||||
for i in range(num_nodes):
|
||||
var node_type = _get_random_room_type(level, num_levels)
|
||||
var node = {
|
||||
"id": _get_next_id(),
|
||||
"type": node_type,
|
||||
"level": level,
|
||||
"position": Vector2(available_positions[i], level),
|
||||
"elo": level_elo,
|
||||
"metadata": {}
|
||||
}
|
||||
node = generate_node_data(node, player)
|
||||
nodes.append(node)
|
||||
level_nodes.append(node)
|
||||
|
||||
levels_nodes[level] = level_nodes
|
||||
|
||||
# Connect nodes between levels
|
||||
# First connect starting node to level 1
|
||||
if levels_nodes.has(1) and levels_nodes[1].size() > 0:
|
||||
var num_connections = min(_rng.randi_range(2, 3), levels_nodes[1].size())
|
||||
var targets = levels_nodes[1].duplicate()
|
||||
targets.shuffle()
|
||||
|
||||
for i in range(num_connections):
|
||||
connections.append({
|
||||
"from": start_node.id,
|
||||
"to": targets[i].id
|
||||
})
|
||||
|
||||
# Keep track of which nodes are connected
|
||||
var connected_nodes = [start_node.id]
|
||||
for level in range(1, num_levels - 1):
|
||||
if not levels_nodes.has(level) or not levels_nodes.has(level + 1):
|
||||
continue
|
||||
|
||||
var current_level_nodes = levels_nodes[level]
|
||||
var next_level_nodes = levels_nodes[level + 1].duplicate()
|
||||
next_level_nodes.shuffle()
|
||||
|
||||
# For each node in current level that is connected from previous level
|
||||
for node in current_level_nodes:
|
||||
if _is_node_connected_to(node.id, connections):
|
||||
# Add to connected nodes
|
||||
connected_nodes.append(node.id)
|
||||
|
||||
# Connect to 1-2 nodes in next level if not the final level
|
||||
if level < num_levels - 2:
|
||||
var num_next_connections = _rng.randi_range(1, max_connections_per_node)
|
||||
num_next_connections = min(num_next_connections, next_level_nodes.size())
|
||||
var shouldTrim = _rng.randi_range(1, 15) > 3 || num_next_connections > 3
|
||||
for i in range(num_next_connections):
|
||||
if i < next_level_nodes.size():
|
||||
connections.append({
|
||||
"from": node.id,
|
||||
"to": next_level_nodes[i].id
|
||||
})
|
||||
|
||||
# # Remove the selected nodes so they aren't picked again
|
||||
# if lots of ocnnection earlier and deeper than lvl 3
|
||||
# if num_next_connections >= 2 && level > 3:
|
||||
if shouldTrim:
|
||||
for i in range(num_next_connections):
|
||||
if next_level_nodes.size() > 0:
|
||||
next_level_nodes.pop_front()
|
||||
|
||||
# Connect to final boss if at the level before
|
||||
elif level == num_levels - 2:
|
||||
connections.append({
|
||||
"from": node.id,
|
||||
"to": final_node.id
|
||||
})
|
||||
|
||||
# Remove any nodes that aren't connected
|
||||
var valid_nodes = []
|
||||
for node in nodes:
|
||||
if connected_nodes.has(node.id) or node.id == final_node.id:
|
||||
valid_nodes.append(node)
|
||||
|
||||
# Clean up connections to removed nodes
|
||||
var valid_connections = []
|
||||
for conn in connections:
|
||||
var from_valid = false
|
||||
var to_valid = false
|
||||
|
||||
for node in valid_nodes:
|
||||
if node.id == conn.from:
|
||||
from_valid = true
|
||||
if node.id == conn.to:
|
||||
to_valid = true
|
||||
|
||||
if from_valid and to_valid:
|
||||
valid_connections.append(conn)
|
||||
var index = 0
|
||||
for valid_node in valid_nodes:
|
||||
var isLeaf = true;
|
||||
for connection in valid_connections:
|
||||
# if theres outgoing connection we arent at a dead end
|
||||
if connection.from == valid_node.id:
|
||||
isLeaf = false
|
||||
break;
|
||||
valid_node.metadata.is_escape = isLeaf
|
||||
valid_nodes[index] = valid_node;
|
||||
index += 1
|
||||
|
||||
return {
|
||||
"nodes": valid_nodes,
|
||||
"connections": valid_connections,
|
||||
"levels": num_levels,
|
||||
"seed": _rng.seed
|
||||
}
|
||||
|
||||
func _get_random_room_type(level, total_levels):
|
||||
# return Utils.RoomType.BOSS
|
||||
var boss_chance = 0.1 + (level / float(total_levels) * 0.1)
|
||||
var shop_chance = 0.1 + (level / float(total_levels) * 0.05)
|
||||
var event_chance = 0.05
|
||||
|
||||
if level == total_levels - 2:
|
||||
boss_chance += 0.3
|
||||
|
||||
var roll = _rng.randf()
|
||||
if roll < boss_chance:
|
||||
return Utils.RoomType.BOSS
|
||||
elif roll < boss_chance + shop_chance:
|
||||
return Utils.RoomType.SHOP
|
||||
elif roll < boss_chance + shop_chance + event_chance:
|
||||
return Utils.RoomType.EVENT
|
||||
else:
|
||||
return Utils.RoomType.NORMAL
|
||||
|
||||
func _is_node_connected_to(node_id, connections):
|
||||
for conn in connections:
|
||||
if conn.to == node_id:
|
||||
return true
|
||||
return false
|
||||
|
||||
func _get_next_id():
|
||||
var id = _next_id
|
||||
_next_id += 1
|
||||
return id
|
||||
|
||||
|
||||
|
||||
func get_weighted_node_count(min_count, max_count):
|
||||
# Use exponential distribution to weight toward lower values
|
||||
var range_size = max_count - min_count + 1
|
||||
|
||||
var rand_val = _rng.randf()
|
||||
|
||||
# Apply exponential weighting (higher exponent = more weight toward minimum)
|
||||
var weight_exponent = 2 # Adjust this to control the curve
|
||||
var weighted_val = pow(rand_val, weight_exponent)
|
||||
|
||||
var node_count = min_count + floor(weighted_val * range_size)
|
||||
|
||||
if rand_val > 0.70:
|
||||
node_count = max_count
|
||||
|
||||
return node_count
|
||||
|
||||
func generate_node_data(node, player):
|
||||
var data = {
|
||||
"id": node.id,
|
||||
"type": node.type,
|
||||
"level": node.level,
|
||||
"position": node.position,
|
||||
"elo": node.elo,
|
||||
"metadata": node.metadata
|
||||
}
|
||||
|
||||
match data.type:
|
||||
Utils.RoomType.STARTING:
|
||||
data.metadata = generate_starting_data(data, player)
|
||||
Utils.RoomType.NORMAL:
|
||||
data.metadata = generate_chess_data(data, player)
|
||||
Utils.RoomType.BOSS:
|
||||
data.metadata = generate_boss_data(data, player)
|
||||
Utils.RoomType.FINAL:
|
||||
data.metadata = generate_final_data(data, player)
|
||||
Utils.RoomType.SHOP:
|
||||
data.metadata = generate_shop_data(data, player)
|
||||
Utils.RoomType.EVENT:
|
||||
data.metadata = generate_event_data(data, player)
|
||||
_:
|
||||
data.metadata = {}
|
||||
return data
|
||||
|
||||
|
||||
func generate_boss_data(node, player):
|
||||
# level_unit_distribution
|
||||
# current_max_level
|
||||
var rng = float(node.level) / int(current_max_level)
|
||||
var game_type = Utils.BossType.ZERG
|
||||
var height = 6;
|
||||
# (current_value, min_value, max_value, min_index, max_index)
|
||||
var index = map_to_array_index(node.level, 2, current_max_level - 2, 1, level_unit_distribution.size() - 1);
|
||||
var unit_string = level_unit_distribution[index]
|
||||
var special_unit = ""
|
||||
var pawn_string = ""
|
||||
var enemy_unit_depth = 3
|
||||
if game_type == Utils.BossType.ZERG:
|
||||
index = 7
|
||||
height = 7
|
||||
# if index > 7:
|
||||
unit_string = level_unit_distribution[index]
|
||||
elif game_type == Utils.BossType.DOUBLETROUBLE:
|
||||
index = 9
|
||||
height = 9
|
||||
elif game_type == Utils.BossType.WARLARD:
|
||||
index = 10
|
||||
height = 10
|
||||
unit_string = "1rnbqkbnr1"
|
||||
special_unit = "rnnnqkbbbr"
|
||||
# if game_type == "zerg":
|
||||
# index = map_to_array_index(node.level, 5, current_max_level - 2, 1, level_unit_distribution.size() - 1);
|
||||
|
||||
for x in unit_string.length():
|
||||
pawn_string += "p"
|
||||
|
||||
|
||||
var fen = "";
|
||||
# "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR"
|
||||
if node.level >= 7 and node.level <= 10:
|
||||
# height = node.level
|
||||
enemy_unit_depth = 3
|
||||
elif node.level > 10:
|
||||
# height = 10
|
||||
enemy_unit_depth = 4
|
||||
for x in height - enemy_unit_depth:
|
||||
if x == 0:
|
||||
fen += unit_string + "/"
|
||||
elif x == 1:
|
||||
fen += pawn_string + "/"
|
||||
# elif x == height - 3:
|
||||
# fen += "u"
|
||||
# # for y in unit_string.length() - 1:
|
||||
# # fen += "*"
|
||||
# fen += "u/"
|
||||
else:
|
||||
fen += str(unit_string.length()) + "/"
|
||||
|
||||
|
||||
if game_type == Utils.BossType.ZERG:
|
||||
for x in enemy_unit_depth - 1:
|
||||
fen += pawn_string.to_upper() + "/"
|
||||
fen += pawn_string.to_upper()
|
||||
elif game_type == Utils.BossType.WARLARD:
|
||||
fen += pawn_string.to_upper() + "/" + special_unit.to_upper()
|
||||
else:
|
||||
fen += pawn_string.to_upper() + "/" + unit_string.to_upper()
|
||||
var fen_ending = " w KQkq - 0 1"
|
||||
# print("generate_chess_data ", fen + fen_ending)
|
||||
# change condition
|
||||
|
||||
var win_condition = Utils.WinConditionType.TURN_NUMBER
|
||||
var win_target_turn = null
|
||||
var loss_target_unit = null
|
||||
var win_target_unit = null
|
||||
var boss_turn_additional = null
|
||||
var loss_condition = Utils.LossConditionType.UNIT_LOST
|
||||
|
||||
if game_type == Utils.BossType.ZERG:
|
||||
win_condition = Utils.WinConditionType.TURN_NUMBER
|
||||
# smallest board = 2 x 6 = 12
|
||||
# largest board = 10 x 12 = 120
|
||||
# smallest turn target 10
|
||||
# largest turn target 50
|
||||
var roomSize = height * unit_string.length()
|
||||
var clamped_value = clamp(roomSize, 12, 120)
|
||||
var percentage = (clamped_value - 12) / (120 - 12)
|
||||
|
||||
win_target_turn = 10 + percentage * (50 - 10)
|
||||
loss_target_unit = "King"
|
||||
loss_condition = Utils.LossConditionType.UNIT_LOST
|
||||
elif game_type == Utils.BossType.DOUBLETROUBLE:
|
||||
boss_turn_additional = 2
|
||||
win_condition = Utils.WinConditionType.CAPTURE_UNIT
|
||||
win_target_unit = "King"
|
||||
loss_target_unit = "King"
|
||||
loss_condition = Utils.LossConditionType.UNIT_LOST
|
||||
return {
|
||||
"is_escape": node.metadata.is_escape if node.metadata.has("is_escape") else false,
|
||||
"fen": fen + fen_ending,
|
||||
"game_type": "chess",
|
||||
"boss_type": game_type,
|
||||
"has_opponent": true,
|
||||
"boss_turn_additional": boss_turn_additional,
|
||||
"win_condition": win_condition,
|
||||
"win_target_turn": win_target_turn,
|
||||
"win_target_unit": win_target_unit,
|
||||
"loss_target_unit": loss_target_unit,
|
||||
"loss_condition": loss_condition,
|
||||
"reward": {
|
||||
"gold": 50 * node.level,
|
||||
"cards": [],
|
||||
"selection": generate_shop_cards(3, player),
|
||||
"selection_limit": 2
|
||||
},
|
||||
"elo": node.elo,
|
||||
}
|
||||
|
||||
|
||||
func generate_shop_data(node, player):
|
||||
var num_shop_cards = min(randi_range(5, 7), player.unlocked_cards.size())
|
||||
return {
|
||||
"is_escape": node.metadata.is_escape if node.metadata.has("is_escape") else false,
|
||||
"cards": generate_shop_cards(num_shop_cards, player),
|
||||
}
|
||||
|
||||
func generate_shop_cards(num_shop_cards, player):
|
||||
var shop_cards = []
|
||||
|
||||
var all_cards = []
|
||||
|
||||
for card_class in player.unlocked_cards:
|
||||
var card = create_new_card_instance(card_class)
|
||||
all_cards.append(card)
|
||||
|
||||
all_cards.shuffle()
|
||||
|
||||
|
||||
for i in range(num_shop_cards):
|
||||
shop_cards.append(all_cards[i % player.unlocked_cards.size()])
|
||||
|
||||
return shop_cards
|
||||
|
||||
|
||||
func generate_starting_data(node, player):
|
||||
return {
|
||||
"fen": "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1",
|
||||
"elo": node.elo,
|
||||
}
|
||||
|
||||
func generate_event_data(node, player):
|
||||
var index = map_to_array_index(node.level, 2, current_max_level - 2, 1, level_unit_distribution.size() - 1);
|
||||
var unit_string = level_unit_distribution[index]
|
||||
|
||||
var height = 6
|
||||
var width = unit_string.length()
|
||||
if node.level > 7 and node.level <= 10:
|
||||
height = node.level
|
||||
elif node.level > 10:
|
||||
height = 10
|
||||
# var mazedata = generate_maze(width, height)
|
||||
var mazedata = generate_maze(12, 12)
|
||||
# print("**************************")
|
||||
# print("**************************")
|
||||
# print("**************************")
|
||||
# print(mazedata)
|
||||
# print("**************************")
|
||||
# print("**************************")
|
||||
# print("**************************")
|
||||
return {
|
||||
"is_escape": node.metadata.is_escape if node.metadata.has("is_escape") else false,
|
||||
"fen": mazedata.fen + " w KQkq - 0 1",
|
||||
"game_type": "maze",
|
||||
"has_opponent": false,
|
||||
"win_condition": Utils.WinConditionType.TILE_REACHED,
|
||||
"win_target": [mazedata.end],
|
||||
"win_target_unit": "King",
|
||||
"elo": node.elo,
|
||||
"reward": {
|
||||
"gold": 150 * node.level,
|
||||
"cards": [],
|
||||
"selection": generate_shop_cards(5, player),
|
||||
"selection_limit": 1
|
||||
},
|
||||
}
|
||||
# "rnbqkbnr1/pppppppp1/9/9/9/9/9/PPPPPPPP1/RNBQKBNR1 w KQkq - 0 1"
|
||||
|
||||
func generate_final_data(node, player):
|
||||
# level_unit_distribution
|
||||
# current_max_level
|
||||
var rng = float(node.level) / int(current_max_level)
|
||||
# print(node.level, " ", 2, " ", current_max_level - 2, " ", 1, " ", level_unit_distribution.size() - 1)
|
||||
var index = map_to_array_index(node.level, 2, current_max_level - 2, 1, level_unit_distribution.size() - 1);
|
||||
# print("generate_chess_data ", index)
|
||||
var unit_string = level_unit_distribution[index]
|
||||
var pawn_string = ""
|
||||
|
||||
for x in unit_string.length():
|
||||
pawn_string += "p"
|
||||
|
||||
var height = 6;
|
||||
# "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR"
|
||||
var fen = "";
|
||||
if node.level > 8 and node.level <= 10:
|
||||
height = node.level
|
||||
elif node.level > 10:
|
||||
height = 10
|
||||
for x in height - 2:
|
||||
if x == 0:
|
||||
fen += unit_string + "/"
|
||||
elif x == 1:
|
||||
fen += pawn_string + "/"
|
||||
# elif x == height - 3:
|
||||
# fen += "u"
|
||||
# # for y in unit_string.length() - 1:
|
||||
# # fen += "*"
|
||||
# fen += "u/"
|
||||
else:
|
||||
fen += str(unit_string.length()) + "/"
|
||||
|
||||
fen += pawn_string.to_upper() + "/" + unit_string.to_upper()
|
||||
var fen_ending = " w KQkq - 0 1"
|
||||
# print("generate_chess_data ", fen + fen_ending)
|
||||
var win_condition = Utils.WinConditionType.CAPTURE_UNIT # Default
|
||||
var loss_condition = Utils.LossConditionType.UNIT_LOST # Default
|
||||
|
||||
# Randomly select a win condition with increasing variety at higher levels
|
||||
var rng_val = _rng.randf()
|
||||
var conditions_pool = [Utils.WinConditionType.CAPTURE_UNIT] # Default pool
|
||||
|
||||
var additional_metadata = {}
|
||||
var result = {
|
||||
"is_escape": true,
|
||||
"fen": fen + fen_ending,
|
||||
"game_type": "chess",
|
||||
"win_condition": win_condition,
|
||||
"loss_condition": loss_condition,
|
||||
"has_opponent": true,
|
||||
"elo": node.elo,
|
||||
"reward": {
|
||||
"gold": 50 * node.level
|
||||
}
|
||||
}
|
||||
# Merge additional metadata
|
||||
for key in additional_metadata:
|
||||
result[key] = additional_metadata[key]
|
||||
|
||||
# print("final_node ", result)
|
||||
return result
|
||||
|
||||
func generate_chess_data(node, player):
|
||||
# level_unit_distribution
|
||||
# current_max_level
|
||||
var rng = float(node.level) / int(current_max_level)
|
||||
# print(node.level, " ", 2, " ", current_max_level - 2, " ", 1, " ", level_unit_distribution.size() - 1)
|
||||
var index = map_to_array_index(node.level, 2, current_max_level - 2, 1, level_unit_distribution.size() - 1);
|
||||
# print("generate_chess_data ", index)
|
||||
var unit_string = level_unit_distribution[index]
|
||||
var pawn_string = ""
|
||||
var height = 6;
|
||||
# unit_string = level_unit_distribution[level_unit_distribution.size() - 1]
|
||||
# height = 8
|
||||
for x in unit_string.length():
|
||||
pawn_string += "p"
|
||||
|
||||
# "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR"
|
||||
var fen = "";
|
||||
if node.level > 8 and node.level <= 10:
|
||||
height = node.level
|
||||
elif node.level > 10:
|
||||
height = 10
|
||||
for x in height - 2:
|
||||
if x == 0:
|
||||
fen += unit_string + "/"
|
||||
elif x == 1:
|
||||
fen += pawn_string + "/"
|
||||
# elif x == height - 3:
|
||||
# fen += "u"
|
||||
# # for y in unit_string.length() - 1:
|
||||
# # fen += "*"
|
||||
# fen += "u/"
|
||||
else:
|
||||
fen += str(unit_string.length()) + "/"
|
||||
|
||||
fen += pawn_string.to_upper() + "/" + unit_string.to_upper()
|
||||
var fen_ending = " w KQkq - 0 1"
|
||||
# print("generate_chess_data ", fen + fen_ending)
|
||||
var win_condition = Utils.WinConditionType.CAPTURE_UNIT # Default
|
||||
var loss_condition = Utils.LossConditionType.UNIT_LOST # Default
|
||||
|
||||
# Randomly select a win condition with increasing variety at higher levels
|
||||
var rng_val = _rng.randf()
|
||||
var conditions_pool = [Utils.WinConditionType.CAPTURE_UNIT] # Default pool
|
||||
|
||||
# # Add more varied win conditions at higher levels
|
||||
# if node.level >= 3:
|
||||
# conditions_pool.append(Utils.WinConditionType.BOARD_CLEARED)
|
||||
# if node.level >= 5:
|
||||
# conditions_pool.append(Utils.WinConditionType.TILE_REACHED)
|
||||
# if node.level >= 7:
|
||||
# conditions_pool.append(Utils.WinConditionType.TURN_NUMBER)
|
||||
|
||||
# # For certain special nodes, always use specific conditions
|
||||
# if node.type == Utils.RoomType.EVENT:
|
||||
# win_condition = Utils.WinConditionType.TILE_REACHED
|
||||
# elif node.type == Utils.RoomType.BOSS and node.level > 5:
|
||||
# # Bosses at higher levels have more varied win conditions
|
||||
# win_condition = conditions_pool[_rng.randi() % conditions_pool.size()]
|
||||
# else:
|
||||
# # Regular nodes have weighted randomness
|
||||
# if rng_val < 0.7: # 70% chance of default capture king
|
||||
# win_condition = Utils.WinConditionType.CAPTURE_UNIT
|
||||
# else:
|
||||
# # Pick randomly from the conditions pool
|
||||
# win_condition = conditions_pool[_rng.randi() % conditions_pool.size()]
|
||||
|
||||
# Create additional metadata for specific win conditions
|
||||
var additional_metadata = {}
|
||||
|
||||
# match win_condition:
|
||||
# Utils.WinConditionType.TILE_REACHED:
|
||||
# # Generate target tiles for the Tile win condition
|
||||
# var target_tiles = []
|
||||
# var target_count = 1 # Default to 1 target tile
|
||||
|
||||
# # Determine target unit (default is any piece)
|
||||
# var target_unit = ""
|
||||
# if _rng.randf() < 0.5: # 50% chance of specific piece
|
||||
# var pieces = ["Pawn", "Knight", "Bishop", "Rook", "Queen"]
|
||||
# target_unit = pieces[_rng.randi() % pieces.size()]
|
||||
|
||||
# # Create target tiles at the enemy's back rank
|
||||
# var target_x = _rng.randi() % unit_string.length()
|
||||
# target_tiles.append(str(target_x) + "-0") # Top row
|
||||
|
||||
# additional_metadata["target_tiles"] = target_tiles
|
||||
# additional_metadata["target_unit"] = target_unit
|
||||
|
||||
# Utils.WinConditionType.TURN_NUMBER:
|
||||
# # Set a target turn number
|
||||
# additional_metadata["target_turn"] = (node.level * 2) + _rng.randi_range(5, 10)
|
||||
|
||||
# # Also add a turn limit for loss condition
|
||||
# loss_condition = Utils.LossConditionType.TURN_NUMBER
|
||||
# additional_metadata["turn_limit"] = additional_metadata["target_turn"] + _rng.randi_range(5, 15)
|
||||
|
||||
# Build the result metadata
|
||||
var result = {
|
||||
"is_escape": node.metadata.is_escape if node.metadata.has("is_escape") else false,
|
||||
"fen": fen + fen_ending,
|
||||
"game_type": "chess",
|
||||
"win_condition": win_condition,
|
||||
"loss_condition": loss_condition,
|
||||
"has_opponent": true,
|
||||
"elo": node.elo,
|
||||
"reward": {
|
||||
"gold": 50 * node.level
|
||||
}
|
||||
}
|
||||
|
||||
# Merge additional metadata
|
||||
for key in additional_metadata:
|
||||
result[key] = additional_metadata[key]
|
||||
|
||||
return result
|
||||
|
||||
func map_to_array_index(current_value, min_value, max_value, min_index, max_index):
|
||||
# Ensure the current value is within bounds
|
||||
var clamped_value = clamp(current_value, min_value, max_value)
|
||||
|
||||
# Calculate how far along the input range we are (0.0 to 1.0)
|
||||
var normalized_position = float(clamped_value - min_value) / float(max_value - min_value)
|
||||
|
||||
# Map this to our target index range
|
||||
var index_range = max_index - min_index
|
||||
var mapped_index = min_index + round(normalized_position * index_range)
|
||||
|
||||
# Ensure we're returning an integer within the valid array index range
|
||||
return int(clamp(mapped_index, min_index, max_index))
|
||||
|
||||
|
||||
|
||||
func generate_maze(width: int, height: int) -> Dictionary:
|
||||
# Ensure dimensions are odd to have proper walls
|
||||
if width % 2 == 0:
|
||||
width += 1
|
||||
if height % 2 == 0:
|
||||
height += 1
|
||||
|
||||
# Initialize the maze with all walls
|
||||
var maze = []
|
||||
for y in range(height):
|
||||
var row = []
|
||||
for x in range(width):
|
||||
row.append("*") # * represents wall
|
||||
maze.append(row)
|
||||
|
||||
# Use a recursive backtracking algorithm to generate the maze
|
||||
var rng = RandomNumberGenerator.new()
|
||||
rng.randomize()
|
||||
|
||||
# Start at a random odd position
|
||||
var start_x = rng.randi_range(0, width/2-1) * 2 + 1
|
||||
var start_y = rng.randi_range(0, height/2-1) * 2 + 1
|
||||
|
||||
# Carve the maze recursively
|
||||
_carve_maze(maze, start_x, start_y, width, height, rng)
|
||||
|
||||
# Pick a random end point (far from start point)
|
||||
var end_x = 0
|
||||
var end_y = 0
|
||||
var max_distance = 0
|
||||
|
||||
for y in range(1, height, 2):
|
||||
for x in range(1, width, 2):
|
||||
if maze[y][x] == " ": # Only consider path cells
|
||||
var distance = abs(x - start_x) + abs(y - start_y)
|
||||
if distance > max_distance:
|
||||
max_distance = distance
|
||||
end_x = x
|
||||
end_y = y
|
||||
|
||||
maze[start_y][start_x] = "k"
|
||||
|
||||
# Mark the end position with a space (keep it as a path)
|
||||
# It's already a space, but we ensure it here
|
||||
maze[end_y][end_x] = " "
|
||||
|
||||
# Convert the maze to a FEN-like string
|
||||
var fen_string = ""
|
||||
for y in range(height):
|
||||
var empty_count = 0
|
||||
for x in range(width):
|
||||
if maze[y][x] == " ": # Empty space
|
||||
empty_count += 1
|
||||
else:
|
||||
if empty_count > 0:
|
||||
fen_string += str(empty_count)
|
||||
empty_count = 0
|
||||
fen_string += maze[y][x]
|
||||
|
||||
# Add any remaining empty count
|
||||
if empty_count > 0:
|
||||
fen_string += str(empty_count)
|
||||
|
||||
# Add row separator (except for the last row)
|
||||
if y < height - 1:
|
||||
fen_string += "/"
|
||||
return {
|
||||
"fen": fen_string,
|
||||
"start": str(start_x) + "-"+ str(start_y),
|
||||
"end": str(end_x) + "-"+ str(end_y)
|
||||
}
|
||||
|
||||
# Recursive function to carve out the maze
|
||||
func _carve_maze(maze, x, y, width, height, rng):
|
||||
# Mark the current cell as a path
|
||||
maze[y][x] = " "
|
||||
|
||||
# Define the four possible directions (up, right, down, left)
|
||||
var directions = [[0, -2], [2, 0], [0, 2], [-2, 0]]
|
||||
|
||||
# Shuffle directions for randomness
|
||||
directions.shuffle()
|
||||
|
||||
# Try each direction
|
||||
for dir in directions:
|
||||
var nx = x + dir[0]
|
||||
var ny = y + dir[1]
|
||||
|
||||
# Check if the new position is valid and unvisited
|
||||
if nx > 0 and nx < width-1 and ny > 0 and ny < height-1 and maze[ny][nx] == "*":
|
||||
# Carve a path between the current cell and the new cell
|
||||
maze[y + dir[1]/2][x + dir[0]/2] = " "
|
||||
|
||||
# Recursively carve from the new cell
|
||||
_carve_maze(maze, nx, ny, width, height, rng)
|
||||
|
||||
|
||||
|
||||
func create_new_card_instance(template_card: Card) -> Card:
|
||||
var new_card = null
|
||||
|
||||
var script = template_card.get_script()
|
||||
|
||||
if script:
|
||||
new_card = script.new()
|
||||
else:
|
||||
print("Warning: Could not get script from card: " + template_card.cardName)
|
||||
|
||||
var class_list = ProjectSettings.get_global_class_list()
|
||||
var card_class_name = template_card.get_class()
|
||||
|
||||
for class_info in class_list:
|
||||
if class_info["class"] == card_class_name:
|
||||
var card_script = load(class_info["path"])
|
||||
if card_script:
|
||||
new_card = card_script.new()
|
||||
break
|
||||
|
||||
return new_card
|
||||
|
|
@ -1 +0,0 @@
|
|||
uid://bs0u5jjxm2c18
|
||||
|
|
@ -1,556 +0,0 @@
|
|||
extends Control
|
||||
class_name MapScreen
|
||||
|
||||
signal back_pressed
|
||||
signal deckmanager_open_requested(options)
|
||||
signal node_selected(node_data)
|
||||
signal map_visibility_changed(is_visible)
|
||||
const MapGenerator = preload("res://Systems/Game/Map/MapGenerator.gd")
|
||||
# Room type constants
|
||||
|
||||
# Node config
|
||||
const NODE_SIZE = Vector2(60, 60)
|
||||
const NODE_SPACING_X = 150
|
||||
const NODE_SPACING_Y = 120
|
||||
const LINE_WIDTH = 3
|
||||
const LINE_COLOR = Color(0.2, 0.2, 0.2)
|
||||
const LINE_COLOR_SELECTED = Color(0.2, 0.6, 0.2)
|
||||
const LINE_COLOR_ACCESSIBLE = Color(0.6, 0.6, 0.2)
|
||||
|
||||
const NODE_COLOR = Color(0.1, 0.1, 0.1, 0.5)
|
||||
const NODE_LEAF = Color(0.2, 0.6, 0.2)
|
||||
# Node symbols and colors
|
||||
const NODE_SYMBOLS = {
|
||||
Utils.RoomType.STARTING: "O", # Circle
|
||||
Utils.RoomType.NORMAL: "■", # Square
|
||||
Utils.RoomType.BOSS: "★", # Star
|
||||
Utils.RoomType.FINAL: "✱", # Burst
|
||||
Utils.RoomType.SHOP: "₵", # Star
|
||||
Utils.RoomType.EVENT: "?" # Burst
|
||||
}
|
||||
|
||||
const NODE_COLORS = {
|
||||
Utils.RoomType.STARTING: Color(1, 1, 1), # White
|
||||
Utils.RoomType.NORMAL: Color(0.2, 0.2, 0.2), # Dark gray
|
||||
Utils.RoomType.BOSS: Color(0.9, 0.2, 0.2), # Red
|
||||
Utils.RoomType.FINAL: Color(0.9, 0.9, 0.2), # Yellow
|
||||
Utils.RoomType.SHOP: Color(0.2, 0.7, 0.9), # Yellow
|
||||
Utils.RoomType.EVENT: Color(0.8, 0.4, 0.9) # Yellow
|
||||
}
|
||||
|
||||
var map_nodes = []
|
||||
var map_connections = []
|
||||
var node_buttons = {}
|
||||
var connection_lines = []
|
||||
var traversed_map = []
|
||||
var current_node = null
|
||||
|
||||
const SCROLL_PADDING_TOP = 80
|
||||
const SCROLL_PADDING_BOTTOM = 80
|
||||
const SCROLL_PADDING_LEFT = 60
|
||||
const SCROLL_PADDING_RIGHT = 100
|
||||
|
||||
var node_popup_scene = preload("res://node_map_popup.tscn")
|
||||
var selected_node_panel: NodePopup = null
|
||||
|
||||
@onready var map_container = $MapScrollContainer/MapContainer
|
||||
@onready var back_button = $BackButton
|
||||
@onready var deck_manager_button = $DeckManagerButton
|
||||
@onready var title_label = $TitleLabel
|
||||
@onready var legend_container = $LegendContainer
|
||||
@onready var gold_label = $GoldLabel
|
||||
|
||||
func _ready():
|
||||
# Connect back button
|
||||
if back_button:
|
||||
back_button.visible = false;
|
||||
back_button.connect("pressed", Callable(self, "_on_back_button_pressed"))
|
||||
if deck_manager_button:
|
||||
deck_manager_button.connect("pressed", Callable(self, "_on_deck_button_pressed"))
|
||||
|
||||
# Create legend
|
||||
create_legend()
|
||||
# generate_map()
|
||||
# display_map()
|
||||
selected_node_panel = node_popup_scene.instantiate()
|
||||
selected_node_panel.visible = false
|
||||
add_child(selected_node_panel)
|
||||
|
||||
if selected_node_panel:
|
||||
selected_node_panel.pressed.connect(on_node_panel_pressed)
|
||||
|
||||
func create_legend():
|
||||
# Create legend for room types
|
||||
var legend_items = [
|
||||
{"type": Utils.RoomType.BOSS, "text": "Boss"},
|
||||
{"type": Utils.RoomType.STARTING, "text": "Starting Room"},
|
||||
{"type": Utils.RoomType.FINAL, "text": "Final Boss"},
|
||||
{"type": Utils.RoomType.NORMAL, "text": "Normal Room"},
|
||||
{"type": Utils.RoomType.SHOP, "text": "Shop"},
|
||||
{"type": Utils.RoomType.EVENT, "text": "Event"},
|
||||
{"type": Utils.RoomType.NORMAL, "text": "Escape Room"}
|
||||
]
|
||||
|
||||
for item in legend_items:
|
||||
var hbox = HBoxContainer.new()
|
||||
hbox.add_theme_constant_override("separation", 10)
|
||||
|
||||
var symbol = Label.new()
|
||||
symbol.text = NODE_SYMBOLS[item.type]
|
||||
if item.text != "Escape Room":
|
||||
symbol.add_theme_color_override("font_color", NODE_COLORS[item.type])
|
||||
else:
|
||||
symbol.add_theme_color_override("font_color", NODE_LEAF)
|
||||
symbol.add_theme_font_size_override("font_size", 24)
|
||||
|
||||
var text = Label.new()
|
||||
text.text = item.text
|
||||
|
||||
hbox.add_child(symbol)
|
||||
hbox.add_child(text)
|
||||
legend_container.add_child(hbox)
|
||||
|
||||
func generate_map(options):
|
||||
# {"mode": options.mode}
|
||||
# Clear existing map
|
||||
|
||||
var game = get_node_or_null("/root/Board") as ChessGame
|
||||
var player = game.player
|
||||
map_nodes.clear()
|
||||
map_connections.clear()
|
||||
connection_lines.clear()
|
||||
traversed_map.clear()
|
||||
var mapGen = MapGenerator.new().generate_map(options.mode, player)
|
||||
# Create starting node
|
||||
# var start_node = {
|
||||
# "id": 0,
|
||||
# "type": Utils.RoomType.STARTING,
|
||||
# "level": 0,
|
||||
# "position": Vector2(3, 4) # Column, Row
|
||||
# }
|
||||
# map_nodes.append(start_node)
|
||||
# current_node = start_node
|
||||
|
||||
# # Create final boss node
|
||||
# var final_node = {
|
||||
# "id": 1,
|
||||
# "type": Utils.RoomType.FINAL,
|
||||
# "level": levels - 1,
|
||||
# "position": Vector2(3, 0) # Column, Row
|
||||
# }
|
||||
# map_nodes.append(final_node)
|
||||
for node_data in mapGen.nodes:
|
||||
add_node(
|
||||
node_data.id,
|
||||
node_data.type,
|
||||
node_data.level,
|
||||
node_data.position,
|
||||
node_data.elo,
|
||||
node_data.metadata
|
||||
)
|
||||
|
||||
# Store elo rating if needed
|
||||
map_nodes[map_nodes.size() - 1].elo = node_data.elo
|
||||
|
||||
# Set current node to starting node (id 0)
|
||||
if node_data.id == 0:
|
||||
current_node = map_nodes[map_nodes.size() - 1]
|
||||
|
||||
# Process connections
|
||||
for connection in mapGen.connections:
|
||||
add_connection(connection.from, connection.to)
|
||||
|
||||
|
||||
func add_node(id, type, level, position, elo, metadata):
|
||||
map_nodes.append({
|
||||
"id": id,
|
||||
"type": type,
|
||||
"level": level,
|
||||
"position": position,
|
||||
"elo": elo,
|
||||
"metadata": metadata,
|
||||
})
|
||||
|
||||
func add_connection(from_id, to_id):
|
||||
map_connections.append({
|
||||
"from": from_id,
|
||||
"to": to_id
|
||||
})
|
||||
|
||||
func display_map():
|
||||
# Clear previous display
|
||||
for child in map_container.get_children():
|
||||
child.queue_free()
|
||||
node_buttons.clear()
|
||||
connection_lines = []
|
||||
|
||||
# Calculate map dimensions for proper padding
|
||||
var min_y = 9999
|
||||
var max_y = 0
|
||||
var min_x = 9999
|
||||
var max_x = 0
|
||||
# Find boundaries of the map
|
||||
for node_data in map_nodes:
|
||||
var x_pos = node_data.position.x * NODE_SPACING_X
|
||||
var y_pos = node_data.position.y * NODE_SPACING_Y
|
||||
min_x = min(min_x, x_pos)
|
||||
max_x = max(max_x, x_pos)
|
||||
min_y = min(min_y, y_pos)
|
||||
max_y = max(max_y, y_pos)
|
||||
|
||||
var map_width = max_x - min_x + NODE_SIZE.x * 2 + SCROLL_PADDING_LEFT + SCROLL_PADDING_RIGHT
|
||||
var map_height = max_y - min_y + NODE_SIZE.y * 2 + SCROLL_PADDING_TOP + SCROLL_PADDING_BOTTOM
|
||||
map_container.custom_minimum_size = Vector2(map_width, map_height)
|
||||
|
||||
var top_padding = Control.new()
|
||||
top_padding.custom_minimum_size = Vector2(map_width, SCROLL_PADDING_TOP)
|
||||
top_padding.position = Vector2(0, 0)
|
||||
map_container.add_child(top_padding)
|
||||
|
||||
var bottom_padding = Control.new()
|
||||
bottom_padding.custom_minimum_size = Vector2(map_width, SCROLL_PADDING_BOTTOM)
|
||||
bottom_padding.position = Vector2(0, map_height - SCROLL_PADDING_BOTTOM)
|
||||
map_container.add_child(bottom_padding)
|
||||
|
||||
var left_padding = Control.new()
|
||||
left_padding.custom_minimum_size = Vector2(SCROLL_PADDING_LEFT, map_height)
|
||||
left_padding.position = Vector2(0, 0)
|
||||
map_container.add_child(left_padding)
|
||||
|
||||
var right_padding = Control.new()
|
||||
right_padding.custom_minimum_size = Vector2(SCROLL_PADDING_RIGHT, map_height)
|
||||
right_padding.position = Vector2(map_width - SCROLL_PADDING_RIGHT, 0)
|
||||
map_container.add_child(right_padding)
|
||||
|
||||
|
||||
# Draw connections first (so they're behind nodes)
|
||||
for connection in map_connections:
|
||||
var from_node = get_node_by_id(connection.from)
|
||||
var to_node = get_node_by_id(connection.to)
|
||||
|
||||
if from_node and to_node:
|
||||
draw_curved_connection(from_node, to_node)
|
||||
|
||||
# Draw nodes
|
||||
for node_data in map_nodes:
|
||||
draw_node(node_data)
|
||||
|
||||
|
||||
func get_node_by_id(id):
|
||||
for node in map_nodes:
|
||||
if node.id == id:
|
||||
return node
|
||||
return null
|
||||
|
||||
func draw_node(node_data):
|
||||
var isLeaf = true;
|
||||
for connection in map_connections:
|
||||
# if theres outgoing connection we arent at a dead end
|
||||
# dont change final node colour
|
||||
if connection.from == node_data.id || node_data.id == 1:
|
||||
isLeaf = false
|
||||
break;
|
||||
|
||||
var button = Button.new()
|
||||
button.text = NODE_SYMBOLS[node_data.type]
|
||||
button.custom_minimum_size = NODE_SIZE
|
||||
button.flat = true
|
||||
|
||||
# Add some styling
|
||||
var font = FontFile.new()
|
||||
var style = StyleBoxFlat.new()
|
||||
style.bg_color = NODE_COLOR
|
||||
style.corner_radius_top_left = 30
|
||||
style.corner_radius_top_right = 30
|
||||
style.corner_radius_bottom_left = 30
|
||||
style.corner_radius_bottom_right = 30
|
||||
style.border_width_left = 2
|
||||
style.border_width_top = 2
|
||||
style.border_width_right = 2
|
||||
style.border_width_bottom = 2
|
||||
if isLeaf:
|
||||
style.bg_color = NODE_LEAF
|
||||
|
||||
style.border_color = NODE_COLORS[node_data.type]
|
||||
|
||||
button.add_theme_font_size_override("font_size", 28)
|
||||
if isLeaf:
|
||||
button.add_theme_color_override("font_color", NODE_LEAF)
|
||||
# style.bg_color = NODE_LEAF
|
||||
else:
|
||||
button.add_theme_color_override("font_color", NODE_COLORS[node_data.type])
|
||||
button.add_theme_stylebox_override("normal", style)
|
||||
|
||||
# Position the node with top and left padding adjustments
|
||||
var x_pos = node_data.position.x * NODE_SPACING_X + SCROLL_PADDING_LEFT
|
||||
var y_pos = node_data.position.y * NODE_SPACING_Y + SCROLL_PADDING_TOP
|
||||
button.position = Vector2(x_pos, y_pos) - NODE_SIZE/2
|
||||
|
||||
# Store data
|
||||
button.set_meta("node_data", node_data)
|
||||
|
||||
# Connect signal
|
||||
button.pressed.connect(func(): _on_node_pressed(node_data))
|
||||
# Add to map
|
||||
map_container.add_child(button)
|
||||
node_buttons[node_data.id] = button
|
||||
|
||||
# Highlight current node
|
||||
if current_node and node_data.id == current_node.id:
|
||||
highlight_current_node(button)
|
||||
|
||||
func on_node_panel_pressed(id):
|
||||
# print("on_node_panel_pressed ", id )
|
||||
if id != null:
|
||||
|
||||
current_node = null
|
||||
for node in map_nodes:
|
||||
if node.id == id:
|
||||
current_node = node
|
||||
break
|
||||
|
||||
traversed_node(current_node)
|
||||
# Refresh display to update highlights
|
||||
display_map()
|
||||
|
||||
# Emit signal with selected node data
|
||||
emit_signal("node_selected", current_node)
|
||||
selected_node_panel.visible = false;
|
||||
func draw_connection(from_node, to_node):
|
||||
var line = Line2D.new()
|
||||
line.width = LINE_WIDTH
|
||||
line.default_color = LINE_COLOR
|
||||
if from_node.id == current_node.id:
|
||||
line.default_color = LINE_COLOR_ACCESSIBLE
|
||||
elif traversed_map.has(to_node.id) and (traversed_map.has(from_node.id) || from_node.id == 0):
|
||||
line.default_color = LINE_COLOR_SELECTED
|
||||
|
||||
# Calculate start and end positions with top and left padding adjustments
|
||||
var start_pos = from_node.position * Vector2(NODE_SPACING_X, NODE_SPACING_Y) + Vector2(SCROLL_PADDING_LEFT, SCROLL_PADDING_TOP)
|
||||
var end_pos = to_node.position * Vector2(NODE_SPACING_X, NODE_SPACING_Y) + Vector2(SCROLL_PADDING_LEFT, SCROLL_PADDING_TOP)
|
||||
|
||||
# Add points
|
||||
line.add_point(start_pos)
|
||||
line.add_point(end_pos)
|
||||
|
||||
# Add to map
|
||||
map_container.add_child(line)
|
||||
connection_lines.append(line)
|
||||
|
||||
func highlight_current_node(button):
|
||||
var style = button.get_theme_stylebox("normal").duplicate()
|
||||
style.bg_color = Color(0.3, 0.3, 0.3, 0.7)
|
||||
style.border_width_left = 4
|
||||
style.border_width_top = 4
|
||||
style.border_width_right = 4
|
||||
style.border_width_bottom = 4
|
||||
button.add_theme_stylebox_override("normal", style)
|
||||
|
||||
func _on_node_pressed(node_data):
|
||||
print("SELECTED NODE", node_data)
|
||||
if is_node_accessible(node_data) || is_node_path_accessible(node_data):
|
||||
# Get node type description
|
||||
var node_name = get_node_type_name(node_data.type)
|
||||
var node_desc = get_node_description(node_data)
|
||||
|
||||
# Position the popup near the clicked node
|
||||
var button_pos = node_buttons[node_data.id].global_position
|
||||
var popup_pos = button_pos + Vector2(NODE_SIZE.x/2, NODE_SIZE.y/2)
|
||||
selected_node_panel.global_position = popup_pos
|
||||
|
||||
# Set popup data and make it visible
|
||||
selected_node_panel.set_card(node_data.id, node_name, node_desc)
|
||||
if traversed_map.has(node_data.id):
|
||||
selected_node_panel._disable_enter()
|
||||
else:
|
||||
selected_node_panel._enable_enter()
|
||||
selected_node_panel.visible = true
|
||||
else:
|
||||
print("Node not accessible from current position", node_data)
|
||||
|
||||
func traversed_node(node_data):
|
||||
traversed_map.append(node_data.id)
|
||||
|
||||
func is_node_path_accessible(node_data):
|
||||
if current_node == null:
|
||||
return node_data.type == Utils.RoomType.STARTING
|
||||
|
||||
# Check if there's a direct connection from current node to the path travelled
|
||||
for id in traversed_map:
|
||||
var cur = get_node_by_id(id)
|
||||
for connection in map_connections:
|
||||
if connection.from == cur.id and connection.to == node_data.id:
|
||||
return true
|
||||
|
||||
return false
|
||||
|
||||
func is_node_accessible(node_data):
|
||||
if current_node == null:
|
||||
return node_data.type == Utils.RoomType.STARTING
|
||||
|
||||
# Check if there's a direct connection from current node to target node
|
||||
for connection in map_connections:
|
||||
if connection.from == current_node.id and connection.to == node_data.id:
|
||||
return true
|
||||
|
||||
return false
|
||||
|
||||
func _on_back_button_pressed():
|
||||
emit_signal("back_pressed")
|
||||
visible = false
|
||||
|
||||
|
||||
func _on_deck_button_pressed():
|
||||
emit_signal("deckmanager_open_requested", {})
|
||||
visible = false
|
||||
|
||||
func draw_curved_connection(from_node, to_node):
|
||||
var line = Line2D.new()
|
||||
line.width = LINE_WIDTH
|
||||
line.default_color = LINE_COLOR
|
||||
if from_node.id == current_node.id:
|
||||
line.default_color = LINE_COLOR_ACCESSIBLE
|
||||
elif traversed_map.has(to_node.id) and (traversed_map.has(from_node.id) || from_node.id == 0):
|
||||
line.default_color = LINE_COLOR_SELECTED
|
||||
|
||||
var start_pos = from_node.position * Vector2(NODE_SPACING_X, NODE_SPACING_Y) + Vector2(SCROLL_PADDING_LEFT, SCROLL_PADDING_TOP)
|
||||
var end_pos = to_node.position * Vector2(NODE_SPACING_X, NODE_SPACING_Y) + Vector2(SCROLL_PADDING_LEFT, SCROLL_PADDING_TOP)
|
||||
|
||||
# Create a bezier curve path
|
||||
var points = []
|
||||
|
||||
# If nodes are on different levels (y positions)
|
||||
if from_node.position.y != to_node.position.y:
|
||||
var mid_y = (start_pos.y + end_pos.y) / 2
|
||||
var control_offset = NODE_SPACING_Y * 0.5 * (1 + abs(from_node.position.x - to_node.position.x) * 0.2)
|
||||
|
||||
var control1 = Vector2(start_pos.x, start_pos.y + control_offset)
|
||||
var control2 = Vector2(end_pos.x, end_pos.y - control_offset)
|
||||
|
||||
# Add more points for a smoother curve
|
||||
for i in range(11): # 0.0, 0.1, 0.2, ..., 1.0
|
||||
var t = i / 10.0
|
||||
var point = cubic_bezier(start_pos, control1, control2, end_pos, t)
|
||||
points.append(point)
|
||||
else:
|
||||
# For nodes on the same level, use a simple curved path
|
||||
var mid_x = (start_pos.x + end_pos.x) / 2
|
||||
var mid_y = start_pos.y
|
||||
var curve_height = NODE_SPACING_Y * 0.3 * (1 + abs(from_node.position.x - to_node.position.x) * 0.1)
|
||||
|
||||
if to_node.position.x > from_node.position.x:
|
||||
mid_y -= curve_height
|
||||
else:
|
||||
mid_y += curve_height
|
||||
|
||||
var mid_point = Vector2(mid_x, mid_y)
|
||||
|
||||
# Create a quadratic bezier curve with just 3 points
|
||||
for i in range(11):
|
||||
var t = i / 10.0
|
||||
var point = quadratic_bezier(start_pos, mid_point, end_pos, t)
|
||||
points.append(point)
|
||||
|
||||
# Add the points to the line
|
||||
for point in points:
|
||||
line.add_point(point)
|
||||
|
||||
# Add to map
|
||||
map_container.add_child(line)
|
||||
connection_lines.append(line)
|
||||
|
||||
func cubic_bezier(p0: Vector2, p1: Vector2, p2: Vector2, p3: Vector2, t: float) -> Vector2:
|
||||
var q0 = p0.lerp(p1, t)
|
||||
var q1 = p1.lerp(p2, t)
|
||||
var q2 = p2.lerp(p3, t)
|
||||
|
||||
var r0 = q0.lerp(q1, t)
|
||||
var r1 = q1.lerp(q2, t)
|
||||
|
||||
return r0.lerp(r1, t)
|
||||
|
||||
func quadratic_bezier(p0: Vector2, p1: Vector2, p2: Vector2, t: float) -> Vector2:
|
||||
var q0 = p0.lerp(p1, t)
|
||||
var q1 = p1.lerp(p2, t)
|
||||
|
||||
return q0.lerp(q1, t)
|
||||
|
||||
|
||||
|
||||
|
||||
func get_node_type_name(type):
|
||||
match type:
|
||||
Utils.RoomType.STARTING:
|
||||
return "Starting Room"
|
||||
Utils.RoomType.NORMAL:
|
||||
return "Chess Match"
|
||||
Utils.RoomType.BOSS:
|
||||
return "Boss Battle"
|
||||
Utils.RoomType.FINAL:
|
||||
return "Final Boss"
|
||||
Utils.RoomType.SHOP:
|
||||
return "Card Shop"
|
||||
Utils.RoomType.EVENT:
|
||||
return "Random Event"
|
||||
_:
|
||||
return "Unknown Room"
|
||||
|
||||
|
||||
func get_node_description(node_data):
|
||||
var desc = ""
|
||||
match node_data.type:
|
||||
Utils.RoomType.STARTING:
|
||||
desc = "Begin the dungeon"
|
||||
Utils.RoomType.NORMAL:
|
||||
desc = "A chess match \n\n ELO: " + str(node_data.elo)
|
||||
Utils.RoomType.BOSS:
|
||||
desc = "Maybe add some Boss lore? \n\n ELO: " + str(node_data.elo)
|
||||
Utils.RoomType.FINAL:
|
||||
desc = "BHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHA"
|
||||
Utils.RoomType.SHOP:
|
||||
desc = "Purchase new cards"
|
||||
Utils.RoomType.EVENT:
|
||||
desc = "A random event from pawn zerg to slot machine"
|
||||
_:
|
||||
desc = "An unknown room type."
|
||||
|
||||
# Add additional info if this is a leaf node (dead end)
|
||||
var isLeaf = true
|
||||
for connection in map_connections:
|
||||
if connection.from == node_data.id || node_data.id == 1:
|
||||
isLeaf = false
|
||||
break
|
||||
|
||||
if isLeaf && node_data.type != Utils.RoomType.FINAL:
|
||||
desc += "\n\nWarning: This is an escape node"
|
||||
|
||||
return desc
|
||||
|
||||
|
||||
|
||||
func update_gold_display():
|
||||
var game = get_node_or_null("/root/Board") as ChessGame
|
||||
if gold_label:
|
||||
gold_label.text = str(game.player.get_gold()) + " GOLD"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# Notification
|
||||
func _notification(what):
|
||||
if what == NOTIFICATION_VISIBILITY_CHANGED:
|
||||
_on_visibility_changed(visible)
|
||||
|
||||
func _on_visibility_changed(is_visible):
|
||||
|
||||
print("MapScreen visibility changed to: ", is_visible)
|
||||
if is_visible:
|
||||
update_gold_display()
|
||||
else:
|
||||
print("MapScreen is now invisible")
|
||||
|
||||
emit_signal("map_visibility_changed", is_visible)
|
||||
|
|
@ -1 +0,0 @@
|
|||
uid://ckpv3snpg6g34
|
||||
|
|
@ -1,147 +0,0 @@
|
|||
extends RefCounted
|
||||
class_name NodeLayoutHelper
|
||||
|
||||
# Configuration
|
||||
var map_width: int = 6 # Available horizontal positions (0-5)
|
||||
var level_height: float = 1.0 # Vertical spacing between levels
|
||||
var node_spread: float = 0.7 # How much to spread nodes horizontally (0-1)
|
||||
var path_smoothness: float = 0.3 # How much to align nodes with their connections (0-1)
|
||||
|
||||
# Internal
|
||||
var _rng = RandomNumberGenerator.new()
|
||||
|
||||
func _init(seed_value: int = 0):
|
||||
if seed_value != 0:
|
||||
_rng.seed = seed_value
|
||||
else:
|
||||
_rng.randomize()
|
||||
|
||||
# Organize node positions for better visual layout
|
||||
func organize_layout(nodes: Array, connections: Array) -> Array:
|
||||
# Clone the nodes so we don't modify the original array
|
||||
var updated_nodes = []
|
||||
for node in nodes:
|
||||
updated_nodes.append(node.duplicate())
|
||||
|
||||
# Group nodes by level
|
||||
var nodes_by_level = {}
|
||||
|
||||
for node in updated_nodes:
|
||||
var level = node.level
|
||||
if not nodes_by_level.has(level):
|
||||
nodes_by_level[level] = []
|
||||
nodes_by_level[level].append(node)
|
||||
|
||||
# Process each level
|
||||
for level in nodes_by_level:
|
||||
var level_nodes = nodes_by_level[level]
|
||||
|
||||
# Skip levels with fixed positions
|
||||
if level == 0 or level == nodes_by_level.size() - 1:
|
||||
continue
|
||||
|
||||
# Calculate optimal positions based on connections
|
||||
for node in level_nodes:
|
||||
_adjust_node_position(node, updated_nodes, connections, nodes_by_level)
|
||||
|
||||
# Resolve overlaps after initial placement
|
||||
for level in nodes_by_level:
|
||||
var level_nodes = nodes_by_level[level]
|
||||
if level_nodes.size() > 1:
|
||||
_resolve_horizontal_overlaps(level_nodes)
|
||||
|
||||
return updated_nodes
|
||||
|
||||
# Adjust node position based on its connections
|
||||
func _adjust_node_position(node, all_nodes, connections, nodes_by_level):
|
||||
# Find all connections to/from this node
|
||||
var connected_from = [] # Nodes in the previous level connecting to this node
|
||||
var connected_to = [] # Nodes in the next level this node connects to
|
||||
|
||||
for conn in connections:
|
||||
if conn.to == node.id:
|
||||
var from_node = _find_node_by_id(all_nodes, conn.from)
|
||||
if from_node and from_node.level == node.level - 1:
|
||||
connected_from.append(from_node)
|
||||
|
||||
if conn.from == node.id:
|
||||
var to_node = _find_node_by_id(all_nodes, conn.to)
|
||||
if to_node and to_node.level == node.level + 1:
|
||||
connected_to.append(to_node)
|
||||
|
||||
# Calculate optimal x position based on connected nodes
|
||||
var optimal_x = node.position.x # Start with current position
|
||||
|
||||
if connected_from.size() > 0 or connected_to.size() > 0:
|
||||
var avg_x = 0.0
|
||||
var total_connected = 0
|
||||
|
||||
# Consider positions of connected nodes
|
||||
for from_node in connected_from:
|
||||
avg_x += from_node.position.x
|
||||
total_connected += 1
|
||||
|
||||
for to_node in connected_to:
|
||||
avg_x += to_node.position.x
|
||||
total_connected += 1
|
||||
|
||||
if total_connected > 0:
|
||||
avg_x /= total_connected
|
||||
|
||||
# Blend between current position and optimal position
|
||||
optimal_x = lerp(node.position.x, avg_x, path_smoothness)
|
||||
|
||||
# Add some random variation to prevent everything aligning too perfectly
|
||||
optimal_x += (_rng.randf() - 0.5) * node_spread
|
||||
|
||||
# Keep within bounds
|
||||
optimal_x = clamp(optimal_x, 0, map_width)
|
||||
|
||||
# Update node position
|
||||
node.position.x = optimal_x
|
||||
|
||||
# Y position is determined by level
|
||||
node.position.y = node.level * level_height
|
||||
|
||||
# Resolve horizontal overlaps between nodes on the same level
|
||||
func _resolve_horizontal_overlaps(level_nodes):
|
||||
# Sort nodes by x position
|
||||
level_nodes.sort_custom(func(a, b): return a.position.x < b.position.x)
|
||||
|
||||
# Minimum distance between nodes
|
||||
var min_distance = 1.0
|
||||
|
||||
# Check for overlaps and adjust
|
||||
for i in range(1, level_nodes.size()):
|
||||
var prev_node = level_nodes[i-1]
|
||||
var curr_node = level_nodes[i]
|
||||
|
||||
if curr_node.position.x - prev_node.position.x < min_distance:
|
||||
# If too close, move them apart
|
||||
var midpoint = (prev_node.position.x + curr_node.position.x) / 2
|
||||
var half_distance = min_distance / 2
|
||||
|
||||
# Try to maintain relative positions
|
||||
prev_node.position.x = midpoint - half_distance
|
||||
curr_node.position.x = midpoint + half_distance
|
||||
|
||||
# Ensure we stay within bounds
|
||||
if prev_node.position.x < 0:
|
||||
var shift = -prev_node.position.x
|
||||
prev_node.position.x += shift
|
||||
curr_node.position.x += shift
|
||||
|
||||
if curr_node.position.x > map_width:
|
||||
var shift = curr_node.position.x - map_width
|
||||
prev_node.position.x -= shift
|
||||
curr_node.position.x -= shift
|
||||
|
||||
# Final boundary check for previous node
|
||||
prev_node.position.x = max(0, prev_node.position.x)
|
||||
|
||||
# Find a node by its ID
|
||||
func _find_node_by_id(nodes, id):
|
||||
for node in nodes:
|
||||
if node.id == id:
|
||||
return node
|
||||
return null
|
||||
|
|
@ -1 +0,0 @@
|
|||
uid://bmi14xk8tn7xc
|
||||
|
|
@ -1,132 +0,0 @@
|
|||
# Add or modify this in your CardVisual.gd script
|
||||
|
||||
extends Control
|
||||
class_name NodePopup
|
||||
|
||||
signal pressed(id)
|
||||
# Node references
|
||||
@onready var card_container = $CardContainer
|
||||
@onready var name_label = $CardContainer/VBoxContainer/NameLabel
|
||||
@onready var desc_label = $CardContainer/VBoxContainer/DescriptionLabel
|
||||
@onready var enter_btn = $CardContainer/VBoxContainer/HBoxContainer/EnterButton
|
||||
@onready var close_btn = $CardContainer/VBoxContainer/HBoxContainer/CloseButton
|
||||
var node_id = null;
|
||||
var animation_tween
|
||||
|
||||
|
||||
func _ready():
|
||||
|
||||
mouse_filter = Control.MOUSE_FILTER_STOP
|
||||
gui_input.connect(_on_gui_input)
|
||||
if card_container:
|
||||
card_container.mouse_filter = Control.MOUSE_FILTER_PASS
|
||||
for child in get_children():
|
||||
if child is Control:
|
||||
child.mouse_filter = Control.MOUSE_FILTER_PASS
|
||||
|
||||
if enter_btn:
|
||||
enter_btn.connect("pressed", Callable(self, "_on_enter_button_pressed"))
|
||||
|
||||
if close_btn:
|
||||
close_btn.connect("pressed", Callable(self, "_on_close_button_pressed"))
|
||||
card_container.visible = false
|
||||
# Setup initial scale for animation
|
||||
card_container.scale = Vector2(0.8, 0.8)
|
||||
set_process_input(true)
|
||||
|
||||
|
||||
func set_card(id:int, name: String, desc: String):
|
||||
if id == null:
|
||||
card_container.visible = false
|
||||
return
|
||||
|
||||
node_id = id
|
||||
card_container.visible = true
|
||||
|
||||
name_label.text = name
|
||||
desc_label.text = desc
|
||||
|
||||
# Animate popup appearance
|
||||
if animation_tween:
|
||||
animation_tween.kill()
|
||||
|
||||
animation_tween = create_tween()
|
||||
animation_tween.tween_property(card_container, "scale", Vector2(1.0, 1.0), 0.2).set_ease(Tween.EASE_OUT).set_trans(Tween.TRANS_BACK)
|
||||
|
||||
# Make sure popup is fully visible on screen
|
||||
call_deferred("ensure_on_screen")
|
||||
|
||||
func ensure_on_screen():
|
||||
await get_tree().process_frame
|
||||
# Get viewport size
|
||||
var viewport_size = get_viewport_rect().size
|
||||
# Get popup global position and size
|
||||
var popup_rect = Rect2(global_position, card_container.size)
|
||||
|
||||
# Adjust if off-screen
|
||||
if popup_rect.position.x < 0:
|
||||
global_position.x = 10
|
||||
elif popup_rect.position.x + popup_rect.size.x > viewport_size.x:
|
||||
global_position.x = viewport_size.x - popup_rect.size.x - 10
|
||||
|
||||
if popup_rect.position.y < 0:
|
||||
global_position.y = 10
|
||||
elif popup_rect.position.y + popup_rect.size.y > viewport_size.y:
|
||||
global_position.y = viewport_size.y - popup_rect.size.y - 10
|
||||
|
||||
|
||||
func _on_gui_input(event):
|
||||
if event is InputEventMouseButton:
|
||||
if event.button_index == MOUSE_BUTTON_LEFT and event.pressed:
|
||||
var tween = create_tween()
|
||||
tween.tween_property(card_container, "scale", Vector2(1.05, 1.05), 0.1)
|
||||
tween.tween_property(card_container, "scale", Vector2(1.0, 1.0), 0.1)
|
||||
|
||||
# Handle dragging the popup
|
||||
var dragging = false
|
||||
var drag_start_pos = Vector2()
|
||||
|
||||
func _input(event):
|
||||
if not visible or not card_container.visible:
|
||||
return
|
||||
|
||||
if event is InputEventMouseButton:
|
||||
if event.button_index == MOUSE_BUTTON_LEFT:
|
||||
if event.pressed:
|
||||
# Check if mouse is over the card container
|
||||
var local_mouse_pos = get_local_mouse_position()
|
||||
if card_container.get_rect().has_point(local_mouse_pos):
|
||||
dragging = true
|
||||
drag_start_pos = local_mouse_pos - card_container.position
|
||||
else:
|
||||
dragging = false
|
||||
|
||||
elif event is InputEventMouseMotion and dragging:
|
||||
card_container.position = get_local_mouse_position() - drag_start_pos
|
||||
func _on_enter_button_pressed():
|
||||
# Add a confirmation animation before closing
|
||||
var tween = create_tween()
|
||||
tween.tween_property(card_container, "scale", Vector2(1.1, 1.1), 0.1)
|
||||
tween.tween_property(card_container, "scale", Vector2(0.8, 0.8), 0.2)
|
||||
tween.tween_callback(func(): _confirm_selection())
|
||||
|
||||
func _disable_enter():
|
||||
enter_btn.disabled = true;
|
||||
func _enable_enter():
|
||||
enter_btn.disabled = false;
|
||||
|
||||
func _confirm_selection():
|
||||
emit_signal("pressed", node_id)
|
||||
card_container.visible = false
|
||||
visible = false
|
||||
|
||||
func _on_close_button_pressed():
|
||||
# Add a closing animation
|
||||
var tween = create_tween()
|
||||
tween.tween_property(card_container, "scale", Vector2(0.8, 0.8), 0.2)
|
||||
tween.tween_callback(func(): _cancel_selection())
|
||||
|
||||
func _cancel_selection():
|
||||
emit_signal("pressed", null)
|
||||
card_container.visible = false
|
||||
visible = false
|
||||
|
|
@ -1 +0,0 @@
|
|||
uid://tsgxcwb1u8uc
|
||||
|
|
@ -1,491 +0,0 @@
|
|||
extends Control
|
||||
class_name MenuContainer
|
||||
|
||||
const VERSION_FILE_PATH = "res://Game.json"
|
||||
# Signal to notify parent when a new game is requested
|
||||
# signal new_game_requested
|
||||
signal reward_closed(node)
|
||||
signal new_game_requested(options)
|
||||
signal shop_open_requested(options)
|
||||
signal reward_open_requested(options)
|
||||
signal deckmanager_open_requested(options)
|
||||
signal map_open_requested(options)
|
||||
signal node_completed(options)
|
||||
signal shop_closed
|
||||
signal card_purchased(card, price)
|
||||
signal card_unlocked(card, price)
|
||||
signal lobby_open_requested(options)
|
||||
signal lobby_shop_open_requested(options)
|
||||
signal vanilla_mode_selected
|
||||
signal deeper_mode_selected
|
||||
@onready var newGameButton = $HBoxContainer/VBoxContainer/MenuOptions/NewGameText
|
||||
@onready var continueButton = $HBoxContainer/VBoxContainer/MenuOptions/Continue
|
||||
@onready var optionsButton = $HBoxContainer/VBoxContainer/MenuOptions/Options
|
||||
@onready var versionText = $HBoxContainer/VBoxContainer/VersionText
|
||||
@onready var titleText = $HBoxContainer/VBoxContainer/TitleText
|
||||
@onready var developerText = $HBoxContainer/VBoxContainer/DeveloperText
|
||||
@onready var gameMenuScreen = get_node("/root/Board/GameMenuScreen")
|
||||
@onready var deckManagerScreen = get_node("/root/Board/DeckManagerScreen")
|
||||
@onready var mapScreen = get_node("/root/Board/MapScreen")
|
||||
@onready var stateMachine = get_node("/root/Board/StateMachine")
|
||||
@onready var shopScreen = get_node("/root/Board/ShopScreen")
|
||||
@onready var progressionScreen = get_node("/root/Board/ProgressionScreen")
|
||||
@onready var rewardScreen = get_node("/root/Board/RewardScreen")
|
||||
@onready var game = get_node_or_null("/root/Board") as ChessGame
|
||||
@onready var lobbyScreen = get_node_or_null("/root/Board/LobbyScreen")
|
||||
@onready var lobbyShopScreen = get_node_or_null("/root/Board/LobbyShopScreen")
|
||||
@onready var preloadhandScreen = get_node_or_null("/root/Board/HandPreloadScreen")
|
||||
|
||||
|
||||
|
||||
var player_gold = 10
|
||||
var back_to_map = false
|
||||
var winConditionManager: WinConditionManager
|
||||
var currentWinCondition = Utils.WinConditionType.CAPTURE_UNIT
|
||||
var currentLossCondition = Utils.LossConditionType.UNIT_LOST
|
||||
|
||||
|
||||
func _ready():
|
||||
# Connect menu option signals
|
||||
_connect_button(newGameButton, "_on_new_game_pressed")
|
||||
_connect_button(continueButton, "_on_continue_pressed")
|
||||
_connect_button(optionsButton, "_on_options_pressed")
|
||||
if gameMenuScreen:
|
||||
gameMenuScreen.setup(self)
|
||||
gameMenuScreen.connect("shop_open_requested", Callable(self, "_on_shop_open_requested"))
|
||||
gameMenuScreen.connect("deckmanager_open_requested", Callable(self, "_on_deckmanager_open_requested"))
|
||||
gameMenuScreen.connect("map_open_requested", Callable(self, "_on_map_open_requested"))
|
||||
gameMenuScreen.connect("new_game_requested", Callable(self, "_on_start_game_pressed"))
|
||||
gameMenuScreen.connect("reward_open_requested", Callable(self, "_on_reward_open_requested"))
|
||||
gameMenuScreen.connect("lobby_open_requested", Callable(self, "_on_lobby_open_requested"))
|
||||
gameMenuScreen.visible = false
|
||||
else:
|
||||
connect("map_open_requested", Callable(self, "_on_map_open_requested"))
|
||||
connect("reward_closed", Callable(self, "_on_reward_completed"))
|
||||
|
||||
game.connect("node_completed", Callable(self, "_on_node_completed"))
|
||||
if deckManagerScreen:
|
||||
deckManagerScreen.connect("back_pressed", Callable(self, "_on_deck_manager_back_pressed"))
|
||||
deckManagerScreen.visible = false
|
||||
|
||||
if preloadhandScreen:
|
||||
preloadhandScreen.connect("back_pressed", Callable(self, "_on_preload_back_pressed"))
|
||||
preloadhandScreen.visible = false
|
||||
if progressionScreen:
|
||||
progressionScreen.connect("save_pressed", Callable(self, "_on_progression_save_pressed"))
|
||||
progressionScreen.visible = false
|
||||
if mapScreen:
|
||||
mapScreen.connect("back_pressed", Callable(self, "_on_map_back_pressed"))
|
||||
mapScreen.connect("deckmanager_open_requested", Callable(self, "_on_deckmanager_open_requested_from_map"))
|
||||
mapScreen.connect("node_selected", Callable(self, "_on_map_node_selected"))
|
||||
mapScreen.visible = false
|
||||
|
||||
if shopScreen:
|
||||
shopScreen.connect("back_pressed", Callable(self, "_on_shop_back_pressed"))
|
||||
shopScreen.connect("card_purchased", Callable(self, "_on_shop_card_purchased"))
|
||||
shopScreen.visible = false
|
||||
if rewardScreen:
|
||||
rewardScreen.connect("back_pressed", Callable(self, "_on_reward_back_pressed"))
|
||||
rewardScreen.connect("card_purchased", Callable(self, "_on_reward_card"))
|
||||
rewardScreen.visible = false
|
||||
if lobbyScreen:
|
||||
lobbyScreen.connect("vanilla_selected", Callable(self, "_on_vanilla_selected"))
|
||||
lobbyScreen.connect("preload_selected", Callable(self, "_on_preload_selected"))
|
||||
lobbyScreen.connect("shop_selected", Callable(self, "_on_lobby_shop_open_requested"))
|
||||
lobbyScreen.connect("deeper_selected", Callable(self, "_on_deeper_selected"))
|
||||
lobbyScreen.connect("back_pressed", Callable(self, "_on_lobby_back_pressed"))
|
||||
# lobbyScreen.connect("map_open_requested", Callable(self, "_on_map_open_requested"))
|
||||
lobbyScreen.visible = false
|
||||
|
||||
if lobbyShopScreen:
|
||||
lobbyShopScreen.connect("back_pressed", Callable(self, "_on_lobby_shop_back_pressed"))
|
||||
lobbyShopScreen.connect("card_unlocked", Callable(self, "_on_lobby_shop_card_unlocked"))
|
||||
lobbyShopScreen.connect("hand_size_increased", Callable(self, "_on_hand_size_increased"))
|
||||
lobbyShopScreen.visible = false
|
||||
load_version()
|
||||
|
||||
|
||||
func load_version():
|
||||
|
||||
var version = "v.0.0.0"
|
||||
var title = "ChessBuilder"
|
||||
var developer = ""
|
||||
var file = FileAccess.open(VERSION_FILE_PATH, FileAccess.READ)
|
||||
if file:
|
||||
var json_text = file.get_as_text()
|
||||
file.close()
|
||||
|
||||
# Parse the JSON
|
||||
var json = JSON.new()
|
||||
var error = json.parse(json_text)
|
||||
|
||||
if error == OK:
|
||||
var data = json.get_data()
|
||||
if data and typeof(data) == TYPE_DICTIONARY and data.has("version"):
|
||||
version = "v " + str(data.version)
|
||||
if data and typeof(data) == TYPE_DICTIONARY and data.has("title"):
|
||||
title = str(data.title)
|
||||
if data and typeof(data) == TYPE_DICTIONARY and data.has("developer"):
|
||||
developer = str(data.developer)
|
||||
else:
|
||||
print("JSON Parse Error: ", json.get_error_message())
|
||||
|
||||
if versionText:
|
||||
versionText.text = version
|
||||
if titleText:
|
||||
titleText.text = title
|
||||
if developerText:
|
||||
developerText.text = developer
|
||||
# Connect a button signal to a method
|
||||
func _connect_button(button, method_name):
|
||||
if button and button.has_signal("pressed"):
|
||||
if !button.is_connected("pressed", Callable(self, method_name)):
|
||||
button.connect("pressed", Callable(self, method_name))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
func _on_preload_back_pressed():
|
||||
if preloadhandScreen:
|
||||
preloadhandScreen.visible = false
|
||||
if lobbyScreen:
|
||||
lobbyScreen.visible = true
|
||||
|
||||
|
||||
# Handle New Game button press
|
||||
func _on_new_game_pressed():
|
||||
print("New Game pressed, showing game menu")
|
||||
self.visible = false
|
||||
if gameMenuScreen:
|
||||
gameMenuScreen.visible = true
|
||||
|
||||
# _on_map_open_requested({})
|
||||
# emit_signal("new_game_requested")
|
||||
|
||||
|
||||
|
||||
func _on_lobby_open_requested(options):
|
||||
print("Lobby requested with options:", options)
|
||||
if gameMenuScreen:
|
||||
gameMenuScreen.visible = false
|
||||
if lobbyScreen:
|
||||
lobbyScreen.visible = true
|
||||
lobbyScreen.initialize(options)
|
||||
lobbyScreen.show_screen()
|
||||
print("_on_lobby_open_requested" )
|
||||
|
||||
func _on_lobby_back_pressed():
|
||||
if lobbyScreen:
|
||||
lobbyScreen.visible = false
|
||||
self.visible = true
|
||||
|
||||
func _on_preload_selected():
|
||||
print("Preload mode selected")
|
||||
preloadhandScreen.initialize(null)
|
||||
preloadhandScreen.visible = true
|
||||
|
||||
func _on_vanilla_selected():
|
||||
print("Vanilla mode selected")
|
||||
if gameMenuScreen:
|
||||
gameMenuScreen.visible = true
|
||||
game.mode = "vanilla"
|
||||
_on_map_open_requested({"mode": "vanilla", "hand_size": 2, })
|
||||
# emit_signal("vanilla_mode_selected")
|
||||
|
||||
func _on_deeper_selected():
|
||||
print("Deeper mode selected")
|
||||
if gameMenuScreen:
|
||||
gameMenuScreen.visible = true
|
||||
game.mode = "deeper"
|
||||
_on_map_open_requested({"mode": "deeper", "hand_size": game.player.hand_size,})
|
||||
|
||||
func _on_lobby_shop_open_requested(options):
|
||||
print("Lobby Shop requested with options:", options)
|
||||
if lobbyScreen:
|
||||
lobbyScreen.visible = false
|
||||
if lobbyShopScreen:
|
||||
lobbyShopScreen.visible = true
|
||||
lobbyShopScreen.initialize(options)
|
||||
emit_signal("lobby_shop_open_requested", options)
|
||||
|
||||
func _on_lobby_shop_back_pressed():
|
||||
if lobbyShopScreen:
|
||||
lobbyShopScreen.visible = false
|
||||
if lobbyScreen:
|
||||
lobbyScreen.visible = true
|
||||
|
||||
|
||||
func _on_lobby_shop_card_unlocked(card, token_cost):
|
||||
# Add the card to the player's bank
|
||||
if game and "deckManager" in game:
|
||||
var deck_manager = game.deckManager
|
||||
game.player.unlock_card(card)
|
||||
print("Added unlocked card to whitelist: ", card.cardName)
|
||||
|
||||
func _on_hand_size_increased():
|
||||
print("Hand size increased")
|
||||
|
||||
|
||||
# Handle Continue button press
|
||||
func _on_continue_pressed():
|
||||
print("Continue pressed")
|
||||
# Implement continue game logic here
|
||||
# self.visible = false
|
||||
# You can add logic to load a saved game if needed
|
||||
|
||||
# Handle Options button press
|
||||
func _on_options_pressed():
|
||||
print("Options pressed")
|
||||
# Implement options menu logic here
|
||||
# You could show an options panel or switch to an options menu
|
||||
|
||||
# Game Menu Screen Signals
|
||||
func _on_shop_open_requested(options):
|
||||
print("Shop requested with options:", options)
|
||||
if gameMenuScreen:
|
||||
gameMenuScreen.visible = false
|
||||
|
||||
if options == null:
|
||||
options = {}
|
||||
|
||||
if "gold" in options:
|
||||
options["gold"] = game.player.get_gold()
|
||||
if mapScreen:
|
||||
mapScreen.visible = false
|
||||
if shopScreen:
|
||||
shopScreen.visible = true
|
||||
shopScreen.initialize(options)
|
||||
emit_signal("shop_open_requested", options)
|
||||
|
||||
func _on_shop_back_pressed():
|
||||
if shopScreen:
|
||||
shopScreen.visible = false
|
||||
game.player.set_gold(shopScreen.player_gold)
|
||||
|
||||
# Show game menu again
|
||||
if not back_to_map and gameMenuScreen:
|
||||
gameMenuScreen.visible = true
|
||||
if back_to_map:
|
||||
mapScreen.visible = true
|
||||
# Emit signal that shop was closed
|
||||
emit_signal("shop_closed")
|
||||
|
||||
func _on_shop_card_purchased(card, price):
|
||||
game.player.remove_gold(price)
|
||||
# Forward the signal
|
||||
emit_signal("card_purchased", card, price)
|
||||
|
||||
# Add the card to the player's bank
|
||||
if game and "deckManager" in game:
|
||||
var deck_manager = game.deckManager
|
||||
deck_manager.bank.append(card)
|
||||
print("Added purchased card to bank:", card.cardName)
|
||||
|
||||
|
||||
|
||||
func _on_reward_open_requested(options):
|
||||
print("Reward requested with options:", options)
|
||||
if shopScreen:
|
||||
shopScreen.visible = false
|
||||
|
||||
if options == null:
|
||||
options = {}
|
||||
|
||||
|
||||
if "gold" in options.metadata:
|
||||
options.metadata["gold"] = game.player.get_gold()
|
||||
if rewardScreen:
|
||||
rewardScreen.visible = true
|
||||
rewardScreen.initialize(options)
|
||||
emit_signal("reward_open_requested", options)
|
||||
|
||||
|
||||
func _on_reward_back_pressed(completed_node):
|
||||
if rewardScreen:
|
||||
rewardScreen.visible = false
|
||||
|
||||
if not back_to_map and gameMenuScreen:
|
||||
gameMenuScreen.visible = true
|
||||
if back_to_map:
|
||||
mapScreen.visible = true
|
||||
print("DONE: ", completed_node)
|
||||
var is_escape = completed_node.metadata.is_escape
|
||||
emit_signal("reward_closed", completed_node)
|
||||
|
||||
func _on_reward_card(card, price):
|
||||
# Forward the signal
|
||||
emit_signal("card_purchased", card, price)
|
||||
|
||||
# Add the card to the player's bank
|
||||
if game and "deckManager" in game:
|
||||
var deck_manager = game.deckManager
|
||||
deck_manager.bank.append(card)
|
||||
print("Added reward card to bank:", card.cardName)
|
||||
|
||||
|
||||
func _on_reward_completed(completed_node):
|
||||
var is_escape = completed_node.metadata.is_escape
|
||||
if progressionScreen and is_escape:
|
||||
progressionScreen.initialize({"max_deck_size": 10, "node": completed_node})
|
||||
if completed_node.type == Utils.RoomType.FINAL:
|
||||
game.player.add_tokens(1)
|
||||
progressionScreen.visible = true
|
||||
|
||||
|
||||
|
||||
func _on_progression_save_pressed():
|
||||
print("_on_progression_save_pressed")
|
||||
progressionScreen.visible = false
|
||||
gameMenuScreen.visible = false
|
||||
_on_lobby_open_requested(null)
|
||||
|
||||
|
||||
|
||||
func _on_deckmanager_open_requested_from_map(options):
|
||||
print("Deck Manager requested with options:", options)
|
||||
|
||||
# Hide game menu
|
||||
gameMenuScreen.visible = false
|
||||
|
||||
# Show and initialize deck manager
|
||||
|
||||
if deckManagerScreen:
|
||||
deckManagerScreen.visible = true
|
||||
deckManagerScreen.initialize(options)
|
||||
back_to_map = true
|
||||
# Also emit signal for other systems that might need to know
|
||||
emit_signal("deckmanager_open_requested", options)
|
||||
|
||||
|
||||
|
||||
func _on_deckmanager_open_requested(options):
|
||||
print("Deck Manager requested with options:", options)
|
||||
|
||||
# Hide game menu
|
||||
gameMenuScreen.visible = false
|
||||
|
||||
# Show and initialize deck manager
|
||||
if deckManagerScreen:
|
||||
deckManagerScreen.visible = true
|
||||
deckManagerScreen.initialize(options)
|
||||
|
||||
# Also emit signal for other systems that might need to know
|
||||
emit_signal("deckmanager_open_requested", options)
|
||||
|
||||
func _on_deck_manager_back_pressed():
|
||||
# Return to game menu screen
|
||||
deckManagerScreen.visible = false
|
||||
if not back_to_map:
|
||||
gameMenuScreen.visible = true
|
||||
else:
|
||||
mapScreen.visible = true
|
||||
|
||||
func _on_map_open_requested(options):
|
||||
print("Map requested with options:", options)
|
||||
gameMenuScreen.visible = false
|
||||
if "hand_size" in options:
|
||||
game.player.update_deck_hand_size(options.hand_size)
|
||||
if mapScreen:
|
||||
mapScreen.generate_map({"mode": options.mode})
|
||||
mapScreen.display_map()
|
||||
mapScreen.visible = true
|
||||
emit_signal("map_open_requested", options)
|
||||
|
||||
func _on_start_game_pressed(options):
|
||||
print("Starting game with options:", options)
|
||||
|
||||
emit_signal("new_game_requested", options)
|
||||
|
||||
func _on_map_back_pressed():
|
||||
mapScreen.visible = false
|
||||
gameMenuScreen.visible = true
|
||||
|
||||
# Map node selection
|
||||
func _on_map_node_selected(node_data):
|
||||
print("Selected map node: ", node_data)
|
||||
if node_data.type == Utils.RoomType.SHOP:
|
||||
print("Utils.RoomType.SHOP")
|
||||
back_to_map = true
|
||||
_on_shop_open_requested(node_data.metadata)
|
||||
elif node_data.type == Utils.RoomType.FINAL:
|
||||
print("Utils.RoomType.FINAL")
|
||||
if gameMenuScreen:
|
||||
gameMenuScreen.visible = false
|
||||
if deckManagerScreen:
|
||||
deckManagerScreen.visible = false
|
||||
if mapScreen:
|
||||
mapScreen.visible = false
|
||||
back_to_map = true
|
||||
_on_start_game_pressed(node_data)
|
||||
elif node_data.type == Utils.RoomType.NORMAL:
|
||||
print("Utils.RoomType.NORMAL")
|
||||
if gameMenuScreen:
|
||||
gameMenuScreen.visible = false
|
||||
if deckManagerScreen:
|
||||
deckManagerScreen.visible = false
|
||||
if mapScreen:
|
||||
mapScreen.visible = false
|
||||
back_to_map = true
|
||||
_on_start_game_pressed(node_data)
|
||||
elif node_data.type == Utils.RoomType.BOSS:
|
||||
print("Utils.RoomType.BOSS")
|
||||
if gameMenuScreen:
|
||||
gameMenuScreen.visible = false
|
||||
if deckManagerScreen:
|
||||
deckManagerScreen.visible = false
|
||||
if mapScreen:
|
||||
mapScreen.visible = false
|
||||
back_to_map = true
|
||||
_on_start_game_pressed(node_data)
|
||||
elif node_data.type == Utils.RoomType.EVENT:
|
||||
print("Utils.RoomType.EVENT")
|
||||
if gameMenuScreen:
|
||||
gameMenuScreen.visible = false
|
||||
if deckManagerScreen:
|
||||
deckManagerScreen.visible = false
|
||||
if mapScreen:
|
||||
mapScreen.visible = false
|
||||
back_to_map = true
|
||||
_on_start_game_pressed(node_data)
|
||||
|
||||
func _on_node_completed(options):
|
||||
var node_data = options.node
|
||||
var completed = options.completed
|
||||
var success = options.success
|
||||
if success:
|
||||
_on_reward_open_requested(node_data)
|
||||
print("**********************************")
|
||||
print("**********************************")
|
||||
print("**********************************")
|
||||
print("**********************************")
|
||||
print("**************MATCH***************")
|
||||
print("**************OVER****************")
|
||||
print("**********************************")
|
||||
print("**********************************")
|
||||
print("**********************************")
|
||||
print("**********************************")
|
||||
else:
|
||||
print("**********************************")
|
||||
print("**********************************")
|
||||
print("**********************************")
|
||||
print("**********************************")
|
||||
print("**************GAME****************")
|
||||
print("**************OVER****************")
|
||||
print("*************GITGUD***************")
|
||||
print("**********************************")
|
||||
print("**********************************")
|
||||
print("**********************************")
|
||||
|
||||
|
||||
# Public method to show the menu
|
||||
func show_menu():
|
||||
self.visible = true
|
||||
if gameMenuScreen:
|
||||
gameMenuScreen.visible = false
|
||||
if deckManagerScreen:
|
||||
deckManagerScreen.visible = false
|
||||
if mapScreen:
|
||||
mapScreen.visible = false
|
||||
|
|
@ -1 +0,0 @@
|
|||
uid://bf5ljae05pvla
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
extends TextureRect
|
||||
class_name MenuOption
|
||||
|
||||
signal pressed
|
||||
|
||||
# Original modulate to return to after hover
|
||||
var _original_modulate: Color
|
||||
|
||||
func _ready():
|
||||
# Make this texture rect clickable
|
||||
mouse_filter = Control.MOUSE_FILTER_STOP
|
||||
|
||||
# Save original modulate color
|
||||
_original_modulate = modulate
|
||||
|
||||
# Connect the gui_input signal to our own handler
|
||||
connect("gui_input", Callable(self, "_on_gui_input"))
|
||||
|
||||
# Connect hover signals for better user experience
|
||||
connect("mouse_entered", Callable(self, "_on_mouse_entered"))
|
||||
connect("mouse_exited", Callable(self, "_on_mouse_exited"))
|
||||
|
||||
func _on_gui_input(event):
|
||||
if event is InputEventMouseButton:
|
||||
if event.button_index == MOUSE_BUTTON_LEFT and event.pressed:
|
||||
emit_signal("pressed")
|
||||
get_viewport().set_input_as_handled()
|
||||
|
||||
func _on_mouse_entered():
|
||||
# Change appearance when mouse hovers
|
||||
modulate = Color(1.2, 1.2, 0.8) # Slight yellowish tint and brightening
|
||||
|
||||
# Optional: Add a hover sound effect
|
||||
# if has_node("/root/SoundManager"):
|
||||
# get_node("/root/SoundManager").play_hover_sound()
|
||||
|
||||
func _on_mouse_exited():
|
||||
# Restore original appearance
|
||||
modulate = _original_modulate
|
||||
|
|
@ -1 +0,0 @@
|
|||
uid://dbm5dv81lbdod
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
extends RichTextLabel
|
||||
class_name MenuTextOption
|
||||
|
||||
signal pressed
|
||||
|
||||
# Style properties
|
||||
var normal_color: Color = Color(1, 1, 1, 1) # White
|
||||
var hover_color: Color = Color(1, 1, 0, 1) # Yellow
|
||||
var font_size: int = 24
|
||||
|
||||
func _ready():
|
||||
# Make this label clickable
|
||||
mouse_filter = Control.MOUSE_FILTER_STOP
|
||||
|
||||
# Set up base styling
|
||||
add_theme_font_size_override("normal_font_size", font_size)
|
||||
add_theme_color_override("default_color", normal_color)
|
||||
|
||||
# Make text bold
|
||||
bbcode_enabled = true
|
||||
text = "[b]" + text + "[/b]"
|
||||
|
||||
# Connect the gui_input signal to our own handler
|
||||
connect("gui_input", Callable(self, "_on_gui_input"))
|
||||
|
||||
# Connect hover signals for better user experience
|
||||
connect("mouse_entered", Callable(self, "_on_mouse_entered"))
|
||||
connect("mouse_exited", Callable(self, "_on_mouse_exited"))
|
||||
|
||||
func _on_gui_input(event):
|
||||
if event is InputEventMouseButton:
|
||||
if event.button_index == MOUSE_BUTTON_LEFT and event.pressed:
|
||||
emit_signal("pressed")
|
||||
get_viewport().set_input_as_handled()
|
||||
|
||||
func _on_mouse_entered():
|
||||
# Change appearance when mouse hovers
|
||||
add_theme_color_override("default_color", hover_color)
|
||||
|
||||
# Scale up slightly on hover (optional)
|
||||
var tween = create_tween()
|
||||
tween.tween_property(self, "scale", Vector2(1.05, 1.05), 0.1)
|
||||
|
||||
func _on_mouse_exited():
|
||||
# Restore original appearance
|
||||
add_theme_color_override("default_color", normal_color)
|
||||
|
||||
# Scale back to normal (optional)
|
||||
var tween = create_tween()
|
||||
tween.tween_property(self, "scale", Vector2(1.0, 1.0), 0.1)
|
||||
|
|
@ -1 +0,0 @@
|
|||
uid://c47i2m0ll101x
|
||||
|
|
@ -1,167 +0,0 @@
|
|||
extends Node
|
||||
class_name Player
|
||||
|
||||
var attached_cards: Dictionary = {}
|
||||
var attached_effects: Dictionary = {}
|
||||
var hand_size: int = 2
|
||||
var gold: int = 70
|
||||
var tokens: int = 1
|
||||
var game: ChessGame
|
||||
var run_count: int = 0
|
||||
const MAX_HAND_SIZE = 5
|
||||
var unlocked_cards = []
|
||||
|
||||
var cards_by_rank = {
|
||||
Card.Rank.RANK_0: [], # Most powerful (one-time use)
|
||||
Card.Rank.RANK_1: [], # Once per match
|
||||
Card.Rank.RANK_2: [], # Discard pile cards
|
||||
Card.Rank.RANK_3: [] # Basic cards
|
||||
}
|
||||
|
||||
var cards_by_name = {}
|
||||
func _init(g: int, size: int, tok: int, gm: ChessGame):
|
||||
print("INIT PLAYER CHARACTER")
|
||||
# var classList = ProjectSettings.get_global_class_list();
|
||||
# for classType in classList:
|
||||
# if classType.base == "Card":
|
||||
# print("ALL CARDS ", classType)
|
||||
gold = g
|
||||
tokens = tok
|
||||
game = gm
|
||||
hand_size = size
|
||||
update_deck_hand_size(size)
|
||||
load_all_cards()
|
||||
initialize_deck_from_defaults(unlocked_cards)
|
||||
# Debug output
|
||||
print_card_list()
|
||||
|
||||
|
||||
func initialize_deck_from_defaults(unlocked_cards: Array = []):
|
||||
if "deckManager" in game:
|
||||
game.deckManager.initializeStartingDeck(unlocked_cards)
|
||||
|
||||
|
||||
func update_deck_hand_size(size: int):
|
||||
if "deckManager" in game:
|
||||
game.deckManager.set_hand_size(hand_size)
|
||||
|
||||
func set_hand_size(size: int):
|
||||
hand_size = size
|
||||
|
||||
func get_tokens() -> int:
|
||||
return tokens
|
||||
|
||||
func set_tokens(amount: int) -> void:
|
||||
tokens = amount
|
||||
|
||||
func remove_tokens(amount: int) -> void:
|
||||
tokens = max(0, tokens - amount)
|
||||
|
||||
func add_tokens(t: int) -> void :
|
||||
tokens += t
|
||||
|
||||
func increment_runs() -> void :
|
||||
run_count += 1
|
||||
|
||||
func get_run_count() -> int :
|
||||
return run_count
|
||||
|
||||
func get_gold() -> int :
|
||||
return gold
|
||||
|
||||
func add_gold(g: int) -> void :
|
||||
gold += g
|
||||
|
||||
func remove_gold(g: int) -> void :
|
||||
gold -= g
|
||||
|
||||
func set_gold(g: int) -> void :
|
||||
gold = g
|
||||
|
||||
func unlock_card(card_instance):
|
||||
unlocked_cards.append(card_instance)
|
||||
|
||||
func load_all_cards():
|
||||
var class_list = ProjectSettings.get_global_class_list()
|
||||
|
||||
print("Loading all card classes...")
|
||||
|
||||
for class_info in class_list:
|
||||
if class_info.base == "Card":
|
||||
var cname = class_info["class"]
|
||||
print("Found card class: ", cname)
|
||||
|
||||
var script_path = class_info["path"]
|
||||
var card_script = load(script_path)
|
||||
|
||||
if card_script == null:
|
||||
print("Couldn't load script: ", script_path)
|
||||
continue
|
||||
|
||||
var card_instance = card_script.new()
|
||||
if card_instance.is_default:
|
||||
unlocked_cards.append(card_instance)
|
||||
cards_by_name[card_instance.cardName] = card_instance
|
||||
|
||||
if card_instance.rank in cards_by_rank:
|
||||
cards_by_rank[card_instance.rank].append(card_instance)
|
||||
else:
|
||||
print("Warning: Card has unknown rank: ", card_instance.cardName, " (", card_instance.rank, ")")
|
||||
|
||||
print("Successfully loaded card: ", card_instance.cardName, " (Rank ", card_instance.rank, ")")
|
||||
|
||||
print("Card loading complete. Loaded ", cards_by_name.size(), " cards.")
|
||||
|
||||
|
||||
|
||||
func print_card_list():
|
||||
print("\n--- CARD LIST BY RANK ---")
|
||||
|
||||
for rank in cards_by_rank:
|
||||
var rank_name = ""
|
||||
match rank:
|
||||
Card.Rank.RANK_0: rank_name = "RANK 0 (One-time use)"
|
||||
Card.Rank.RANK_1: rank_name = "RANK 1 (Once per match)"
|
||||
Card.Rank.RANK_2: rank_name = "RANK 2 (Discard pile)"
|
||||
Card.Rank.RANK_3: rank_name = "RANK 3 (Basic)"
|
||||
_: rank_name = "UNKNOWN RANK"
|
||||
|
||||
print("\n" + rank_name + " (" + str(cards_by_rank[rank].size()) + " cards):")
|
||||
|
||||
for card in cards_by_rank[rank]:
|
||||
print(" - " + card.cardName)
|
||||
|
||||
print("\n--- END CARD LIST ---\n")
|
||||
print("\n--- UNLOCKED CARD LIST ---")
|
||||
for card in unlocked_cards:
|
||||
print(" - " + card.cardName)
|
||||
print("\n--- END UNLOCKED CARD LIST ---\n")
|
||||
|
||||
|
||||
|
||||
|
||||
func get_card(card_name: String) -> Card:
|
||||
if card_name in cards_by_name:
|
||||
return cards_by_name[card_name].duplicate()
|
||||
return null
|
||||
|
||||
func get_random_card(rank: int) -> Card:
|
||||
if rank in cards_by_rank and not cards_by_rank[rank].is_empty():
|
||||
var random_index = randi() % cards_by_rank[rank].size()
|
||||
return cards_by_rank[rank][random_index].duplicate()
|
||||
return null
|
||||
|
||||
func get_cards_by_rank(rank: int) -> Array:
|
||||
if rank in cards_by_rank:
|
||||
var result = []
|
||||
for card in cards_by_rank[rank]:
|
||||
result.append(card.duplicate())
|
||||
return result
|
||||
return []
|
||||
|
||||
func get_all_cards() -> Array:
|
||||
var result = []
|
||||
for rank in cards_by_rank:
|
||||
for card in cards_by_rank[rank]:
|
||||
result.append(card.duplicate())
|
||||
return result
|
||||
|
|
@ -1 +0,0 @@
|
|||
uid://bulvkisinp2ur
|
||||
|
|
@ -1,250 +0,0 @@
|
|||
extends Control
|
||||
class_name ProgressionScreen
|
||||
|
||||
signal save_pressed
|
||||
signal progression_screen_visibility_changed(isvisible)
|
||||
|
||||
@onready var deckGrid = $MainContainer/GridScrollContainer/GridContainer
|
||||
@onready var bankContainer = $MainContainer/BankContainer/ScrollContainer/VBoxContainer
|
||||
@onready var saveButton = $SaveButton
|
||||
@onready var card_preview = $CardPreviewPanel
|
||||
|
||||
var maxDeckSize = 10
|
||||
var deckManager = null
|
||||
var game = null
|
||||
var bankCards = [] # All Users card
|
||||
|
||||
# The current deck
|
||||
var currentDeck = []
|
||||
var current_preview_card = null
|
||||
|
||||
func _ready():
|
||||
# Connect back button
|
||||
if saveButton:
|
||||
saveButton.connect("pressed", Callable(self, "_on_save_button_pressed"))
|
||||
|
||||
if card_preview:
|
||||
card_preview.visible = false
|
||||
|
||||
|
||||
func initialize(options = null):
|
||||
if options:
|
||||
if options.has("max_deck_size") and options.max_deck_size is int:
|
||||
maxDeckSize = options.max_deck_size
|
||||
|
||||
|
||||
# Find the DeckManager instance
|
||||
var board = get_node_or_null("/root/Board") as ChessGame
|
||||
game = board
|
||||
if board and "deckManager" in board:
|
||||
deckManager = board.deckManager
|
||||
print("Found deck manager:", deckManager)
|
||||
# if(!deckManager.deck):
|
||||
# deckManager.initializeStartingDeck()
|
||||
else:
|
||||
print("DeckManager not found on Board node")
|
||||
print("DECK MANAGER")
|
||||
|
||||
# Load cards from deck and bank
|
||||
loadCards()
|
||||
|
||||
# Set up the grid with empty card containers
|
||||
setupDeckGrid()
|
||||
|
||||
# Populate the bank with available cards
|
||||
populateBank()
|
||||
|
||||
func loadCards():
|
||||
var board = get_node_or_null("/root/Board") as ChessGame
|
||||
if board and "deckManager" in board:
|
||||
deckManager = board.deckManager
|
||||
print("Found deck manager:", deckManager)
|
||||
if deckManager:
|
||||
# Clone the deck to work with
|
||||
currentDeck = []
|
||||
bankCards = deckManager.bank.duplicate()
|
||||
bankCards.append_array(deckManager.deck.duplicate())
|
||||
else:
|
||||
# Fallback with empty collections if deck manager not found
|
||||
currentDeck = []
|
||||
bankCards = []
|
||||
print("Warning: DeckManager not found")
|
||||
|
||||
|
||||
func setupDeckGrid():
|
||||
# Clear existing children
|
||||
for child in deckGrid.get_children():
|
||||
child.queue_free()
|
||||
|
||||
# Calculate grid dimensions
|
||||
var cols = 4 #check screen to deteremine
|
||||
var rows = maxDeckSize / cols
|
||||
deckGrid.columns = cols
|
||||
|
||||
# Create card slots
|
||||
for i in range(maxDeckSize):
|
||||
var card_slot = preload("res://card_slot.tscn").instantiate()
|
||||
deckGrid.add_child(card_slot)
|
||||
|
||||
# Connect signals
|
||||
card_slot.connect("card_selected", Callable(self, "_on_deck_card_selected"))
|
||||
_connect_hover_signals(card_slot)
|
||||
|
||||
# Set card if available
|
||||
if i < currentDeck.size():
|
||||
card_slot.set_card(currentDeck[i])
|
||||
else:
|
||||
card_slot.clear()
|
||||
|
||||
func _connect_hover_signals(node):
|
||||
# Add hover signals for preview functionality
|
||||
if node.is_class("Control"):
|
||||
node.mouse_entered.connect(Callable(self, "_on_card_mouse_entered").bind(node))
|
||||
# node.mouse_exited.connect(Callable(self, "_on_card_mouse_exited").bind(node))
|
||||
|
||||
|
||||
func populateBank():
|
||||
# Clear existing children
|
||||
for child in bankContainer.get_children():
|
||||
child.queue_free()
|
||||
|
||||
# Add each bank card to the list
|
||||
for card in bankCards:
|
||||
var card_item = preload("res://card_bank_item.tscn").instantiate()
|
||||
bankContainer.add_child(card_item)
|
||||
card_item.set_card(card)
|
||||
card_item.connect("card_selected", Callable(self, "_on_bank_card_selected"))
|
||||
_connect_hover_signals(card_item)
|
||||
|
||||
func _on_deck_card_selected(card_slot, card):
|
||||
if card:
|
||||
# Remove card from deck
|
||||
var index = currentDeck.find(card)
|
||||
if index >= 0:
|
||||
currentDeck.remove_at(index)
|
||||
|
||||
# Add to bank
|
||||
bankCards.append(card)
|
||||
|
||||
# Update UI
|
||||
card_slot.clear()
|
||||
populateBank()
|
||||
if current_preview_card == card:
|
||||
hide_card_preview()
|
||||
|
||||
func _on_bank_card_selected(card_item, card):
|
||||
print("_on_bank_card_selected")
|
||||
# Find first empty slot in deck
|
||||
var empty_slot_index = -1
|
||||
for i in range(deckGrid.get_child_count()):
|
||||
var slot = deckGrid.get_child(i)
|
||||
if !slot.has_card():
|
||||
empty_slot_index = i
|
||||
break
|
||||
|
||||
if empty_slot_index >= 0 and currentDeck.size() < maxDeckSize:
|
||||
# Remove from bank
|
||||
var bank_index = bankCards.find(card)
|
||||
if bank_index >= 0:
|
||||
bankCards.remove_at(bank_index)
|
||||
|
||||
# Add to deck
|
||||
currentDeck.append(card)
|
||||
|
||||
# Update UI
|
||||
deckGrid.get_child(empty_slot_index).set_card(card)
|
||||
populateBank()
|
||||
|
||||
func _on_card_mouse_entered(node):
|
||||
var card = null
|
||||
|
||||
# Get the card from the node
|
||||
if node.has_method("has_card") and node.has_card():
|
||||
# This is a CardSlot
|
||||
card = node.current_card
|
||||
elif node.has_method("set_card") and node.current_card:
|
||||
# This is a CardBankItem
|
||||
card = node.current_card
|
||||
|
||||
if card:
|
||||
show_card_preview(card)
|
||||
|
||||
# func _on_card_mouse_exited(node):
|
||||
# # Add a short delay before hiding the preview
|
||||
# # This prevents flickering when moving between cards
|
||||
# await get_tree().create_timer(0.1).timeout
|
||||
|
||||
# # Only hide if we're not hovering over another card that shows the same preview
|
||||
# if current_preview_card:
|
||||
# hide_card_preview()
|
||||
|
||||
func show_card_preview(card):
|
||||
if card_preview and card:
|
||||
current_preview_card = card
|
||||
card_preview.preview_card(card)
|
||||
|
||||
func hide_card_preview():
|
||||
if card_preview:
|
||||
current_preview_card = null
|
||||
card_preview.hide_preview()
|
||||
|
||||
func save_deck():
|
||||
sell_remaining_cards()
|
||||
if deckManager:
|
||||
# Save the current deck to the deck manager
|
||||
deckManager.deck = currentDeck.duplicate()
|
||||
deckManager.bank = []
|
||||
|
||||
print("Deck saved with ", currentDeck.size(), " cards")
|
||||
if game.player:
|
||||
game.player.increment_runs()
|
||||
|
||||
func sell_remaining_cards():
|
||||
for card in bankCards:
|
||||
sell_selected_card(card)
|
||||
bankCards.clear()
|
||||
|
||||
func get_card_price(card):
|
||||
if not card:
|
||||
return 0
|
||||
if card.rank in Utils.CardPrices:
|
||||
return Utils.CardPrices[card.rank]
|
||||
return 50
|
||||
|
||||
|
||||
func sell_selected_card(selected_card):
|
||||
|
||||
var price = get_card_price(selected_card)
|
||||
|
||||
game.player.add_gold(price)
|
||||
|
||||
return true
|
||||
|
||||
func _on_save_button_pressed():
|
||||
# Save changes before returning
|
||||
save_deck()
|
||||
|
||||
# Emit signal to go back
|
||||
emit_signal("save_pressed")
|
||||
|
||||
# Hide this screen
|
||||
visible = false
|
||||
|
||||
|
||||
|
||||
|
||||
# Notification
|
||||
func _notification(what):
|
||||
if what == NOTIFICATION_VISIBILITY_CHANGED:
|
||||
_on_visibility_changed(visible)
|
||||
|
||||
func _on_visibility_changed(is_visible):
|
||||
|
||||
print("Progression Screen visibility changed to: ", is_visible)
|
||||
if is_visible:
|
||||
loadCards()
|
||||
setupDeckGrid()
|
||||
else:
|
||||
print("Progression Screen is now invisible")
|
||||
|
||||
emit_signal("progression_screen_visibility_changed", is_visible)
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue