Choose color against cpu
This commit is contained in:
@@ -35,7 +35,7 @@ public class ChessController(
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[HttpGet("new")]
|
[HttpGet("new")]
|
||||||
[HttpGet("new/{difficulty}")]
|
[HttpGet("new/{difficulty}")]
|
||||||
public ActionResult CreateGame(int difficulty = 20)
|
public ActionResult CreateGame(int difficulty = 20, string color = "random")
|
||||||
{
|
{
|
||||||
var gameState = chessService.CreateNewGame();
|
var gameState = chessService.CreateNewGame();
|
||||||
_games[gameState.GameId] = gameState;
|
_games[gameState.GameId] = gameState;
|
||||||
@@ -45,7 +45,12 @@ public class ChessController(
|
|||||||
gameState.BlackJoined = true;
|
gameState.BlackJoined = true;
|
||||||
Guid playerId = Guid.NewGuid();
|
Guid playerId = Guid.NewGuid();
|
||||||
Guid computerId = 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);
|
gameState.Computer = engineFactory.Create(difficulty);
|
||||||
|
|
||||||
|
|||||||
@@ -89,6 +89,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</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 {
|
@section Scripts {
|
||||||
<script src="~/js/signalr/signalr.min.js"></script>
|
<script src="~/js/signalr/signalr.min.js"></script>
|
||||||
<script src="~/js/ChessScripts/GameState.js?v=@ViewData["cssVersion"]"></script>
|
<script src="~/js/ChessScripts/GameState.js?v=@ViewData["cssVersion"]"></script>
|
||||||
|
|||||||
Binary file not shown.
@@ -157,6 +157,47 @@
|
|||||||
pointer-events: none; /* ensures img doesn't steal the click */
|
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 {
|
.chessSquare .coordinate-label {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
font-size: 1.2vw;
|
font-size: 1.2vw;
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ const ChessAPI = {
|
|||||||
return await response.json();
|
return await response.json();
|
||||||
},
|
},
|
||||||
|
|
||||||
async createCPUGame(difficulty) {
|
async createCPUGame(difficulty, color = "random") {
|
||||||
const response = await fetch(`/api/chess/new/${difficulty}`);
|
const response = await fetch(`/api/chess/new/${difficulty}?color=${color}`);
|
||||||
return await response.json();
|
return await response.json();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,16 @@ const ChessModals = {
|
|||||||
resolve(difficulty);
|
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 {
|
try {
|
||||||
const difficulty = await ChessModals.promptDifficulty();
|
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.setGameInfo(gameData.gameId, gameData.id, gameData.isWhite);
|
||||||
GameState.clearPreviousMove();
|
GameState.clearPreviousMove();
|
||||||
|
|||||||
@@ -208,14 +208,32 @@ static int evaluatePawn(const chess::Position& pos, const chess::Color c, const
|
|||||||
return score;
|
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) {
|
static int castleIncentive(const chess::Position& pos, chess::Color c) {
|
||||||
if (!pos.pieces(chess::QUEEN))
|
chess::Bitboard pcs = pos.pieces();
|
||||||
return 0;
|
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);
|
chess::Square k = pos.king_square(c);
|
||||||
bool castled = (c == chess::WHITE) ? (k == chess::G1 || k == chess::C1)
|
bool castled = (c == chess::WHITE) ? (k == chess::G1 || k == chess::C1)
|
||||||
: (k == chess::G8 || k == chess::C8);
|
: (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) {
|
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_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 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
|
/* 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
|
* 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
|
* victim, least valuable attacker), then the two killer moves for this ply (quiet moves that
|
||||||
|
|||||||
Reference in New Issue
Block a user