Created
March 6, 2026 17:55
-
-
Save williamd1k0/301d4fc641bd1a94c839fe5e5c827310 to your computer and use it in GitHub Desktop.
Dynasty Chess - A unique chess variant where capturing the king promotes one of the opponent's pieces to king instead of ending the game. The first player to lose all their pieces loses.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #include <SDL2/SDL.h> | |
| #include <SDL2/SDL_ttf.h> | |
| #include <iostream> | |
| #include <vector> | |
| #include <string> | |
| #include <ctime> | |
| #include <cstdlib> | |
| // Dynasty Chess - A unique chess variant where capturing the king promotes one of the opponent's pieces to king instead of ending the game. The first player to lose all their pieces loses. This implementation includes a predefined line of succession for promotions and a simple UI using SDL2. | |
| // This code was ~99% written by AI, with some minor adjustments. | |
| // Constants | |
| const int WINDOW_WIDTH = 600; | |
| const int WINDOW_HEIGHT = 675; | |
| const int BOARD_SIZE = 8; | |
| const int SQUARE_SIZE = 75; | |
| const int BOARD_OFFSET_X = 0; | |
| const int BOARD_OFFSET_Y = 75; | |
| // Colors - Elegant dark theme with warm accents | |
| const SDL_Color COLOR_LIGHT_SQUARE = {240, 217, 181, 255}; | |
| const SDL_Color COLOR_DARK_SQUARE = {181, 136, 99, 255}; | |
| const SDL_Color COLOR_SELECTED = {255, 200, 87, 180}; | |
| const SDL_Color COLOR_VALID_MOVE = {144, 238, 144, 120}; | |
| const SDL_Color COLOR_KING_HIGHLIGHT = {255, 215, 0, 150}; | |
| const SDL_Color COLOR_BG = {34, 40, 49, 255}; | |
| const SDL_Color COLOR_TEXT = {238, 238, 238, 255}; | |
| const SDL_Color COLOR_ACCENT = {255, 183, 77, 255}; | |
| enum PieceType { | |
| EMPTY = 0, | |
| PAWN, | |
| KNIGHT, | |
| BISHOP, | |
| ROOK, | |
| QUEEN, | |
| KING | |
| }; | |
| enum PieceColor { | |
| NONE = 0, | |
| WHITE, | |
| BLACK | |
| }; | |
| struct Piece { | |
| PieceType type; | |
| PieceColor color; | |
| bool hasMoved; | |
| Piece() : type(EMPTY), color(NONE), hasMoved(false) {} | |
| Piece(PieceType t, PieceColor c) : type(t), color(c), hasMoved(false) {} | |
| }; | |
| struct Position { | |
| int row, col; | |
| Position(int r = 0, int c = 0) : row(r), col(c) {} | |
| bool operator==(const Position& other) const { | |
| return row == other.row && col == other.col; | |
| } | |
| }; | |
| class ChessGame { | |
| private: | |
| SDL_Window* window; | |
| SDL_Renderer* renderer; | |
| TTF_Font* font; | |
| TTF_Font* pieceFont; | |
| TTF_Font* smallFont; | |
| Piece board[BOARD_SIZE][BOARD_SIZE]; | |
| PieceColor currentPlayer; | |
| Position selectedSquare; | |
| bool pieceSelected; | |
| std::vector<Position> validMoves; | |
| bool gameOver; | |
| PieceColor winner; | |
| std::string gameMessage; | |
| // Pre-defined line of succession (can be modified later) | |
| std::vector<PieceType> successionOrder; | |
| // Unicode chess pieces | |
| const char* pieceSymbols[7][2] = { | |
| {"", ""}, // EMPTY | |
| {"♙", "♟"}, // PAWN | |
| {"♘", "♞"}, // KNIGHT | |
| {"♗", "♝"}, // BISHOP | |
| {"♖", "♜"}, // ROOK | |
| {"♕", "♛"}, // QUEEN | |
| {"♔", "♚"} // KING | |
| }; | |
| public: | |
| ChessGame() : window(nullptr), renderer(nullptr), font(nullptr), | |
| pieceFont(nullptr), smallFont(nullptr), | |
| currentPlayer(WHITE), selectedSquare(-1, -1), | |
| pieceSelected(false), gameOver(false), winner(NONE) { | |
| srand(time(nullptr)); | |
| // Default succession: try to promote highest-value pieces first. | |
| successionOrder = { QUEEN, ROOK, BISHOP, KNIGHT, PAWN }; | |
| } | |
| bool initialize() { | |
| if (SDL_Init(SDL_INIT_VIDEO) < 0) { | |
| std::cerr << "SDL initialization failed: " << SDL_GetError() << std::endl; | |
| return false; | |
| } | |
| if (TTF_Init() < 0) { | |
| std::cerr << "TTF initialization failed: " << TTF_GetError() << std::endl; | |
| return false; | |
| } | |
| window = SDL_CreateWindow("Dynasty Chess", | |
| SDL_WINDOWPOS_CENTERED, | |
| SDL_WINDOWPOS_CENTERED, | |
| WINDOW_WIDTH, WINDOW_HEIGHT, | |
| SDL_WINDOW_SHOWN); | |
| if (!window) { | |
| std::cerr << "Window creation failed: " << SDL_GetError() << std::endl; | |
| return false; | |
| } | |
| renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED); | |
| if (!renderer) { | |
| std::cerr << "Renderer creation failed: " << SDL_GetError() << std::endl; | |
| return false; | |
| } | |
| // Try to load fonts - using default if not available | |
| pieceFont = TTF_OpenFont("/usr/share/fonts/TTF/DejaVuSans.ttf", 64); | |
| font = TTF_OpenFont("/usr/share/fonts/TTF/DejaVuSans-Bold.ttf", 28); | |
| smallFont = TTF_OpenFont("/usr/share/fonts/TTF/DejaVuSans.ttf", 16); | |
| if (!pieceFont || !font || !smallFont) { | |
| std::cerr << "Font loading failed: " << TTF_GetError() << std::endl; | |
| return false; | |
| } | |
| initializeBoard(); | |
| gameMessage = "White to move"; | |
| return true; | |
| } | |
| void initializeBoard() { | |
| // Clear board | |
| for (int i = 0; i < BOARD_SIZE; i++) { | |
| for (int j = 0; j < BOARD_SIZE; j++) { | |
| board[i][j] = Piece(); | |
| } | |
| } | |
| // Setup black pieces | |
| board[0][0] = Piece(ROOK, BLACK); | |
| board[0][1] = Piece(KNIGHT, BLACK); | |
| board[0][2] = Piece(BISHOP, BLACK); | |
| board[0][3] = Piece(QUEEN, BLACK); | |
| board[0][4] = Piece(KING, BLACK); | |
| board[0][5] = Piece(BISHOP, BLACK); | |
| board[0][6] = Piece(KNIGHT, BLACK); | |
| board[0][7] = Piece(ROOK, BLACK); | |
| for (int i = 0; i < BOARD_SIZE; i++) { | |
| board[1][i] = Piece(PAWN, BLACK); | |
| } | |
| // Setup white pieces | |
| for (int i = 0; i < BOARD_SIZE; i++) { | |
| board[6][i] = Piece(PAWN, WHITE); | |
| } | |
| board[7][0] = Piece(ROOK, WHITE); | |
| board[7][1] = Piece(KNIGHT, WHITE); | |
| board[7][2] = Piece(BISHOP, WHITE); | |
| board[7][3] = Piece(QUEEN, WHITE); | |
| board[7][4] = Piece(KING, WHITE); | |
| board[7][5] = Piece(BISHOP, WHITE); | |
| board[7][6] = Piece(KNIGHT, WHITE); | |
| board[7][7] = Piece(ROOK, WHITE); | |
| currentPlayer = WHITE; | |
| gameOver = false; | |
| winner = NONE; | |
| gameMessage = "White to move"; | |
| } | |
| void drawSquare(int row, int col, SDL_Color color) { | |
| SDL_Rect rect = { | |
| BOARD_OFFSET_X + col * SQUARE_SIZE, | |
| BOARD_OFFSET_Y + row * SQUARE_SIZE, | |
| SQUARE_SIZE, | |
| SQUARE_SIZE | |
| }; | |
| SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, color.a); | |
| SDL_RenderFillRect(renderer, &rect); | |
| } | |
| void drawText(const std::string& text, int x, int y, SDL_Color color, TTF_Font* textFont) { | |
| SDL_Surface* surface = TTF_RenderUTF8_Blended(textFont, text.c_str(), color); | |
| if (!surface) return; | |
| SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface); | |
| if (!texture) { | |
| SDL_FreeSurface(surface); | |
| return; | |
| } | |
| SDL_Rect rect = {x, y, surface->w, surface->h}; | |
| SDL_RenderCopy(renderer, texture, nullptr, &rect); | |
| SDL_DestroyTexture(texture); | |
| SDL_FreeSurface(surface); | |
| } | |
| void render() { | |
| // Clear screen with background color | |
| SDL_SetRenderDrawColor(renderer, COLOR_BG.r, COLOR_BG.g, COLOR_BG.b, COLOR_BG.a); | |
| SDL_RenderClear(renderer); | |
| // Draw header | |
| drawText("DYNASTY CHESS", 20, 10, COLOR_ACCENT, font); | |
| drawText(gameMessage, 20, 50, COLOR_TEXT, smallFont); | |
| // Draw board | |
| for (int row = 0; row < BOARD_SIZE; row++) { | |
| for (int col = 0; col < BOARD_SIZE; col++) { | |
| SDL_Color squareColor = ((row + col) % 2 == 0) ? COLOR_LIGHT_SQUARE : COLOR_DARK_SQUARE; | |
| drawSquare(row, col, squareColor); | |
| // Highlight selected square | |
| if (pieceSelected && selectedSquare.row == row && selectedSquare.col == col) { | |
| SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); | |
| drawSquare(row, col, COLOR_SELECTED); | |
| } | |
| // Highlight valid moves | |
| for (const auto& move : validMoves) { | |
| if (move.row == row && move.col == col) { | |
| SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); | |
| drawSquare(row, col, COLOR_VALID_MOVE); | |
| } | |
| } | |
| // Highlight kings | |
| if (board[row][col].type == KING) { | |
| SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); | |
| drawSquare(row, col, COLOR_KING_HIGHLIGHT); | |
| } | |
| } | |
| } | |
| // Draw pieces | |
| for (int row = 0; row < BOARD_SIZE; row++) { | |
| for (int col = 0; col < BOARD_SIZE; col++) { | |
| Piece& piece = board[row][col]; | |
| if (piece.type != EMPTY) { | |
| int colorIndex = (piece.color == WHITE) ? 0 : 1; | |
| std::string symbol = pieceSymbols[piece.type][colorIndex]; | |
| SDL_Color pieceColor = (piece.color == WHITE) ? | |
| SDL_Color{255, 255, 255, 255} : SDL_Color{30, 30, 30, 255}; | |
| int glyph_x_offset = - 28; | |
| int glyph_y_offset = - 38; | |
| int x = BOARD_OFFSET_X + col * SQUARE_SIZE + SQUARE_SIZE / 2 + glyph_x_offset; | |
| int y = BOARD_OFFSET_Y + row * SQUARE_SIZE + SQUARE_SIZE / 2 + glyph_y_offset; | |
| drawText(symbol, x, y, pieceColor, pieceFont); | |
| } | |
| } | |
| } | |
| // Draw game over message | |
| if (gameOver) { | |
| SDL_Rect overlay = {0, 0, WINDOW_WIDTH, WINDOW_HEIGHT}; | |
| SDL_SetRenderDrawColor(renderer, 0, 0, 0, 180); | |
| SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); | |
| SDL_RenderFillRect(renderer, &overlay); | |
| std::string winnerText = (winner == WHITE) ? "White Wins!" : "Black Wins!"; | |
| drawText(winnerText, WINDOW_WIDTH / 2 - 80, WINDOW_HEIGHT / 2 - 40, COLOR_ACCENT, font); | |
| drawText("Press R to restart", WINDOW_WIDTH / 2 - 100, WINDOW_HEIGHT / 2 + 20, COLOR_TEXT, smallFont); | |
| } | |
| SDL_RenderPresent(renderer); | |
| } | |
| bool isValidPosition(int row, int col) { | |
| return row >= 0 && row < BOARD_SIZE && col >= 0 && col < BOARD_SIZE; | |
| } | |
| std::vector<Position> getValidMoves(int row, int col) { | |
| std::vector<Position> moves; | |
| Piece& piece = board[row][col]; | |
| if (piece.type == EMPTY || piece.color != currentPlayer) { | |
| return moves; | |
| } | |
| switch (piece.type) { | |
| case PAWN: | |
| getPawnMoves(row, col, moves); | |
| break; | |
| case KNIGHT: | |
| getKnightMoves(row, col, moves); | |
| break; | |
| case BISHOP: | |
| getBishopMoves(row, col, moves); | |
| break; | |
| case ROOK: | |
| getRookMoves(row, col, moves); | |
| break; | |
| case QUEEN: | |
| getQueenMoves(row, col, moves); | |
| break; | |
| case KING: | |
| getKingMoves(row, col, moves); | |
| break; | |
| default: | |
| break; | |
| } | |
| return moves; | |
| } | |
| void getPawnMoves(int row, int col, std::vector<Position>& moves) { | |
| int direction = (board[row][col].color == WHITE) ? -1 : 1; | |
| int startRow = (board[row][col].color == WHITE) ? 6 : 1; | |
| // Forward move | |
| if (isValidPosition(row + direction, col) && board[row + direction][col].type == EMPTY) { | |
| moves.push_back(Position(row + direction, col)); | |
| // Double move from start | |
| if (row == startRow && board[row + 2 * direction][col].type == EMPTY) { | |
| moves.push_back(Position(row + 2 * direction, col)); | |
| } | |
| } | |
| // Diagonal captures | |
| for (int dc = -1; dc <= 1; dc += 2) { | |
| int newRow = row + direction; | |
| int newCol = col + dc; | |
| if (isValidPosition(newRow, newCol) && | |
| board[newRow][newCol].type != EMPTY && | |
| board[newRow][newCol].color != board[row][col].color) { | |
| moves.push_back(Position(newRow, newCol)); | |
| } | |
| } | |
| } | |
| void getKnightMoves(int row, int col, std::vector<Position>& moves) { | |
| int knightMoves[8][2] = {{-2,-1}, {-2,1}, {-1,-2}, {-1,2}, {1,-2}, {1,2}, {2,-1}, {2,1}}; | |
| for (int i = 0; i < 8; i++) { | |
| int newRow = row + knightMoves[i][0]; | |
| int newCol = col + knightMoves[i][1]; | |
| if (isValidPosition(newRow, newCol) && | |
| (board[newRow][newCol].type == EMPTY || | |
| board[newRow][newCol].color != board[row][col].color)) { | |
| moves.push_back(Position(newRow, newCol)); | |
| } | |
| } | |
| } | |
| void getBishopMoves(int row, int col, std::vector<Position>& moves) { | |
| int directions[4][2] = {{-1,-1}, {-1,1}, {1,-1}, {1,1}}; | |
| for (int d = 0; d < 4; d++) { | |
| for (int i = 1; i < BOARD_SIZE; i++) { | |
| int newRow = row + directions[d][0] * i; | |
| int newCol = col + directions[d][1] * i; | |
| if (!isValidPosition(newRow, newCol)) break; | |
| if (board[newRow][newCol].type == EMPTY) { | |
| moves.push_back(Position(newRow, newCol)); | |
| } else { | |
| if (board[newRow][newCol].color != board[row][col].color) { | |
| moves.push_back(Position(newRow, newCol)); | |
| } | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| void getRookMoves(int row, int col, std::vector<Position>& moves) { | |
| int directions[4][2] = {{-1,0}, {1,0}, {0,-1}, {0,1}}; | |
| for (int d = 0; d < 4; d++) { | |
| for (int i = 1; i < BOARD_SIZE; i++) { | |
| int newRow = row + directions[d][0] * i; | |
| int newCol = col + directions[d][1] * i; | |
| if (!isValidPosition(newRow, newCol)) break; | |
| if (board[newRow][newCol].type == EMPTY) { | |
| moves.push_back(Position(newRow, newCol)); | |
| } else { | |
| if (board[newRow][newCol].color != board[row][col].color) { | |
| moves.push_back(Position(newRow, newCol)); | |
| } | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| void getQueenMoves(int row, int col, std::vector<Position>& moves) { | |
| getBishopMoves(row, col, moves); | |
| getRookMoves(row, col, moves); | |
| } | |
| void getKingMoves(int row, int col, std::vector<Position>& moves) { | |
| for (int dr = -1; dr <= 1; dr++) { | |
| for (int dc = -1; dc <= 1; dc++) { | |
| if (dr == 0 && dc == 0) continue; | |
| int newRow = row + dr; | |
| int newCol = col + dc; | |
| if (isValidPosition(newRow, newCol) && | |
| (board[newRow][newCol].type == EMPTY || | |
| board[newRow][newCol].color != board[row][col].color)) { | |
| moves.push_back(Position(newRow, newCol)); | |
| } | |
| } | |
| } | |
| } | |
| void promoteRandomPiece(PieceColor color) { | |
| std::vector<Position> availablePieces; | |
| // Find all non-king pieces of the given color | |
| for (int row = 0; row < BOARD_SIZE; row++) { | |
| for (int col = 0; col < BOARD_SIZE; col++) { | |
| if (board[row][col].color == color && board[row][col].type != KING) { | |
| availablePieces.push_back(Position(row, col)); | |
| } | |
| } | |
| } | |
| // If no pieces available, player loses | |
| if (availablePieces.empty()) { | |
| gameOver = true; | |
| winner = (color == WHITE) ? BLACK : WHITE; | |
| std::string colorName = (winner == WHITE) ? "White" : "Black"; | |
| gameMessage = colorName + " wins! No pieces left to promote."; | |
| return; | |
| } | |
| // Use predefined line of succession to pick which piece to promote. | |
| // Find the first piece matching the succession order. | |
| Position promoted = availablePieces[0]; // fallback | |
| PieceType oldType = board[promoted.row][promoted.col].type; | |
| bool foundByOrder = false; | |
| for (PieceType desiredType : successionOrder) { | |
| for (const auto& pos : availablePieces) { | |
| if (board[pos.row][pos.col].type == desiredType) { | |
| promoted = pos; | |
| oldType = desiredType; | |
| foundByOrder = true; | |
| break; | |
| } | |
| } | |
| if (foundByOrder) break; | |
| } | |
| // If none matched the succession order (unlikely), keep fallback (first found). | |
| board[promoted.row][promoted.col].type = KING; | |
| std::string pieceNames[] = {"", "Pawn", "Knight", "Bishop", "Rook", "Queen", "King"}; | |
| std::string colorName = (color == WHITE) ? "White" : "Black"; | |
| gameMessage = colorName + " " + pieceNames[oldType] + " promoted to King!"; | |
| } | |
| bool movePiece(Position from, Position to) { | |
| Piece& fromPiece = board[from.row][from.col]; | |
| Piece& toPiece = board[to.row][to.col]; | |
| // Check if capturing a king | |
| if (toPiece.type == KING && toPiece.color != fromPiece.color) { | |
| // King captured! Promote piece according to succession order | |
| promoteRandomPiece(toPiece.color); | |
| toPiece = fromPiece; | |
| board[from.row][from.col] = Piece(); | |
| currentPlayer = (currentPlayer == WHITE) ? BLACK : WHITE; | |
| if (!gameOver) { | |
| std::string nextPlayer = (currentPlayer == WHITE) ? "White" : "Black"; | |
| gameMessage += " | " + nextPlayer + " to move"; | |
| } | |
| return true; | |
| } | |
| // Normal move | |
| toPiece = fromPiece; | |
| board[from.row][from.col] = Piece(); | |
| toPiece.hasMoved = true; | |
| // Pawn promotion (still useful for getting queens/rooks) | |
| if (toPiece.type == PAWN) { | |
| if ((toPiece.color == WHITE && to.row == 0) || | |
| (toPiece.color == BLACK && to.row == 7)) { | |
| toPiece.type = QUEEN; | |
| } | |
| } | |
| currentPlayer = (currentPlayer == WHITE) ? BLACK : WHITE; | |
| std::string nextPlayer = (currentPlayer == WHITE) ? "White" : "Black"; | |
| gameMessage = nextPlayer + " to move"; | |
| return true; | |
| } | |
| void handleClick(int mouseX, int mouseY) { | |
| if (gameOver) return; | |
| int col = (mouseX - BOARD_OFFSET_X) / SQUARE_SIZE; | |
| int row = (mouseY - BOARD_OFFSET_Y) / SQUARE_SIZE; | |
| if (!isValidPosition(row, col)) return; | |
| if (!pieceSelected) { | |
| // Select piece | |
| if (board[row][col].type != EMPTY && board[row][col].color == currentPlayer) { | |
| selectedSquare = Position(row, col); | |
| pieceSelected = true; | |
| validMoves = getValidMoves(row, col); | |
| } | |
| } else { | |
| // Try to move piece | |
| Position clickedPos(row, col); | |
| // Check if clicking on another own piece (reselect) | |
| if (board[row][col].color == currentPlayer) { | |
| selectedSquare = Position(row, col); | |
| validMoves = getValidMoves(row, col); | |
| return; | |
| } | |
| // Check if move is valid | |
| bool isValid = false; | |
| for (const auto& move : validMoves) { | |
| if (move == clickedPos) { | |
| isValid = true; | |
| break; | |
| } | |
| } | |
| if (isValid) { | |
| movePiece(selectedSquare, clickedPos); | |
| } | |
| pieceSelected = false; | |
| validMoves.clear(); | |
| } | |
| } | |
| void run() { | |
| bool running = true; | |
| SDL_Event event; | |
| while (running) { | |
| while (SDL_PollEvent(&event)) { | |
| if (event.type == SDL_QUIT) { | |
| running = false; | |
| } else if (event.type == SDL_MOUSEBUTTONDOWN) { | |
| if (event.button.button == SDL_BUTTON_LEFT) { | |
| handleClick(event.button.x, event.button.y); | |
| } | |
| } else if (event.type == SDL_KEYDOWN) { | |
| if (event.key.keysym.sym == SDLK_r) { | |
| initializeBoard(); | |
| pieceSelected = false; | |
| validMoves.clear(); | |
| } | |
| } | |
| } | |
| render(); | |
| SDL_Delay(16); // ~60 FPS | |
| } | |
| } | |
| void cleanup() { | |
| if (pieceFont) TTF_CloseFont(pieceFont); | |
| if (font) TTF_CloseFont(font); | |
| if (smallFont) TTF_CloseFont(smallFont); | |
| if (renderer) SDL_DestroyRenderer(renderer); | |
| if (window) SDL_DestroyWindow(window); | |
| TTF_Quit(); | |
| SDL_Quit(); | |
| } | |
| }; | |
| int main() { | |
| ChessGame game; | |
| if (!game.initialize()) { | |
| std::cerr << "Failed to initialize game" << std::endl; | |
| return 1; | |
| } | |
| game.run(); | |
| game.cleanup(); | |
| return 0; | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| CXX = g++ | |
| CXXFLAGS = -std=c++11 -Wall -Wextra | |
| LDFLAGS = -lSDL2 -lSDL2_ttf | |
| TARGET = chess2 | |
| SOURCE = chess2.cpp | |
| all: $(TARGET) | |
| $(TARGET): $(SOURCE) | |
| $(CXX) $(CXXFLAGS) -o $(TARGET) $(SOURCE) $(LDFLAGS) | |
| clean: | |
| rm -f $(TARGET) | |
| run: $(TARGET) | |
| ./$(TARGET) | |
| .PHONY: all clean run |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment