diff --git a/JoshHeaps.Net/Controllers/ChessController.cs b/JoshHeaps.Net/Controllers/ChessController.cs index df9190f..46e38e9 100644 --- a/JoshHeaps.Net/Controllers/ChessController.cs +++ b/JoshHeaps.Net/Controllers/ChessController.cs @@ -35,7 +35,7 @@ public class ChessController( /// [HttpGet("new")] [HttpGet("new/{difficulty}")] - public ActionResult CreateGame(int difficulty = 20) + public ActionResult CreateGame(int difficulty = 20, string color = "random") { var gameState = chessService.CreateNewGame(); _games[gameState.GameId] = gameState; @@ -45,7 +45,12 @@ public class ChessController( gameState.BlackJoined = true; Guid playerId = Guid.NewGuid(); Guid computerId = Guid.NewGuid(); - var isWhite = Random.Shared.Next(2) == 0; + var isWhite = color.ToLowerInvariant() switch + { + "white" => true, + "black" => false, + _ => Random.Shared.Next(2) == 0, + }; gameState.Computer = engineFactory.Create(difficulty); diff --git a/JoshHeaps.Net/Pages/Chess.cshtml b/JoshHeaps.Net/Pages/Chess.cshtml index d87778c..817ae1a 100644 --- a/JoshHeaps.Net/Pages/Chess.cshtml +++ b/JoshHeaps.Net/Pages/Chess.cshtml @@ -89,6 +89,15 @@ + + @section Scripts { diff --git a/JoshHeaps.Net/Resources/chess_engine.dll b/JoshHeaps.Net/Resources/chess_engine.dll index f7f7bfd..190af57 100644 Binary files a/JoshHeaps.Net/Resources/chess_engine.dll and b/JoshHeaps.Net/Resources/chess_engine.dll differ diff --git a/JoshHeaps.Net/wwwroot/css/chess/game.css b/JoshHeaps.Net/wwwroot/css/chess/game.css index 88640c4..23b61b8 100644 --- a/JoshHeaps.Net/wwwroot/css/chess/game.css +++ b/JoshHeaps.Net/wwwroot/css/chess/game.css @@ -157,6 +157,47 @@ pointer-events: none; /* ensures img doesn't steal the click */ } +#colorModal { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background-color: #1e1e1e; + padding: 20px; + border-radius: 10px; + box-shadow: 0 0 20px rgba(0,0,0,0.6); + color: white; + z-index: 1000; + text-align: center; +} + +#colorModal p { + margin-bottom: 15px; + font-size: 18px; + font-weight: bold; +} + +#colorButtonContainer { + display: flex; + gap: 10px; + justify-content: center; +} + +#colorButtonContainer button { + background-color: #2c2c2c; + border: none; + padding: 10px 18px; + border-radius: 8px; + cursor: pointer; + transition: transform 0.2s ease; + color: white; +} + +#colorButtonContainer button:hover { + transform: scale(1.1); + background-color: #3a3a3a; +} + .chessSquare .coordinate-label { position: absolute; font-size: 1.2vw; diff --git a/JoshHeaps.Net/wwwroot/js/ChessScripts/ChessAPI.js b/JoshHeaps.Net/wwwroot/js/ChessScripts/ChessAPI.js index 19f872d..06e0d7a 100644 --- a/JoshHeaps.Net/wwwroot/js/ChessScripts/ChessAPI.js +++ b/JoshHeaps.Net/wwwroot/js/ChessScripts/ChessAPI.js @@ -4,8 +4,8 @@ const ChessAPI = { return await response.json(); }, - async createCPUGame(difficulty) { - const response = await fetch(`/api/chess/new/${difficulty}`); + async createCPUGame(difficulty, color = "random") { + const response = await fetch(`/api/chess/new/${difficulty}?color=${color}`); return await response.json(); }, diff --git a/JoshHeaps.Net/wwwroot/js/ChessScripts/ChessModals.js b/JoshHeaps.Net/wwwroot/js/ChessScripts/ChessModals.js index b8a78f2..935f1ac 100644 --- a/JoshHeaps.Net/wwwroot/js/ChessScripts/ChessModals.js +++ b/JoshHeaps.Net/wwwroot/js/ChessScripts/ChessModals.js @@ -23,6 +23,16 @@ const ChessModals = { resolve(difficulty); }; }); + }, + + promptColor() { + return new Promise(resolve => { + document.getElementById("colorModal").style.display = "block"; + window.selectColor = (color) => { + document.getElementById("colorModal").style.display = "none"; + resolve(color); + }; + }); } }; diff --git a/JoshHeaps.Net/wwwroot/js/ChessScripts/chessMain.js b/JoshHeaps.Net/wwwroot/js/ChessScripts/chessMain.js index 826816f..0f48029 100644 --- a/JoshHeaps.Net/wwwroot/js/ChessScripts/chessMain.js +++ b/JoshHeaps.Net/wwwroot/js/ChessScripts/chessMain.js @@ -91,7 +91,8 @@ async function startCPUGame() { try { const difficulty = await ChessModals.promptDifficulty(); - const gameData = await ChessAPI.createCPUGame(difficulty); + const color = await ChessModals.promptColor(); + const gameData = await ChessAPI.createCPUGame(difficulty, color); GameState.setGameInfo(gameData.gameId, gameData.id, gameData.isWhite); GameState.clearPreviousMove(); diff --git a/native/chess_engine/src/chess_engine.cpp b/native/chess_engine/src/chess_engine.cpp index 740531e..a674594 100644 --- a/native/chess_engine/src/chess_engine.cpp +++ b/native/chess_engine/src/chess_engine.cpp @@ -208,14 +208,32 @@ static int evaluatePawn(const chess::Position& pos, const chess::Color c, const return score; } +static int piece_value(chess::PieceType pt) { + switch (pt) { + case chess::PAWN: return 100; + case chess::KNIGHT: return 320; + case chess::BISHOP: return 330; + case chess::ROOK: return 500; + case chess::QUEEN: return 900; + default: return 0; + } +} + static int castleIncentive(const chess::Position& pos, chess::Color c) { - if (!pos.pieces(chess::QUEEN)) - return 0; + chess::Bitboard pcs = pos.pieces(); + int total = 0; + while (pcs) { + chess::Square s = chess::pop_lsb(pcs); + chess::Piece pc = pos.piece_on(s); + chess::Color c = chess::color_of(pc); + total += piece_value(chess::type_of(pc)); + } + chess::Square k = pos.king_square(c); bool castled = (c == chess::WHITE) ? (k == chess::G1 || k == chess::C1) : (k == chess::G8 || k == chess::C8); - return castled ? 600 : 0; + return castled ? (total / 10) : 0; } static int evaluatePiece(const chess::Position& pos, const chess::Square& s, const chess::Piece& pc, const chess::Color& c) { @@ -274,17 +292,6 @@ static int evaluate_stm(const chess::Position& pos, bool whiteToMove) { static int score_to_tt(int s, int ply) { return s >= MATE_BOUND ? s + ply : s <= -MATE_BOUND ? s - ply : s; } static int score_from_tt(int s, int ply) { return s >= MATE_BOUND ? s - ply : s <= -MATE_BOUND ? s + ply : s; } -static int piece_value(chess::PieceType pt) { - switch (pt) { - case chess::PAWN: return 100; - case chess::KNIGHT: return 320; - case chess::BISHOP: return 330; - case chess::ROOK: return 500; - case chess::QUEEN: return 900; - default: return 0; - } -} - /* Heuristic for searching the most promising moves first, which makes alpha-beta prune far * more. Bands, highest first: the TT best move, then captures by MVV-LVA (most valuable * victim, least valuable attacker), then the two killer moves for this ply (quiet moves that