Choose color against cpu

This commit is contained in:
Josh-Heaps
2026-06-12 10:51:26 -06:00
parent 6402ef268c
commit 743c395e11
8 changed files with 92 additions and 19 deletions
+7 -2
View File
@@ -35,7 +35,7 @@ public class ChessController(
/// </summary>
[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);
+9
View File
@@ -89,6 +89,15 @@
</div>
</div>
<div id="colorModal" style="display: none;">
<p>Play as:</p>
<div id="colorButtonContainer">
<button onclick="selectColor('white')">White</button>
<button onclick="selectColor('black')">Black</button>
<button onclick="selectColor('random')">Random</button>
</div>
</div>
@section Scripts {
<script src="~/js/signalr/signalr.min.js"></script>
<script src="~/js/ChessScripts/GameState.js?v=@ViewData["cssVersion"]"></script>
Binary file not shown.
+41
View File
@@ -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;
@@ -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();
},
@@ -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);
};
});
}
};
@@ -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();
+21 -14
View File
@@ -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