Chess
Chess
nvgt"
#include "menu.nvgt"
#include "chess_check.nvgt"
#include "chess_ai.nvgt"
// --- ADDED: A dedicated sound function for the chess game ---
void playChessSound(const string &in filename) {
pack_file@ pkToUse = @gChessSoundPack;
if (@pkToUse is null && @gMainMenuPack !is null && gMainMenuPack.active) {
@pkToUse = @gMainMenuPack;
}
if (@pkToUse is null || filename == "" || @gChessSoundHandle is null) return;
if (gChessSoundHandle.playing) { gChessSoundHandle.stop(); }
if (gChessSoundHandle.load(filename, pkToUse)) {
gChessSoundHandle.play();
}
}
if (!filename.ends_with(".json")) {
filename += ".json";
}
// Board state
json_array@ board_json = json_array();
for (int y = 0; y < BOARD_SIZE; y++) {
json_array@ row = json_array();
for (int x = 0; x < BOARD_SIZE; x++) {
row.add(board[y][x]);
}
board_json.add(row);
}
root["board"] = board_json;
// Game state
json_object@ state = json_object();
state["player_is_white"] = player_is_white;
state["vs_computer"] = vs_computer;
state["is_human_white"] = is_human_white;
state["fifty_move_counter"] = fifty_move_counter;
state["en_passant_x"] = en_passant_x;
state["en_passant_y"] = en_passant_y;
state["ai_search_depth"] = AI_SEARCH_DEPTH; // --- NEW: Save AI difficulty
root["state"] = state;
// Castling Rights
json_object@ rights = json_object();
rights["white_king_moved"] = white_king_moved;
rights["black_king_moved"] = black_king_moved;
rights["white_rook_a_moved"] = white_rook_a_moved;
rights["white_rook_h_moved"] = white_rook_h_moved;
rights["black_rook_a_moved"] = black_rook_a_moved;
rights["black_rook_h_moved"] = black_rook_h_moved;
root["rights"] = rights;
// Write to file
string json_string = root.stringify(2);
if (file_put_contents(filename, json_string)) {
speak("Game saved successfully.", true);
} else {
speak("Error: Could not save game to file.", true);
}
}
try {
var@ data = parse_json(json_string);
json_object@ root = cast<json_object@>(data);
if (@root is null) throw("Invalid save file format.");
} catch {
speak("Error parsing save file: " + get_exception_info(), true);
return false;
}
return true;
}
bool is_valid_move(int from_x, int from_y, int to_x, int to_y, PieceColor
moving_color_override = NONE) {
if (from_x < 0 || from_y < 0) return false;
if (from_x == to_x && from_y == to_y) return false;
string piece = board[from_y][from_x];
string target = board[to_y][to_x];
if (target == "K" || target == "k") return false;
if (piece == ".") return false;
PieceColor color = get_piece_color(piece);
PieceColor target_color = get_piece_color(target);
PieceColor allowed = moving_color_override != NONE ? moving_color_override :
(player_is_white ? WHITE : BLACK);
if (color != allowed) return false;
if (color == target_color && target_color != NONE) return false;
int dx = to_x - from_x;
int dy = to_y - from_y;
if (piece == "P" || piece == "p") {
int dir = (piece == "P") ? -1 : 1;
int start_row = (piece == "P") ? 6 : 1;
if (dx == 0 && target == ".") {
if (dy == dir) return true;
if (from_y == start_row && dy == 2 * dir && board[from_y + dir][from_x]
== "." && board[to_y][to_x] == ".") return true;
}
if ((dx == 1 || dx == -1) && dy == dir) {
if (target != "." && color != target_color) return true;
if (to_x == en_passant_x && to_y == en_passant_y) return true;
}
return false;
}
if ((piece == "K" && !white_king_moved) || (piece == "k" && !black_king_moved))
{
if (abs(dx) == 2 && dy == 0) {
if (is_king_in_check(player_is_white)) return false; // Can't castle
out of check
if (dx == 2) { // Kingside
if (piece == "K" && !white_rook_h_moved && board[from_y][from_x+1]
== "." && board[from_y][from_x+2] == "."){
// Check if squares king moves over are attacked
board[from_y][from_x+1] = "K"; board[from_y][from_x] = ".";
if(is_king_in_check(true)) { board[from_y][from_x+1] = ".";
board[from_y][from_x] = "K"; return false; }
board[from_y][from_x+2] = "K"; board[from_y][from_x+1] = ".";
if(is_king_in_check(true)) { board[from_y][from_x+2] = ".";
board[from_y][from_x] = "K"; return false; }
board[from_y][from_x] = "K"; board[from_y][from_x+2] = ".";
return true;
}
if (piece == "k" && !black_rook_h_moved && board[from_y][from_x+1]
== "." && board[from_y][from_x+2] == "."){
board[from_y][from_x+1] = "k"; board[from_y][from_x] = ".";
if(is_king_in_check(false)) { board[from_y][from_x+1] = ".";
board[from_y][from_x] = "k"; return false; }
board[from_y][from_x+2] = "k"; board[from_y][from_x+1] = ".";
if(is_king_in_check(false)) { board[from_y][from_x+2] = ".";
board[from_y][from_x] = "k"; return false; }
board[from_y][from_x] = "k"; board[from_y][from_x+2] = ".";
return true;
}
}
if (dx == -2) { // Queenside
if (piece == "K" && !white_rook_a_moved && board[from_y][from_x-1]
== "." && board[from_y][from_x-2] == "." && board[from_y][from_x-3] == "."){
board[from_y][from_x-1] = "K"; board[from_y][from_x] = ".";
if(is_king_in_check(true)) { board[from_y][from_x-1] = ".";
board[from_y][from_x] = "K"; return false; }
board[from_y][from_x-2] = "K"; board[from_y][from_x-1] = ".";
if(is_king_in_check(true)) { board[from_y][from_x-2] = ".";
board[from_y][from_x] = "K"; return false; }
board[from_y][from_x] = "K"; board[from_y][from_x-2] = ".";
return true;
}
if (piece == "k" && !black_rook_a_moved && board[from_y][from_x-1]
== "." && board[from_y][from_x-2] == "." && board[from_y][from_x-3] == "."){
board[from_y][from_x-1] = "k"; board[from_y][from_x] = ".";
if(is_king_in_check(false)) { board[from_y][from_x-1] = ".";
board[from_y][from_x] = "k"; return false; }
board[from_y][from_x-2] = "k"; board[from_y][from_x-1] = ".";
if(is_king_in_check(false)) { board[from_y][from_x-2] = ".";
board[from_y][from_x] = "k"; return false; }
board[from_y][from_x] = "k"; board[from_y][from_x-2] = ".";
return true;
}
}
}
}
if (piece == "R" || piece == "r") {
if (dx != 0 && dy != 0) return false;
int step_x = dx == 0 ? 0 : (dx > 0 ? 1 : -1);
int step_y = dy == 0 ? 0 : (dy > 0 ? 1 : -1);
int x = from_x + step_x, y = from_y + step_y;
while (x != to_x || y != to_y) {
if (board[y][x] != ".") return false;
x += step_x; y += step_y;
}
return true;
}
if (piece == "N" || piece == "n") {
if ((abs(dx) == 2 && abs(dy) == 1) || (abs(dx) == 1 && abs(dy) == 2))
return true;
return false;
}
if (piece == "B" || piece == "b") {
if (abs(dx) != abs(dy)) return false;
int step_x = dx > 0 ? 1 : -1;
int step_y = dy > 0 ? 1 : -1;
int x = from_x + step_x, y = from_y + step_y;
while (x != to_x && y != to_y) {
if (board[y][x] != ".") return false;
x += step_x; y += step_y;
}
return true;
}
if (piece == "Q" || piece == "q") {
if (dx == 0 || dy == 0) {
int step_x = dx == 0 ? 0 : (dx > 0 ? 1 : -1);
int step_y = dy == 0 ? 0 : (dy > 0 ? 1 : -1);
int x = from_x + step_x, y = from_y + step_y;
while (x != to_x || y != to_y) {
if (board[y][x] != ".") return false;
x += step_x; y += step_y;
}
return true;
} else if (abs(dx) == abs(dy)) {
int step_x = dx > 0 ? 1 : -1;
int step_y = dy > 0 ? 1 : -1;
int x = from_x + step_x, y = from_y + step_y;
while (x != to_x && y != to_y) {
if (board[y][x] != ".") return false;
x += step_x; y += step_y;
}
return true;
}
return false;
}
if (piece == "K" || piece == "k") {
if (abs(dx) <= 1 && abs(dy) <= 1) return true;
return false;
}
return false;
}
string get_board_state_string() {
string state = "";
for (int y = 0; y < BOARD_SIZE; y++) {
for (int x = 0; x < BOARD_SIZE; x++) {
state += board[y][x];
}
}
state += (player_is_white ? "w" : "b");
string castle_rights = "";
if (!white_king_moved && !white_rook_h_moved) castle_rights += "K";
if (!white_king_moved && !white_rook_a_moved) castle_rights += "Q";
if (!black_king_moved && !black_rook_h_moved) castle_rights += "k";
if (!black_king_moved && !black_rook_a_moved) castle_rights += "q";
state += (castle_rights.empty() ? "-" : castle_rights);
if (en_passant_x != -1) {
state += string("abcdefgh")[en_passant_x];
state += (8 - en_passant_y);
} else {
state += "-";
}
return state;
}
bool check_for_insufficient_material() {
int white_knights = 0, white_bishops = 0, white_others = 0;
int black_knights = 0, black_bishops = 0, black_others = 0;
array<int> white_bishop_squares, black_bishop_squares;
return false;
}
if (is_pawn_move || is_capture) {
fifty_move_counter = 0;
if(@position_history !is null) position_history.delete_all();
} else {
fifty_move_counter++;
}
if (is_capture) {
playChessSound("capture.wav");
} else {
playChessSound("drop_piece.wav");
}
if (is_en_passant_move) {
// Handle the special case for en passant, where the captured piece is not
on the target square.
string captured_piece_name = "pawn";
string captured_color_name = (moving_color == WHITE) ? "Black " : "White ";
speak(moving_color_name + get_piece_name(moving_piece) + " captures " +
captured_color_name + captured_piece_name + " en passant at " + to_pos + ".",
false);
} else if (is_capture) {
// Handle a standard capture.
PieceColor captured_color = get_piece_color(captured_piece);
string captured_color_name = (captured_color == WHITE) ? "White " :
(captured_color == BLACK) ? "Black " : "";
speak(moving_color_name + get_piece_name(moving_piece) + " captures " +
captured_color_name + get_piece_name(captured_piece) + " at " + to_pos + ".",
false);
} else {
// Handle a simple move with no capture.
speak("Moved " + moving_color_name + get_piece_name(moving_piece) + " from
" + from_pos + " to " + to_pos + ".", false);
}
board[to_y][to_x] = board[from_y][from_x];
board[from_y][from_x] = ".";
if (is_en_passant_move) {
playChessSound("en_passant.wav");
int cap_y = (moving_piece == "P") ? to_y + 1 : to_y - 1;
board[cap_y][to_x] = ".";
}
player_is_white = !player_is_white;
if (!game_over) {
string current_state = get_board_state_string();
int count = 0;
if(position_history.get(current_state, count)) {
count++;
position_history.set(current_state, count);
} else {
count = 1;
position_history.set(current_state, 1);
}
if (count >= 3) {
game_over = true;
end_reason = "Draw by threefold repetition.";
playChessSound("drawn.wav");
}
}
if (game_over) {
playChessSound("game_over.wav");
speak(end_reason, true);
return;
}
if (is_king_in_check(player_is_white)) {
playChessSound("check.wav");
speak((player_is_white ? "White" : "Black") + " is in check.", false);
}
chess_state = SELECT;
selected_x = -1;
selected_y = -1;
// --- MODIFIED: Update cursor position based on the next player's perspective
---
// In human-vs-human mode, this flips the board view for each player's turn.
// The player_is_white variable has already been flipped to the next player.
bool human_view_is_white = vs_computer ? is_human_white : player_is_white;
cursor_x = to_x;
cursor_y = human_view_is_white ? 7 - to_y : to_y;
last_cursor_x = cursor_x;
last_cursor_y = cursor_y;}
@m.pack_file = @gChessSoundPack;
if (@m.pack_file is null) @m.pack_file = @gMainMenuPack;
m.open_sound = "menu_open.wav";
m.select_sound = "promotion.wav";
m.close_sound = "score_list_close.wav";
activateChessTouchControls();
bool game_loaded = false;
if (mode_id == "load") {
string filename = open_file_dialog("NVGT Chess Save (*.json):json",
cwdir());
if (!filename.empty()) {
if (load_game_from_file(filename)) {
game_loaded = true;
game_over = false;
speak("Game loaded successfully.", true);
} else {
return;
}
} else {
return;
}
}
if (!game_loaded) {
if (mode_id == "ai") {
vs_computer = true;
player_is_white = true;
init_board();
game_over = false;
fifty_move_counter = 0;
@position_history = dictionary();
position_history.set(get_board_state_string(), 1);
}
// --- MODIFIED: Cursor now starts at the bottom-left of the current view.
cursor_x = 0;
cursor_y = 0;
chess_state = SELECT;
last_cursor_x = -1;
last_cursor_y = -1;
piece_selected = false;
selected_x = -1;
selected_y = -1;
playChessSound("start_game.wav");
if (!game_loaded) {
speak("Welcome to NVGT Chess. Use arrow keys to move, F1 for help, F2 to
save, and escape to quit.", true); wait(2200);
}
speak((player_is_white ? "White's turn." : "Black's turn."), false);
while (!game_over) {
// --- MODIFIED: Determine view perspective based on the current player's
turn.
// This makes the board flip in human-vs-human games.
bool human_view_is_white = vs_computer ? is_human_white : player_is_white;
if (is_ai_turn) {
wait(500);
speak("Computer is thinking...", true);
wait(100);
dictionary@ move = find_best_move(player_is_white);
if (@move !is null) {
int from_x = 0, from_y = 0, to_x = 0, to_y = 0;
move.get("from_x", from_x);
move.get("from_y", from_y);
move.get("to_x", to_x);
move.get("to_y", to_y);
perform_move(from_x, from_y, to_x, to_y);
} else {
bool in_check = is_king_in_check(player_is_white);
if (in_check) {
speak("The computer is in checkmate. " + (is_human_white ?
"White" : "Black") + " wins!", true);
} else {
speak("The computer is in stalemate. The game is a draw.",
true);
}
game_over = true;
}
continue;
}
gTouchManager.monitor();
if (key_pressed(KEY_F1)) {
string turn_info = "Current turn: " + (player_is_white ? "White" :
"Black") + ". ";
string state_info = (chess_state == SELECT) ? "Waiting to select a
piece." : "Waiting to move the selected piece.";
speak(turn_info + state_info, true);
} else if (key_pressed(KEY_F2)) {
save_game();
}
if (is_drop_target) {
playChessSound("drop_target.wav");
} else {
if ((cursor_x + display_y) % 2 == 0) {
playChessSound("black_square.wav");
}
}
string piece = board[display_y][cursor_x];
string name = get_piece_name(piece);
PieceColor color = get_piece_color(piece);
string color_name = (color == WHITE) ? "White " : (color == BLACK) ?
"Black " : "";
string pos = " " + string("ABCDEFGH").substr(cursor_x, 1) + " " +
(human_view_is_white ? cursor_y + 1 : 8 - cursor_y);
bool is_player_piece = (player_is_white && color == WHITE) || (!
player_is_white && color == BLACK);
if (is_drop_target) {
if (piece == ".") {
speak("Square drop target" + pos, true);
} else {
speak("Square drop target " + color_name + name + pos, true);
}
} else {
if (piece == ".") {
speak("Empty square" + pos, true);
} else {
if (chess_state == SELECT && is_player_piece) {
speak(color_name + name + " draggable" + pos, true);
} else {
speak(color_name + name + pos, true);
}
}
}
last_cursor_x = cursor_x;
last_cursor_y = cursor_y;
}
// --- MODIFIED: Key handling now respects the flipped board view for Black
player ---
if (human_view_is_white) {
// White's (or default) perspective
if (key_pressed(KEY_UP) && cursor_y < 7) cursor_y++;
if (key_pressed(KEY_DOWN) && cursor_y > 0) cursor_y--;
if (key_pressed(KEY_LEFT) && cursor_x > 0) cursor_x--;
if (key_pressed(KEY_RIGHT) && cursor_x < BOARD_SIZE - 1) cursor_x++;
} else {
// Black's perspective (board is flipped)
if (key_pressed(KEY_UP) && cursor_y < 7) cursor_y++;
if (key_pressed(KEY_DOWN) && cursor_y > 0) cursor_y--;
if (key_pressed(KEY_LEFT) && cursor_x < BOARD_SIZE - 1) cursor_x++;
if (key_pressed(KEY_RIGHT) && cursor_x > 0) cursor_x--;
}
if (key_pressed(KEY_RETURN)) {
int board_y = human_view_is_white ? 7 - cursor_y : cursor_y;
if (chess_state == SELECT) {
string piece = board[board_y][cursor_x];
PieceColor color = get_piece_color(piece);
if (piece != "." && ((player_is_white && color == WHITE) || (!
player_is_white && color == BLACK))) {
selected_x = cursor_x;
selected_y = board_y;
chess_state = MOVE;
playChessSound("pick_piece.wav");
speak("Selected " + get_piece_name(piece) + ". Use arrows to
choose destination, enter to drop.", true);
} else {
playChessSound("invalid.wav");
}
} else if (chess_state == MOVE) {
int to_y = human_view_is_white ? 7 - cursor_y : cursor_y;
if (is_valid_move(selected_x, selected_y, cursor_x, to_y)) {
perform_move(selected_x, selected_y, cursor_x, to_y);
} else {
playChessSound("invalid.wav");
speak("Invalid move. Selection cancelled. Select a piece.",
false);
chess_state = SELECT;
selected_x = -1;
selected_y = -1;
last_cursor_x = -1;
last_cursor_y = -1;
}
}
}
}
if (key_pressed(KEY_ESCAPE)) {
speak("Game exited.", true);
} else {
speak("Game over.", true);
wait(2000);
}