Как написать шахматы на питоне


 

·

23 min read
· Updated
nov 2022

· GUI Programming

The chess game is a pretty cool project idea for intermediate Python programmers. It’s good practice for making apps with GUIs while getting good at using classes. In this tutorial, you learn about:

  • Using the basics of pygame.
  • Learn how to code a chess game with Python classes.

Installation and Setting Up

Before we start coding, let’s first install the pygame module in the terminal:

$ pip install pygame

Once we installed the pygame, let’s move into setting up our environment by making the py files and folder we’re using in this order:

> python-chess
    > data
        > classes
            > pieces
                /* Bishop.py
                /* King.py
                /* Knight.py
                /* Pawn.py
                /* Queen.py
                /* Rook.py
            /* Board.py
            /* Piece.py
            /* Square.py
        > imgs
    /* main.py

Move the images of the chess icons you’ll use in the python/data/imgs/ directory. Make sure your image files are named [color's 1st letter]_[piece name].png just like this:

If you don’t have chess icons, you can use mine here.

Coding the Game

And now we’re done setting up; we can start coding now. Our chess game has two main code parts; creating the board and creating the pieces. The board will mainly focus on square positions and game rules, while the pieces focus on the piece they represent and the moves it has.

Making the Board

Let’s start by making the Square class. The Square class creates, colors, position, and draw each chess tile inside our game window:

# /* Square.py
import pygame

# Tile creator
class Square:
    def __init__(self, x, y, width, height):
        self.x = x
        self.y = y
        self.width = width
        self.height = height
        self.abs_x = x * width
        self.abs_y = y * height
        self.abs_pos = (self.abs_x, self.abs_y)
        self.pos = (x, y)
        self.color = 'light' if (x + y) % 2 == 0 else 'dark'
        self.draw_color = (220, 208, 194) if self.color == 'light' else (53, 53, 53)
        self.highlight_color = (100, 249, 83) if self.color == 'light' else (0, 228, 10)
        self.occupying_piece = None
        self.coord = self.get_coord()
        self.highlight = False
        self.rect = pygame.Rect(
            self.abs_x,
            self.abs_y,
            self.width,
            self.height
        )

    # get the formal notation of the tile
    def get_coord(self):
        columns = 'abcdefgh'
        return columns[self.x] + str(self.y + 1)

    def draw(self, display):
        # configures if tile should be light or dark or highlighted tile
        if self.highlight:
            pygame.draw.rect(display, self.highlight_color, self.rect)
        else:
            pygame.draw.rect(display, self.draw_color, self.rect)
        # adds the chess piece icons
        if self.occupying_piece != None:
            centering_rect = self.occupying_piece.img.get_rect()
            centering_rect.center = self.rect.center
            display.blit(self.occupying_piece.img, centering_rect.topleft)

The first thing we’re gonna do is to make a class for making chess Square. Let’s start by adding the __init__() function to get the square’s width, height, x for a row, and y for the column.

With this basic information, we can fulfill other variables using them. As you see above, we have self.x and self.y while we also have self.abs_x and self.abs_y. self.abs_x and self.abs_y dictates where the chess tile is assigned to be drawn inside the window, and we compile them both in self.abs_pos.

The self.color tells the square tile should be light colored if it is divisible by 2 or instead dark if not, while the self.draw_color tells the color configuration for light and dark. We also have self.highlight_color which we use to highlight the tiles with the possible movement of a piece if it was selected. The self.rect configures the width, height, and location (using self.abs_x and self.abs_y) of a square or tile.

The get_coord() returns the name of the tile depending on its x and y based on the real board. Letters symbolize rows, and the number symbolizes columns. Like «a1», it is the bottom leftmost tile in a chess board.

The draw(), executes the configurations we did by drawing the tile in the canvas, in the color it was assigned. The second if statement tells that if the square has a piece in this position, you should access its icon and place it inside the tile.

Now we have a class for making a square. Let’s make another class for handling tiles and the whole board.

# /* Board.py

import pygame
from data.classes.Square import Square
from data.classes.pieces.Rook import Rook
from data.classes.pieces.Bishop import Bishop
from data.classes.pieces.Knight import Knight
from data.classes.pieces.Queen import Queen
from data.classes.pieces.King import King
from data.classes.pieces.Pawn import Pawn

# Game state checker
class Board:
    def __init__(self, width, height):
        self.width = width
        self.height = height
        self.tile_width = width // 8
        self.tile_height = height // 8
        self.selected_piece = None
        self.turn = 'white'
        self.config = [
            ['bR', 'bN', 'bB', 'bQ', 'bK', 'bB', 'bN', 'bR'],
            ['bP', 'bP', 'bP', 'bP', 'bP', 'bP', 'bP', 'bP'],
            ['','','','','','','',''],
            ['','','','','','','',''],
            ['','','','','','','',''],
            ['','','','','','','',''],
            ['wP', 'wP', 'wP', 'wP', 'wP', 'wP', 'wP', 'wP'],
            ['wR', 'wN', 'wB', 'wQ', 'wK', 'wB', 'wN', 'wR'],
        ]
        self.squares = self.generate_squares()
        self.setup_board()

    def generate_squares(self):
        output = []
        for y in range(8):
            for x in range(8):
                output.append(
                    Square(x,  y, self.tile_width, self.tile_height)
                )
        return output

    def get_square_from_pos(self, pos):
        for square in self.squares:
            if (square.x, square.y) == (pos[0], pos[1]):
                return square

    def get_piece_from_pos(self, pos):
        return self.get_square_from_pos(pos).occupying_piece

In making the whole chess board, it’s important to know first what is the width and height of the game window so we can divide it into 8 rows with 8 columns to identify our tiles’ exact size.

The self.config represents the chessboard configuration with a 2D list having our pieces with their default position. Below it, we configure the self.squares with a value calling our self.generate_squares() for making chess tiles and putting them all in a list.

Now let’s create the other parts of the Board, including the self.setup_board() we called above.

    def setup_board(self):
        for y, row in enumerate(self.config):
            for x, piece in enumerate(row):
                if piece != '':
                    square = self.get_square_from_pos((x, y))
                    # looking inside contents, what piece does it have
                    if piece[1] == 'R':
                        square.occupying_piece = Rook(
                            (x, y), 'white' if piece[0] == 'w' else 'black', self
                        )
                    # as you notice above, we put `self` as argument, or means our class Board
                    elif piece[1] == 'N':
                        square.occupying_piece = Knight(
                            (x, y), 'white' if piece[0] == 'w' else 'black', self
                        )
                    elif piece[1] == 'B':
                        square.occupying_piece = Bishop(
                            (x, y), 'white' if piece[0] == 'w' else 'black', self
                        )
                    elif piece[1] == 'Q':
                        square.occupying_piece = Queen(
                            (x, y), 'white' if piece[0] == 'w' else 'black', self
                        )
                    elif piece[1] == 'K':
                        square.occupying_piece = King(
                            (x, y), 'white' if piece[0] == 'w' else 'black', self
                        )
                    elif piece[1] == 'P':
                        square.occupying_piece = Pawn(
                            (x, y), 'white' if piece[0] == 'w' else 'black', self
                        )

The setup_board() creates each piece, and puts them in their respective place by mapping the self.config with the whole board. If the current value of piece in self.config is an empty string or '', then the tile must be empty, and if not, it will access its respective tile position through the current value of x and y. Each piece from self.config will be identified according to the capital letter it has, except the Knight.

If we got 'N', then it’s a Knight if 'P', then it’s a Pawn. If 'R', then it’s a Rook, ‘B’ for Bishop, and so on. After we configure the letters, we’ll overwrite the current square.occupying_piece with a value of their piece class with the color depending on the first value of the piece string. As you noticed here and in the other statement:

                    if piece[1] == 'R':
                        square.occupying_piece = Rook(
                            (x, y), 'white' if piece[0] == 'w' else 'black', self

We put a self as an argument for the Rook class. That means we put our current class, Board, as an argument.

We need a function that detects each click in our game. So let’s make handle_click() in our Board class:

    def handle_click(self, mx, my):
        x = mx // self.tile_width
        y = my // self.tile_height
        clicked_square = self.get_square_from_pos((x, y))
        if self.selected_piece is None:
            if clicked_square.occupying_piece is not None:
                if clicked_square.occupying_piece.color == self.turn:
                    self.selected_piece = clicked_square.occupying_piece
        elif self.selected_piece.move(self, clicked_square):
            self.turn = 'white' if self.turn == 'black' else 'black'
        elif clicked_square.occupying_piece is not None:
            if clicked_square.occupying_piece.color == self.turn:
                self.selected_piece = clicked_square.occupying_piece

The handle_click() accepts the x (mx) and y (my) coordinates of where you click inside the game window as an argument. The x and y variables inside this function compute what row and column you clicked, then we pass its outcomes to clicked_square to get the square or the tile.

This configuration can now receive our every click inside the game window. The following if/else statements process our click if we’re making a move or just clicking around.

It all works once you clicked somewhere inside the game window so let’s assume that you’re playing the game using a white piece and you’ve already clicked. If we haven’t selected any piece yet, it will look as if the tile you clicked has a piece, and if it’s your colors turn, and if yes, it will be your self.selected_piece.

With the help of other classes, your piece’s possible move will be highlighted in the game. After selecting a piece’s move, it will convert the self.turn into the next player’s piece color.

Now that we have a selected piece and chosen a move, it will make the move. I’ll explain the other moving later as we make the classes for that piece.

Let’s add another feature for the Board class; we are adding functions that check if a player is in check or checkmate.

    # check state checker
    def is_in_check(self, color, board_change=None): # board_change = [(x1, y1), (x2, y2)]
        output = False
        king_pos = None
        changing_piece = None
        old_square = None
        new_square = None
        new_square_old_piece = None
        if board_change is not None:
            for square in self.squares:
                if square.pos == board_change[0]:
                    changing_piece = square.occupying_piece
                    old_square = square
                    old_square.occupying_piece = None
            for square in self.squares:
                if square.pos == board_change[1]:
                    new_square = square
                    new_square_old_piece = new_square.occupying_piece
                    new_square.occupying_piece = changing_piece
        pieces = [
            i.occupying_piece for i in self.squares if i.occupying_piece is not None
        ]
        if changing_piece is not None:
            if changing_piece.notation == 'K':
                king_pos = new_square.pos
        if king_pos == None:
            for piece in pieces:
                if piece.notation == 'K' and piece.color == color:
                        king_pos = piece.pos
        for piece in pieces:
            if piece.color != color:
                for square in piece.attacking_squares(self):
                    if square.pos == king_pos:
                        output = True
        if board_change is not None:
            old_square.occupying_piece = changing_piece
            new_square.occupying_piece = new_square_old_piece
        return output

For every move we make, the is_in_check() function will be called, whenever the board_change is not empty.

In the first iteration, it locates the position of the old tile, passes its current piece in changing_piece, and empty that tile while in the second iteration, it catches the new tile position and passes its current piece to new_square_old_piece and give it a new piece from changing_piece.

Once our changing_piece is not empty, it’ll try to identify if it’s a King by getting its self.notation. If so, it’ll override the king_pos and give it the value of new_square.pos.

Note: The self.notation is a variable from the pieces’ class, that serves as an identification containing their letter symbols.

The next thing we’ll try to identify is what the enemy piece does to do the check to our player’s King, where we check starting by if piece.color != color.

        for piece in pieces:
            if piece.color != color:
                for square in piece.attacking_squares(self):
                    if square.pos == king_pos:
                        output = True

With the following code above, we can iterate through enemy pieces and check their attacking_squares, which gets all the possible moves of a piece. If a piece has a position in attacking_squares the same value as king_pos, which means one of the players is checked, so we set the output to True. The output tells if a King is in check or not, so we have to return it.

Now let’s make the is_in_checkmate() function for identifying if we have a winner yet:

    # checkmate state checker
    def is_in_checkmate(self, color):
        output = False
        for piece in [i.occupying_piece for i in self.squares]:
            if piece != None:
                if piece.notation == 'K' and piece.color == color:
                    king = piece
        if king.get_valid_moves(self) == []:
            if self.is_in_check(color):
                output = True
        return output

Once we get the King the same color as the argument we passed, it’ll try to see if it has any moves left. If none, then it’ll check if the player is in check. If that’s the case, then it will return the value of the output which is True, which means the side of the color we passed is checkmate.

Now we have all the board configurations; it’s time to add the final function for the Board class which is the draw() function:

    def draw(self, display):
        if self.selected_piece is not None:
            self.get_square_from_pos(self.selected_piece.pos).highlight = True
            for square in self.selected_piece.get_valid_moves(self):
                square.highlight = True
        for square in self.squares:
            square.draw(display)

This function highlights all the possible moves of a piece once selected while it is its color’s turn.

Making the Pieces

Now we’re finished with Board class, let’s make another class for pieces in Piece.py.

Let’s start by adding a function that gets all the available moves and a checker if the next player got checked by the previous player:

# /* Piece.py

import pygame

class Piece:
    def __init__(self, pos, color, board):
        self.pos = pos
        self.x = pos[0]
        self.y = pos[1]
        self.color = color
        self.has_moved = False

    def get_moves(self, board):
        output = []
        for direction in self.get_possible_moves(board):
            for square in direction:
                if square.occupying_piece is not None:
                    if square.occupying_piece.color == self.color:
                        break
                    else:
                        output.append(square)
                        break
                else:
                    output.append(square)
        return output

The get_moves() gets all the available moves of the current player, including attacking the enemy piece. If an opponent piece is in range of a piece’s move, the piece can capture it where its range will limit on these opponent’s piece tile position through output.append(square) then break unless the piece is Knight which can move in an ‘L-shape’.

    def get_valid_moves(self, board):
        output = []
        for square in self.get_moves(board):
            if not board.is_in_check(self.color, board_change=[self.pos, square.pos]):
                output.append(square)
        return output

Before proceeding for our current player in making a move, the get_valid_moves() checks first if the last player does a move that checked our current player. And if not, then it will return the available moves.

To make the pieces work, we’re adding a move() function that handles every move we make on the board:

    def move(self, board, square, force=False):
        for i in board.squares:
            i.highlight = False
        if square in self.get_valid_moves(board) or force:
            prev_square = board.get_square_from_pos(self.pos)
            self.pos, self.x, self.y = square.pos, square.x, square.y
            prev_square.occupying_piece = None
            square.occupying_piece = self
            board.selected_piece = None
            self.has_moved = True
            # Pawn promotion
            if self.notation == ' ':
                if self.y == 0 or self.y == 7:
                    from data.classes.pieces.Queen import Queen
                    square.occupying_piece = Queen(
                        (self.x, self.y),
                        self.color,
                        board
                    )
            # Move rook if king castles
            if self.notation == 'K':
                if prev_square.x - self.x == 2:
                    rook = board.get_piece_from_pos((0, self.y))
                    rook.move(board, board.get_square_from_pos((3, self.y)), force=True)
                elif prev_square.x - self.x == -2:
                    rook = board.get_piece_from_pos((7, self.y))
                    rook.move(board, board.get_square_from_pos((5, self.y)), force=True)
            return True
        else:
            board.selected_piece = None
            return False

    # True for all pieces except pawn
    def attacking_squares(self, board):
        return self.get_moves(board)

It takes board and square as arguments. If the tile we select to move our chosen piece is in self.get_valid_moves(), the move is valid to execute. To make it happen, the move() function will get the current square using board.get_square_from_pos(self.pos) and save it in prev_square and get its positions square.pos, square.x, square.y and save it in self.pos, self.x, and self.y for further use.

Then the function will empty the prev_square, and the piece (self — the current chess piece class) will be moved to the square.occupying_piece.

Chess has cool features; some of them are castling and pawn promotion, and that’s what we do next.

If the notation of the piece we’ve just moved in is ' ', which is a pawn, and it reaches row 0 (for white pawns) or row 7 (for black pawns), the pawn will be replaced by another queen of the same color.

And if the piece’s notation is 'K' and then moved 2 tiles to the left or right, it means the player’s move is casting.

Making a Class for Each Piece

Now that we’ve finished the Square, Board, and Piece classes, it’s time to create different classes for every piece type. Each piece will have the main Piece class as its parent class:

# /* Pawn.py

import pygame

from data.classes.Piece import Piece

class Pawn(Piece):
    def __init__(self, pos, color, board):
        super().__init__(pos, color, board)
        img_path = 'data/imgs/' + color[0] + '_pawn.png'
        self.img = pygame.image.load(img_path)
        self.img = pygame.transform.scale(self.img, (board.tile_width - 35, board.tile_height - 35))
        self.notation = ' '

    def get_possible_moves(self, board):
        output = []
        moves = []
        # move forward
        if self.color == 'white':
            moves.append((0, -1))
            if not self.has_moved:
                moves.append((0, -2))
        elif self.color == 'black':
            moves.append((0, 1))
            if not self.has_moved:
                moves.append((0, 2))
        for move in moves:
            new_pos = (self.x, self.y + move[1])
            if new_pos[1] < 8 and new_pos[1] >= 0:
                output.append(
                    board.get_square_from_pos(new_pos)
                )
        return output

    def get_moves(self, board):
        output = []
        for square in self.get_possible_moves(board):
            if square.occupying_piece != None:
                break
            else:
                output.append(square)
        if self.color == 'white':
            if self.x + 1 < 8 and self.y - 1 >= 0:
                square = board.get_square_from_pos(
                    (self.x + 1, self.y - 1)
                )
                if square.occupying_piece != None:
                    if square.occupying_piece.color != self.color:
                        output.append(square)
            if self.x - 1 >= 0 and self.y - 1 >= 0:
                square = board.get_square_from_pos(
                    (self.x - 1, self.y - 1)
                )
                if square.occupying_piece != None:
                    if square.occupying_piece.color != self.color:
                        output.append(square)
        elif self.color == 'black':
            if self.x + 1 < 8 and self.y + 1 < 8:
                square = board.get_square_from_pos(
                    (self.x + 1, self.y + 1)
                )
                if square.occupying_piece != None:
                    if square.occupying_piece.color != self.color:
                        output.append(square)
            if self.x - 1 >= 0 and self.y + 1 < 8:
                square = board.get_square_from_pos(
                    (self.x - 1, self.y + 1)
                )
                if square.occupying_piece != None:
                    if square.occupying_piece.color != self.color:
                        output.append(square)
        return output

    def attacking_squares(self, board):
        moves = self.get_moves(board)
        # return the diagonal moves 
        return [i for i in moves if i.x != self.x]

Here is our code for the Pawn pieces, whether it is black or white. As you notice, we have get_moves() and attacking_square() functions here in the Pawn class, just like the functions we have in the Piece class but given with a different script. It is because pawn pieces are basically allowed to move 1 step at a time away from their team position. A pawn also has 3 possible moves; the pawn can move up to 2 tiles from its starting position only, can move 1 step forward at a time, and can capture a piece 1 diagonally step at a time.

As we noticed, we have another function which is the get_possible_moves(). As of its name, it gets all the possible moves of a piece base on the current state of the board.

Now let’s move to do the other codes for other pieces.

Code for Knight.py:

# /* Kinght.py

import pygame
from data.classes.Piece import Piece

class Knight(Piece):
    def __init__(self, pos, color, board):
        super().__init__(pos, color, board)
        img_path = 'data/imgs/' + color[0] + '_knight.png'
        self.img = pygame.image.load(img_path)
        self.img = pygame.transform.scale(self.img, (board.tile_width - 20, board.tile_height - 20))
        self.notation = 'N'

    def get_possible_moves(self, board):
        output = []
        moves = [
            (1, -2),
            (2, -1),
            (2, 1),
            (1, 2),
            (-1, 2),
            (-2, 1),
            (-2, -1),
            (-1, -2)
        ]
        for move in moves:
            new_pos = (self.x + move[0], self.y + move[1])
            if (
                new_pos[0] < 8 and
                new_pos[0] >= 0 and 
                new_pos[1] < 8 and 
                new_pos[1] >= 0
            ):
                output.append([
                    board.get_square_from_pos(
                        new_pos
                    )
                ])
        return output

Code for Bishop.py:

# /* Bishop.py

import pygame
from data.classes.Piece import Piece

class Bishop(Piece):
    def __init__(self, pos, color, board):
        super().__init__(pos, color, board)
        img_path = 'data/imgs/' + color[0] + '_bishop.png'
        self.img = pygame.image.load(img_path)
        self.img = pygame.transform.scale(self.img, (board.tile_width - 20, board.tile_height - 20))
        self.notation = 'B'

    def get_possible_moves(self, board):
        output = []
        moves_ne = []
        for i in range(1, 8):
            if self.x + i > 7 or self.y - i < 0:
                break
            moves_ne.append(board.get_square_from_pos(
                (self.x + i, self.y - i)
            ))
        output.append(moves_ne)
        moves_se = []
        for i in range(1, 8):
            if self.x + i > 7 or self.y + i > 7:
                break
            moves_se.append(board.get_square_from_pos(
                (self.x + i, self.y + i)
            ))
        output.append(moves_se)
        moves_sw = []
        for i in range(1, 8):
            if self.x - i < 0 or self.y + i > 7:
                break
            moves_sw.append(board.get_square_from_pos(
                (self.x - i, self.y + i)
            ))
        output.append(moves_sw)
        moves_nw = []
        for i in range(1, 8):
            if self.x - i < 0 or self.y - i < 0:
                break
            moves_nw.append(board.get_square_from_pos(
                (self.x - i, self.y - i)
            ))
        output.append(moves_nw)
        return output

Code for Rook.py:

# /* Rook.py

import pygame

from data.classes.Piece import Piece

class Rook(Piece):
    def __init__(self, pos, color, board):
        super().__init__(pos, color, board)
        img_path = 'data/imgs/' + color[0] + '_rook.png'
        self.img = pygame.image.load(img_path)
        self.img = pygame.transform.scale(self.img, (board.tile_width - 20, board.tile_height - 20))
        self.notation = 'R'

    def get_possible_moves(self, board):
        output = []
        moves_north = []
        for y in range(self.y)[::-1]:
            moves_north.append(board.get_square_from_pos(
                (self.x, y)
            ))
        output.append(moves_north)
        moves_east = []
        for x in range(self.x + 1, 8):
            moves_east.append(board.get_square_from_pos(
                (x, self.y)
            ))
        output.append(moves_east)
        moves_south = []
        for y in range(self.y + 1, 8):
            moves_south.append(board.get_square_from_pos(
                (self.x, y)
            ))
        output.append(moves_south)
        moves_west = []
        for x in range(self.x)[::-1]:
            moves_west.append(board.get_square_from_pos(
                (x, self.y)
            ))
        output.append(moves_west)
        return output

Code for Queen.py:

# /* Queen.py

import pygame
from data.classes.Piece import Piece

class Queen(Piece):
    def __init__(self, pos, color, board):
        super().__init__(pos, color, board)
        img_path = 'data/imgs/' + color[0] + '_queen.png'
        self.img = pygame.image.load(img_path)
        self.img = pygame.transform.scale(self.img, (board.tile_width - 20, board.tile_height - 20))
        self.notation = 'Q'

    def get_possible_moves(self, board):
        output = []
        moves_north = []
        for y in range(self.y)[::-1]:
            moves_north.append(board.get_square_from_pos(
                (self.x, y)
            ))
        output.append(moves_north)
        moves_ne = []
        for i in range(1, 8):
            if self.x + i > 7 or self.y - i < 0:
                break
            moves_ne.append(board.get_square_from_pos(
                (self.x + i, self.y - i)
            ))
        output.append(moves_ne)
        moves_east = []
        for x in range(self.x + 1, 8):
            moves_east.append(board.get_square_from_pos(
                (x, self.y)
            ))
        output.append(moves_east)
        moves_se = []
        for i in range(1, 8):
            if self.x + i > 7 or self.y + i > 7:
                break
            moves_se.append(board.get_square_from_pos(
                (self.x + i, self.y + i)
            ))
        output.append(moves_se)
        moves_south = []
        for y in range(self.y + 1, 8):
            moves_south.append(board.get_square_from_pos(
                (self.x, y)
            ))
        output.append(moves_south)
        moves_sw = []
        for i in range(1, 8):
            if self.x - i < 0 or self.y + i > 7:
                break
            moves_sw.append(board.get_square_from_pos(
                (self.x - i, self.y + i)
            ))
        output.append(moves_sw)
        moves_west = []
        for x in range(self.x)[::-1]:
            moves_west.append(board.get_square_from_pos(
                (x, self.y)
            ))
        output.append(moves_west)
        moves_nw = []
        for i in range(1, 8):
            if self.x - i < 0 or self.y - i < 0:
                break
            moves_nw.append(board.get_square_from_pos(
                (self.x - i, self.y - i)
            ))
        output.append(moves_nw)
        return output

Code for King.py:

# /* King.py

import pygame
from data.classes.Piece import Piece

class King(Piece):
    def __init__(self, pos, color, board):
        super().__init__(pos, color, board)
        img_path = 'data/imgs/' + color[0] + '_king.png'
        self.img = pygame.image.load(img_path)
        self.img = pygame.transform.scale(self.img, (board.tile_width - 20, board.tile_height - 20))
        self.notation = 'K'

    def get_possible_moves(self, board):
        output = []
        moves = [
            (0,-1), # north
            (1, -1), # ne
            (1, 0), # east
            (1, 1), # se
            (0, 1), # south
            (-1, 1), # sw
            (-1, 0), # west
            (-1, -1), # nw
        ]
        for move in moves:
            new_pos = (self.x + move[0], self.y + move[1])
            if (
                new_pos[0] < 8 and
                new_pos[0] >= 0 and 
                new_pos[1] < 8 and 
                new_pos[1] >= 0
            ):
                output.append([
                    board.get_square_from_pos(
                        new_pos
                    )
                ])
        return output

    def can_castle(self, board):
        if not self.has_moved:
            if self.color == 'white':
                queenside_rook = board.get_piece_from_pos((0, 7))
                kingside_rook = board.get_piece_from_pos((7, 7))
                if queenside_rook != None:
                    if not queenside_rook.has_moved:
                        if [
                            board.get_piece_from_pos((i, 7)) for i in range(1, 4)
                        ] == [None, None, None]:
                            return 'queenside'
                if kingside_rook != None:
                    if not kingside_rook.has_moved:
                        if [
                            board.get_piece_from_pos((i, 7)) for i in range(5, 7)
                        ] == [None, None]:
                            return 'kingside'
            elif self.color == 'black':
                queenside_rook = board.get_piece_from_pos((0, 0))
                kingside_rook = board.get_piece_from_pos((7, 0))
                if queenside_rook != None:
                    if not queenside_rook.has_moved:
                        if [
                            board.get_piece_from_pos((i, 0)) for i in range(1, 4)
                        ] == [None, None, None]:
                            return 'queenside'
                if kingside_rook != None:
                    if not kingside_rook.has_moved:
                        if [
                            board.get_piece_from_pos((i, 0)) for i in range(5, 7)
                        ] == [None, None]:
                            return 'kingside'

    def get_valid_moves(self, board):
        output = []
        for square in self.get_moves(board):
            if not board.is_in_check(self.color, board_change=[self.pos, square.pos]):
                output.append(square)
        if self.can_castle(board) == 'queenside':
            output.append(
                board.get_square_from_pos((self.x - 2, self.y))
            )
        if self.can_castle(board) == 'kingside':
            output.append(
                board.get_square_from_pos((self.x + 2, self.y))
            )
        return output

Let’s finish the game by adding code in main.py that runs our whole game:

import pygame

from data.classes.Board import Board

pygame.init()

WINDOW_SIZE = (600, 600)
screen = pygame.display.set_mode(WINDOW_SIZE)

board = Board(WINDOW_SIZE[0], WINDOW_SIZE[1])

def draw(display):
	display.fill('white')
	board.draw(display)
	pygame.display.update()


if __name__ == '__main__':
	running = True
	while running:
		mx, my = pygame.mouse.get_pos()
		for event in pygame.event.get():
			# Quit the game if the user presses the close button
			if event.type == pygame.QUIT:
				running = False
			elif event.type == pygame.MOUSEBUTTONDOWN: 
       			# If the mouse is clicked
				if event.button == 1:
					board.handle_click(mx, my)
		if board.is_in_checkmate('black'): # If black is in checkmate
			print('White wins!')
			running = False
		elif board.is_in_checkmate('white'): # If white is in checkmate
			print('Black wins!')
			running = False
		# Draw the board
		draw(screen)

As you see above, we had screen and board variable, which has pretty similar arguments but not really.

The screen handles the rendering of the chess board on the screen so we can see what’s happening in the board. The code pygame.display.set_mode(WINDOW_SIZE) creates the game window.

While we use the board for making and handling tiles, tile positions, and what piece a chess square has. As you remember, in the Board class code, we give it two arguments: the game window’s length and width.

To keep the game running, we give it a while loop that runs as long as the value of running is True.

The mx, my = pygame.mouse.get_pos() locates the current position of your mouse as long as it’s inside the game window. If you add print(mx, my) below this code, you’ll see the current mouse position, and its value changes every time you hover it inside the window.

The event.type == pygame.MOUSEBUTTONDOWN catches every click you make. To identify if a player is making a move, every time it catches a player doing a click, the current position of the mouse we get from pygame.mouse.get_pos() will be sent in Board.handle_click(), and process your click back there.

Ok, now let’s try this game. If it’s working so in your terminal, move to the directory where our Main.py file was saved, then run the Main.py. Once you run the file, the game will start immediately:

Start clicking on the pieces that can move, and you’ll see available moves:

Conclusion

To simplify it, always remember that the chess game has two main parts, the board, and the pieces.

The board is in charge of every tile’s name & position and the rules of the game, while the piece classes take care of moves and attacks for every piece.

To make the board, you should have a Square class that creates the handling chess tiles that also mind the piece it contains, and another class called Board, which contains the game rules. We also need to do class for every chess piece, from the Pawn to the King. And that’s how you make a chess game with Python using only classes and pygame!

You can check the complete code here.

Learn also: How to Build a GUI Voice Recorder App in Python

Happy coding ♥

View Full Code

Read Also

How to Make a Drawing Program in Python

How to Make a Planet Simulator with PyGame in Python

How to Make Buttons in PyGame

Comment panel

Коркунова Наталья Ивановна

Python очень обширый язык программирования, на котором можно создать бесконечное множество разнообразных проектов. Свой первый проект я решил посвятить шахматам – одной из древнейших настольных игр с достаточно простыми правилами но в то же время заставляющей человека думать и планировать.

Актуальность выбранной темы проекта связана с актуальностью профессионального изучения информатики и программирования в современном мире.

Новизна проекта заключается в сочетании теории и практики, использовании различных ресурсов для написания кода.

Цель проекта – Создать графический интерфейс на основе PyQt для игры в шахматы для 2 игроков

  1. Реализовать классы фигур, механики передвижения.
  2. Создать поле для игры, механику атаки
  3. Реализовать таймер, обработку событий мыши.
  4. С помощью PyQt создаем графический интерфейс

Все фигуры имеют некоторые одинаковые механики, поэтому для начала создадим базовый класс, от которого будем наследовать другие.

Если представить доску в виде матрицы, то можно определять местоположение фигур с помощью координат х и у.

Пешка может двигаться на 1 клетку по вертикали (вверх если белая и вниз если черная), а также на 2 клетки, если её ещё ни разу не двигали. Исходя из этих данных создаем класс:

def can_move ( self , board , row , col , row1 , col1):
# Пешка может ходить только по вертикали
# «взятие на проходе» не реализовано
if col != col1:
return False

if not self .is_path_clear(board , row , col , row1 , col1):
return False

# Пешка может сделать из начального положения ход на 2 клетки
# вперёд, поэтому поместим индекс начального ряда в start_row.
if self .color == WHITE:
direction = 1
start_row = 1
else :
direction = — 1
start_row = 6

# ход на 1 клетку
if row + direction == row1:
return True

# ход на 2 клетки из начального положения
if row == start_row and row + 2 * direction == row1:
return True

def can_attack ( self , board , row , col , row1 , col1):
direction = 1 if ( self .color == WHITE) else — 1
return (row + direction == row1
and (col + 1 == col1 or col — 1 == col1))

Эта фигура может двигаться только по вертикали и горизонтали, т.е. должны совпадать координаты до передвижения и после лтбо по вертикали либо по горизонтали. Зная это, реализуем класс:

def can_move ( self , board , row , col , row1 , col1):
# Невозможно сделать ход в клетку, которая не лежит в том же ряду
# или столбце клеток.
if row != row1 and col != col1:
return False
if not self .is_path_clear(board , row , col , row1 , col1):
return False

Конь двигается буквой «Г», т.е. на 2 по вертикали и 1 по горизонтали и наоборот. При этом направление не важно, значит нужно использовать ф-цию abs(), т.е. модуль.

class Knight(Piece):
ch = ‘N’

def can_move ( self , board , row , col , row1 , col1):
# Конь двигается буквой Г (2 вертикально, 1 горизонтально)
if abs (row — row1) == 2 and abs (col — col1) == 1 :
return True
# Конь двигается буквой Г (1 вертикально, 2 горизонтально)
if abs (row — row1) == 1 and abs (col — col1) == 2 :
return True

Слон двигается по диагонали т.е. его смещение по вертикали должно быть равно смещению по горизонтали:

class Bishop(Piece):
ch = ‘B’

def can_move ( self , board , row , col , row1 , col1):

if not self .is_path_clear(board , row , col , row1 , col1):
return False

# Слон двигается по диагонали
# Смещение по строка должно равняться смещению по столбцам
if abs (row — row1) == abs (col — col1):
return True

Королева двигается по всем сторонам (по горизонтали, вертикали, диагонали). Копируем эти механики из слона и ладьи и получаем:

class Queen(Piece):
ch = ‘Q’

def can_move ( self , board , row , col , row1 , col1):

if not self .is_path_clear(board , row , col , row1 , col1):
return False

# Ферзь двигается вертикально
if col == col1:
return True

# Ферзь двигается горизонтально
if row == row1:
return True

# Ферзь двигается по диагонали
# Смещение по строка должно равняться смещению по столбцам
if abs (row — row1) == abs (col — col1):
return True

Король двигается в любую сторону на расстояние 1:

def can_move ( self , board , row , col , row1 , col1):

if not self .is_path_clear(board , row , col , row1 , col1):
return False

# Король двигается в любую клетку с рассотоянием равным 1
if max ( abs (row — row1) , abs (col — col1)) == 1 :
return True

А теперь необходимо реализовать некоторые особые механики игры, например «Превращение пешки» и «Рокировка».

Когда пешка доходит до противоположного конца доски она может стать Ферзем, Ладьей, Слоном или Конем. Для этого мы можем просто изменить класс фигуры, в том месте, где стоит пешка.

Для рокировки нам нужно знать: двигались ли король и ладья, есть ли между ними фигуры, приведет ли рокировка к шаху короля. От этого будет зависеть успешность рокировки или её невозможность.

Представим доску в виде матрицы на координатной оси. Тогда расположение фигур на доске будет записано в двухмерном списке. Записыаем стартовые значения матрицы:

self .field = [[ None ] * 8 for _ in range ( 8 )]
self .field[ 0 ] = [
Rook(WHITE) , Knight(WHITE) , Bishop(WHITE) , Queen(WHITE) ,
King(WHITE) , Bishop(WHITE) , Knight(WHITE) , Rook(WHITE)
]
self .field[ 1 ] = [
Pawn(WHITE) , Pawn(WHITE) , Pawn(WHITE) , Pawn(WHITE) ,
Pawn(WHITE) , Pawn(WHITE) , Pawn(WHITE) , Pawn(WHITE)
]
self .field[ 6 ] = [
Pawn(BLACK) , Pawn(BLACK) , Pawn(BLACK) , Pawn(BLACK) ,
Pawn(BLACK) , Pawn(BLACK) , Pawn(BLACK) , Pawn(BLACK)
]
self .field[ 7 ] = [
Rook(BLACK) , Knight(BLACK) , Bishop(BLACK) , Queen(BLACK) ,
King(BLACK) , Bishop(BLACK) , Knight(BLACK) , Rook(BLACK)
]

Цвет фигуры определяют константы WHITE и BLACK.

Каждая клетка имеет свое значение, так белая пешка обозначена как «wP». В дальнейшем это обозначение определит графическую модель фигурки на доске.

В этом классе создаем ф-цию проверки возможности движения:

def check_move ( self , row: int , col: int , row1: int , col1: int ):
«»»Проверка перемещения фигуру из точки (row, col) в точку (row1, col1).
Если перемещение возможно, метод выполнит его и вернет True.
Если нет — вернет False»»»
check = self .check() # Есть ли шах к перед ходом
if not correct_coords(row , col) or not correct_coords(row1 , col1):
return False
if row == row1 and col == col1:
return False # нельзя пойти в ту же клетку
piece = self .field[row][col]
target = self .field[row1][col1]
if piece is None :
return False
if piece.get_color() != self .color:
return False

if isinstance (piece , King) and self .is_under_attack(row1 , col1 , opponent( self .current_player_color())):
return False

if not (piece.can_move( self .field , row , col , row1 , col1) and target is None ) and
not (piece.can_attack( self .field , row , col , row1 , col1) and
(target is not None and not isinstance (target , King)
and target.get_color() == opponent( self .current_player_color()))):
return False

old_field = [x.copy() for x in self .field] # Сохранить поле
self .field[row][col] = None # Снять фигуру.
self .field[row1][col1] = piece # Поставить на новое место.
if check and self .check(): # В результате хода избежать шаха не удалось
# Возвращаемся в исходное состояние
self .field = old_field
return False
# Возвращаемся в исходное состояние
self .field = old_field
return True

Ф-ция проверяет сразу все факторы: выход за пределы доски, походила ли фигурка на свое же место, приведет ли ход короля к его шаху или мату и т.д.

Реализуем ф-цию движения в которой используется ф-ция проверки:

def move_piece ( self , row , col , row1 , col1):
«»»Переместить фигуру из точки (row, col) в точку (row1, col1).
Если перемещение возможно, метод выполнит его и вернет True.
Если нет — вернет False»»»
if self .check_move(row , col , row1 , col1):
piece = self .field[row][col]
self .field[row][col] = None # Снять фигуру.
piece.moved()
self .field[row1][col1] = piece # Поставить на новое место.
if self .check_promotion(piece , row1):
self .promotion = True
self .color = opponent( self .color)
self .change = True
return True
return False

Также здесь реализуем механику атаки:

def is_under_attack ( self , row , col , color):
for row1 in range ( 8 ):
for col1 in range ( 8 ):
if self .field[row1][col1] is not None :
if self .field[row1][col1].get_color() == color and
self .field[row1][col1].can_attack( self .field , row1 , col1 , row , col):
return True
return False

Для короля мы должны реализовать «Шах» и «Мат». Это довольно таки сложно из-за того, что у короля пропадает много возможностей, например «Рокировка». Поэтому я все ещё не полностью смог воссоздать эту механику из шахмат. Но все же в случае шаха игроку будут недоступны ходы, которые приведут к мату.

Таймер и обработка событий мыши

С помощью класса QtTimer от PyQt реализуем таймер для каждого игрока с механикой смены после окончания хода. Также с помощью механизма слотов и сигналов PyQt мы сможем обрабатывать события мыши для удобного взаимодействия с графическим интерфейсом.

Реализация графического интерфейса, пожалуй, одна из самых сложных задач проекта, так как PyQt я знаю ещё очень поверхностно. Но что есть – то есть. Создаем модели фигурок, а также модели иконок и изображение поля. Настраиваем механику обработки мыши под границы моделей клеток и иконок.

Дополняем последние детали в виде кнопок «Новая игра» и «Выйти».

Проект завершен, создан рабочий вариант шахмат.

Во время разработки проекта я узнал о многих механиках программирования, получил знания о классах объектов, познакомился с QT. Этот проект ждет ещё множество новых механик и нововведений: добавление ИИ, реализация более сложных механик, которыми пользуются проффесиональные игроки(прим.: «Взятие на проходе»). В дальнейшем я не собираюсь останавливаться на этом проекте, так как меня ждет ещё множество нереализованных идей, ждущих своего часа.

Machine Learning and Chess

In late 2020, Netflix released the Queen’s Gambit, a TV show taking place in the 1950’s where we follow the journey of a young women thriving to become the best chess player.

However, unlike the protagonist Beth Harmon, chess isn’t really my strongest suit. So I did the next best thing by building my own python chess engine to compensate my poor chess skills.

Episode I: Strategies

When it comes to building any board game engine, there are multiple strategies:

  • Reinforcement Learning
  • Minimax
  • ML model trained on human knowledge

I opted for an hybrid of the second and third strategies: As a Data scientist, I am fond of working with large datasets and machine learning models. Also, despite being a little slower, Minimax is a quite reliable algorithm and does a good job at capturing enemy pieces. Reinforcement learning is simply too complex.

My strategy is simple:

  • First of all, using the machine learning model, the engine will dismiss 50% of the possible moves given a board. It does so by finding the probability of a move being a ‘good move’. All the moves are sorted according to this probability and only the 50th percentile remains.
  • Secondly, the engine will perform the Minimax algorithm on the remaining moves. Depending on the complexity of the board, the search tree will go to a certain depth.

Episode II: Data

In order to make accurate predictions, we need a lot of data. For this project, I will be using a dataset that I found on Kaggle. This dataset is a collection of thousands of games played by chess grand masters in the .pgn format (portable game notation).

The next step is to extract all the moves from all the games and label them as good or bad.

In my opinion, a ‘good move’ is a move that the winner played at some point during the game. A ‘bad move’ is a legal move that the winner chose not to play.

This python script got the job done leaving me with a brand new .csv dataset.

Создаем несложный шахматный ИИ: 5 простых этапов

Перевели для вас статью Лори Хартикка (Lauri Hartikka) о создании простейшего ИИ для шахмат. Она написана еще в 2017 году, но базовые принципы остались теми же. Все файлы, которые использовал Лори, тоже доступны.

Простой искусственный интеллект, который умеет играть в шахматы, можно создать на базе четырех концепций:

  1. 1. Перемещение;
  2. 2. Оценка доски; ; . На каждом этапе работы с алгоритмом будет использоваться одна из них, это позволит постепенно совершенствовать игровые способности ИИ.

Напоминаем: для всех читателей «Хабра» — скидка 10 000 рублей при записи на любой курс Skillbox по промокоду «Хабр».

Готовый исходный код можно найти на GitHub.

I’m having trouble beating a chess program I wrote.

Not sure if I’m a bad player or the algorithm is decent.

— Lauri Hartikka (@lhartikk) March 28, 2017

Этап 1. Визуализация шахматной доски с генерацией ходов

На этом этапе мы будем использовать библиотеки chess.js для генерации ходов и chessboard.js для визуализации доски. Библиотека, которая отвечает за генерацию ходов, позволяет применять все шахматные правила, так что мы можем рассчитывать каждое действие для конкретного расположения фигур.


При клике на картинке она откроется в полном разрешении.

Работа с этими библиотеками позволяет сконцентрироваться на главной задаче — поиске и создании алгоритма, который позволяет найти оптимальный ход. Работу начинаем с написания функции, которая возвращает случайный ход из списка всех возможных.

Несмотря на то, что алгоритм не является идеальным игроком в шахматы, для большинства игроков его уровня этого будет вполне достаточно.

Этап 2. Оценка позиции

Теперь давайте разберемся, у какой стороны преимущество в том или ином положении. Самый простой путь — подсчитать относительную силу фигур на доске, это можно сделать при помощи таблицы.

Используя функцию оценки, мы получаем возможность создать алгоритм, который выбирает ход с максимальной оценкой.

В принципе, уровень прежний, но алгоритм уже может взять чужую фигуру, когда такая возможность есть.

Черные получили возможность брать белые фигуры. (Исходники и игра здесь).

Этап 3. Дерево поиска с минимакс

После этого мы создаем дерево поиска. Теперь программа может выбрать из него лучший ход. Это делается при помощи минимакс-алгоритма.

Здесь рекурсивное дерево с отображением всех возможных ходов анализируется до заданной глубины. Позиция же оценивается по листьям нашего дерева.

Далее мы возвращаем минимальное или максимальное значение потомка в родительский узел. Все зависит от того, ход какой стороны сейчас просчитывается. Другими словами, результат максимизируется или минимизируется на каждом из уровней.


Здесь лучшим ходом для белых является b2-c3, поскольку он гарантирует, что игрок доберется до позиции с оценкой -50.

С минимакс-алгоритмом наш ИИ уже стал понимать базовую тактику шахмат.

Минимакс с глубиной 2 (Исходники и игра здесь)

Стоит отметить, что эффективность минимакс-алгоритма увеличивается с глубиной поиска. За это отвечает следующий этап.

Этап 4. Альфа-бета-отсечения

Это метод оптимизации минимакс-алгоритма, дающий возможность игнорировать некоторые ветви в дереве поиска. А это позволяет увеличить глубину поиска, затрачивая прежний объем ресурсов.

Альфа-бета-отсечение основано на ситуации, когда мы можем остановить оценку определенной ветви, если обнаруживается, что новый ход приведет к худшей ситуации, чем та, которую мы видели при оценке предыдущего.

На результат минимакса оптимизация не влияет, но все начинает работать быстрее.

Этот алгоритм гораздо более эффективен в том случае, если сначала проверить пути, ведущие к хорошим ходам.


Изображение демонстрирует ходы, которые становятся ненужными в процессе использования альфа-бета-отсечения.

Как видите, с альфа-бета-отсечением минимакс оптимизируется, и весьма значительно.


Количество позиций, которые требуется оценить в случае поиска с глубиной 4 и начальной позицией, которая изображена выше. (исходники и игра доступны здесь)

Этап 5. Улучшенная функция оценки

Изначальная функция оценки достаточно простая, поскольку она просто считает очки фигур, находящихся на доске. Для ее оптимизации можно учитывать положение фигур. К примеру, если разместить коня в центре доски, то он становится дороже — спектр доступных ходов для этой фигуры расширится.

На этом этапе мы будем работать с несколько видоизмененной версией квадратных таблиц, изначально описанной в вики Chess Programming.

И теперь наш алгоритм играет уже весьма неплохо, конечно, по сравнению со средним игроком.

Исходники и игра доступны здесь

Заключение

Достоинством предложенного алгоритма является то, что он не делает совсем уж глупых ошибок. Конечно, стратегию здесь сложно назвать совершенной, но тем не менее.

В предыдущей статье я разработал программу игры в шахматы за один день, используя Python.

Теперь я решил поэкспериментировать, чтобы значительно сократить это время, используя chatGPT для генерации кода. ChatGPT — это чат-бот, основанный на GPT3 от OpenAI, о котором сейчас говорят все.

Цель этого эксперимента — проверить способности написания кода chatGPT.

Давайте посмотрим на диалог, который у меня был с chatGPT

Вопрос: Давай вместе напишем программу шахмат на Python

Ответ: Конечно, это звучит очень интересно! Прежде чем мы начнём, я должен упомянуть, что написание полнофункциональной шахматной программы является нетривиальной задачей и, вероятно, потребует значительного количества времени и усилий. Однако мы, безусловно, можем начать с обсуждения некоторых основных концепций и идей, которые были бы задействованы в таком проекте.

Для начала нам нужно было бы представить состояние шахматной доски в нашей программе. Это можно было бы сделать с помощью двумерного массива, где каждый элемент массива представляет собой один квадрат на шахматной доске. Нам нужно было бы сохранить тип фигуры, которая занимает каждый квадрат, а также любую другую соответствующую информацию, такую как переместилась ли фигура, угрожают ли ей какие-либо другие фигуры и т.д.

Как только у нас появится способ представить состояние шахматной доски, нам нужно будет внедрить правила шахмат. Это включало бы такие вещи, как обеспечение того, чтобы фигуры двигались только теми способами, которые им разрешены (например, слон может двигаться только по диагонали), предотвращение совершения игроками незаконных ходов (таких как перемещение фигуры на квадрат, который занят одной из их собственных фигур), и обработка специальных ходов, таких как рокировка и взятие на проходе.

Далее нам нужно было бы реализовать логику игры, которая включала бы отслеживание того, чья очередь, проверку окончания игры (например, когда игрок ставит мат) и обработку других аспектов игры, таких как продвижение пешки и захват фигур.

Наконец, нам нужно было бы реализовать какой-то пользовательский интерфейс, такой как интерфейс командной строки или графический пользовательский интерфейс, чтобы позволить игрокам взаимодействовать с программой и делать свои ходы.

В целом, при написании шахматной программы необходимо учитывать множество различных аспектов, и для её правильного выполнения потребуется хорошее понимание правил шахмат и тщательное планирование. Тем не менее, я думаю, что это был бы сложный и полезный проект для работы, и я с нетерпением жду возможности услышать больше о ваших идеях о том, как подойти к нему!

Вопрос: Начни с генерации кода шахматной доски

Ответ: Конечно, мы можем начать с внедрения шахматной доски. Как я упоминал ранее, одним из способов сделать это было бы использование двумерного массива, где каждый элемент массива представляет собой один квадрат на шахматной доске. Вот один из возможных способов сделать это:

# Define a constant for the size of the chess board
BOARD_SIZE = 8

# Create an empty array to represent the chess board
board = [[None for i in range(BOARD_SIZE)] for j in range(BOARD_SIZE)]

# Populate the array with the initial positions of the pieces
board[0][0] = «Rook»
board[0][1] = «Knight»
board[0][2] = «Bishop»
board[0][3] = «Queen»
board[0][4] = «King»
board[0][5] = «Bishop»
board[0][6] = «Knight»
board[0][7] = «Rook»

for i in range(BOARD_SIZE):
board[1][i] = «Pawn»
board[6][i] = «Pawn»

board[7][0] = «Rook»
board[7][1] = «Knight»
board[7][2] = «Bishop»
board[7][3] = «Queen»
board[7][4] = «King»
board[7][5] = «Bishop»
board[7][6] = «Knight»
board[7][7] = «Rook»

# Print out the resulting board
for i in range(BOARD_SIZE):
for j in range(BOARD_SIZE):
print(board[i][j], end=» «)
print()

Этот код создаёт массив 8×8 под названием board и заполняет его начальными позициями фигур. Затем он выводит получившуюся доску, которая должна выглядеть примерно так:

Rook Knight Bishop Queen King Bishop Knight Rook
Pawn Pawn Pawn Pawn Pawn Pawn Pawn Pawn
None None None None None None None None
None None None None None None None None
None None None None None None None None
None None None None None None None None
Pawn Pawn Pawn Pawn Pawn Pawn Pawn Pawn
Rook Knight Bishop Queen King Bishop Knight Rook

Конечно, это всего лишь один из возможных способов представления шахматной доски, и есть много других способов, которыми мы могли бы это сделать. Например, вместо того, чтобы использовать строки для представления типов фрагментов, мы могли бы использовать целые числа или другие типы данных для представления различных типов фрагментов. Кроме того, мы могли бы хранить дополнительную информацию о каждой детали, такую как её цвет, перемещалась ли она и так далее.

Как только у нас будет способ представить шахматную доску, мы сможем перейти к реализации шахматных правил. Это будет включать в себя написание кода для обработки таких вещей, как перемещение фигур, проверка на наличие незаконных ходов и обработка специальных ходов, таких как рокировка и взятие на проходе.

Вопрос: напиши функцию, которая генерирует все возможные для передвижения позиции

Ответ: Конечно, мы можем написать функцию, которая генерирует все возможные ходы для данной позиции на шахматной доске. Чтобы сделать это, нам нужно будет рассмотреть правила шахмат, которые диктуют, как может двигаться фигура каждого типа. Вот один из возможных способов реализации этой функции:

def get_possible_moves(board, x, y):
# This function will return a list of all possible moves that can be made
# from the given position (x, y) on the given board.

# First, we need to check what type of piece is located at the given position
piece = board[x][y]
if piece is None:
# If there is no piece at the given position, there are no possible moves
return []

# Next, we need to handle the different types of pieces and generate the
# list of possible moves for each type
if piece == «Pawn»:
# Pawns can move forward one square, unless there is another piece
# blocking their path. They can also capture pieces that are located
# on the squares diagonally in front of them.
moves = []
if board[x+1][y] is None:
moves.append((x+1, y))
if y > 0 and board[x+1][y-1] is not None:
moves.append((x+1, y-1))
if y < BOARD_SIZE-1 and board[x+1][y+1] is not None:
moves.append((x+1, y+1))

elif piece == «Rook»:
# Rooks can move any number of squares horizontally or vertically,
# but they cannot move through other pieces.
moves = []
for i in range(x+1, BOARD_SIZE):
if board[i][y] is None:
moves.append((i, y))
else:
break
for i in range(x-1, -1, -1):
if board[i][y] is None:
moves.append((i, y))
else:
break
for j in range(y+1, BOARD_SIZE):
if board[x][j] is None:
moves.append((x, j))
else:
break
for j in range(y-1, -1, -1):
if board[x][j] is None:
moves.append((x, j))
else:
break

elif piece == «Knight»:
# Knights can move in an «L» shape, either two squares horizontally
# and one square vertically, or two squares vertically and one
# square horizontally. They can move over other pieces.
moves = []
if x < BOARD_SIZE-2 and y > 0:
moves.append((x+2, y-1))
if x < BOARD_SIZE-2 and y < BOARD_SIZE-1:
moves.append((x+2, y+1))
if x > 1 and y > 0:
moves.append((x-2, y-1))
if x > 1 and y < BOARD_SIZE-1:
moves.append((x-2, y+1))
if x < BOARD_SIZE-1

Вопрос: что с остальными фигурами?

Ответ: В коде, который я предоставил ранее, я включил только правила перемещения пешек, ладей и коней. Правила для других фигур (слона, ферзя и короля) аналогичны, но немного отличаются. Вот как может выглядеть остальная часть кода:

elif piece == «Bishop»:
# Bishops can move any number of squares diagonally, but they cannot
# move through other pieces.
moves = []
for i in range(1, BOARD_SIZE):
if x+i < BOARD_SIZE and y+i < BOARD_SIZE and board[x+i][y+i] is None:
moves.append((x+i, y+i))
else:
break
for i in range(1, BOARD_SIZE):
if x-i >= 0 and y+i < BOARD_SIZE and board[x-i][y+i] is None:
moves.append((x-i, y+i))
else:
break
for i in range(1, BOARD_SIZE):
if x+i < BOARD_SIZE and y-i >= 0 and board[x+i][y-i] is None:
moves.append((x+i, y-i))
else:
break
for i in range(1, BOARD_SIZE):
if x-i >= 0 and y-i >= 0 and board[x-i][y-i] is None:
moves.append((x-i, y-i))
else:
break

elif piece == «Queen»:
# Queens can move any number of squares horizontally, vertically,
# or diagonally, but they cannot move through other pieces.
moves = []
for i in range(1, BOARD_SIZE):
if x+i < BOARD_SIZE and board[x+i][y] is None:
moves.append((x+i, y))
else:
break
for i in range(1, BOARD_SIZE):
if x-i >= 0 and board[x-i][y] is None:
moves.append((x-i, y))
else:
break
for j in range(1, BOARD_SIZE):
if y+j < BOARD_SIZE and board[x][y+j] is None:
moves.append((x, y+j))
else:
break
for j in range(1, BOARD_SIZE):
if y-j >= 0 and board[x][y-j] is None:
moves.append((x, y-j))
else:
break
for i in range(1, BOARD_SIZE):
if x+i < BOARD_SIZE and y+i < BOARD_SIZE and board[x+i][y+i] is None:
moves.append((x+i, y+i))
else:
break
for i in range(1, BOARD_SIZE):
if x-i >= 0 and y+i < BOARD_SIZE and board[x-i][y+i] is None:
moves.append((x-i, y+i))
else:
break
for i in

Вопрос: пожалуйста, продолжай

Ответ: Конечно! Вот остальная часть кода для функции get_possible_moves():

range(1, BOARD_SIZE):
if x+i < BOARD_SIZE and y-i >= 0 and board[x+i][y-i] is None:
moves.append((x+i, y-i))
else:
break
for i in range(1, BOARD_SIZE):
if x-i >= 0 and y-i >= 0 and board[x-i][y-i] is None:
moves.append((x-i, y-i))
else:
break

elif piece == «King»:
# Kings can move one square in any direction (horizontally, vertically,
# or diagonally), but they cannot move into check (i.e. they cannot
# move into a square that is threatened by an enemy piece).
moves = []
if x > 0:
moves.append((x-1, y))
if x < BOARD_SIZE-1:
moves.append((x+1, y))
if y > 0:
moves.append((x, y-1))
if y < BOARD_SIZE-1:
moves.append((x, y+1))
if x > 0 and y > 0:
moves.append((x-1, y-1))
if x > 0 and y < BOARD_SIZE-1:
moves.append((x-1, y+1))
if x < BOARD_SIZE-1 and y > 0:
moves.append((x+1, y-1))
if x < BOARD_SIZE-1 and y < BOARD_SIZE-1:
moves.append((x+1, y+1))

# Once we have generated the list of possible moves, we can return it
return moves

Этот код обрабатывает правила перемещения фигур каждого типа и генерирует список возможных ходов для данной позиции. Например, если мы вызовем get_possible_moves(board, 0, 1), функция вернёт список возможных ходов для коня, который изначально расположен на квадрате (0,1) на шахматной доске. Она должно включать все квадраты, на которые конь может переместиться за один ход, такие как (2,0), (2,2), (1,3), и так далее.

Конечно, это всего лишь один из способов реализации функции get_possible_moves(), и есть много других способов, которыми мы могли бы это сделать. Например, мы могли бы использовать различные структуры данных для представления шахматной доски и возможных ходов, или мы могли бы включить дополнительную информацию в список возможных ходов. Однако я надеюсь, что это даёт вам общее представление о том, как мы могли бы подойти к этой проблеме. Дайте мне знать, если у вас возникнут какие-либо другие вопросы!

Вопрос: можешь ли ты написать функцию, которая генерирует следующий наилучший ход?

Ответ: Можно написать функцию, которая генерирует следующий “лучший” ход для данной позиции на шахматной доске, но важно отметить, что это очень сложная задача для решения, и универсального варианта не существует. На самом деле, даже самые продвинутые шахматные программы в мире не способны последовательно находить абсолютный “лучший” ход в каждой ситуации.

Одним из возможных способов решения этой проблемы было бы использование метода, называемого “minimax” поиском, который является распространенным методом, используемым в игровых программах искусственного интеллекта. Основная его идея заключается в том, чтобы рассмотреть все возможные ходы, которые могут быть сделаны из текущей позиции, а затем для каждого из этих ходов рассмотреть все возможные контрдвижения, которые мог бы сделать противник. Затем этот алгоритм “минимизирует” значение лучшего хода противника, одновременно “максимизируя” значение хода исходного игрока, чтобы найти ход, который с наибольшей вероятностью приведёт к победе (или, по крайней мере, не к проигрышу).

Чтобы реализовать это, нам нужно было бы определить функцию “value”, которая присваивает числовой счёт каждой позиции на шахматной доске. Эта функция учитывала бы такие факторы, как относительная сила фигур каждого игрока, мобильность фигур каждого игрока и так далее. Затем алгоритм minimax будет использовать эту функцию значений для оценки каждого возможного хода и контрдвижения и выбора хода, который приводит к наибольшему общему баллу.

Однако, как я упоминал ранее, это очень сложная для решения проблема, и даже самые продвинутые шахматные программы не всегда способны найти абсолютный “лучший” ход. Кроме того, производительность алгоритма minimax может быть очень низкой, поскольку он должен учитывать каждый возможный ход и контрдвижение, чтобы найти наилучший ход. По этим причинам часто необходимо использовать различные методы оптимизации, такие как альфа-бета-обрезка и таблицы транспозиции, чтобы сделать этот алгоритм более эффективным.

В целом, хотя возможно написать функцию, которая генерирует следующий “лучший” ход для данной позиции на шахматной доске, это сложная задача, которая потребует хорошего понимания игровых методов искусственного интеллекта и тщательного планирования и реализации. Я надеюсь, что это даёт вам общее представление о том, как можно было бы подойти к этой проблеме, но дайте мне знать, если у вас возникнут какие-либо другие вопросы!

Вопрос: можешь ли ты сгенерировать код на python?

Ответ: Конечно, вот пример того, как алгоритм minimax может быть реализован на Python. Имейте в виду, что это всего лишь один из возможных способов реализации алгоритма, и есть много других способов, которыми мы могли бы это сделать. Кроме того, этот код не включает в себя все оптимизации, которые обычно используются в реальных шахматных программах, поэтому он будет не очень эффективен для больших шахматных позиций.

def minimax(board, depth, maximizingPlayer):
# This function will use the minimax algorithm to find the best move for
# the current player. The «board» parameter is the current state of the
# chess board, the «depth» parameter is the depth of the search tree (i.e.
# how many moves to look ahead), and the «maximizingPlayer» parameter
# indicates whether the current player is the maximizing player (True) or
# the minimizing player (False).

# If the search tree has reached the maximum depth, we can evaluate the
# current position using the value function and return that value.
if depth == 0:
return evaluate(board)

# Generate a list of all possible moves that can be made from the current
# position.
moves = get_possible_moves(board)

# If there are no possible moves, the game is over and we can evaluate
# the final position using the value function.
if len(moves) == 0:
return evaluate(board)

# If the current player is the maximizing player, we want to find the
# move that leads to the highest score, so we initialize the bestValue
# variable to a very low value.
if maximizingPlayer:
bestValue = -float(«inf»)

# Loop through all of the possible moves and compute the score for
# each one.
for move in moves:
# Make the move on a copy of the board.
newBoard = make_move(board, move)

# Recursively call the minimax function to find the best move
# for the opponent.
value = minimax(newBoard, depth-1, False)

# Update the bestValue variable if the current move leads to a
# higher score than the previous best move.
bestValue = max(bestValue, value)

# Once we have considered all of the possible moves, we can return
# the best score.
return bestValue

# If the current player is the minimizing player, we want to find the
# move that leads to the lowest score, so we initialize the bestValue
# variable to a very high value.
else:
bestValue = float(«inf»)

# Loop through all of the possible moves and compute the score for
# each one.
for move in moves:
# Make the move on a copy of the board.
newBoard = make_move(board, move)

# Recursively call the minimax function to find the best move
# for the opponent.
value = minimax(newBoard, depth-1, True)

# Update the bestValue variable if the current move leads to a
# lower score than the previous best move.
bestValue = min(bestValue

Вопрос: пожалуйста, продолжай

Ответ: Конечно, вот остальная часть функции minimax():

value)

# Once we have considered all of the possible moves, we can return
# the best score.
return bestValue

# If the current player is the minimizing player, we want to find the
# move that leads to the lowest score, so we initialize the bestValue
# variable to a very high value.
else:
bestValue = float(«inf»)

# Loop through all of the possible moves and compute the score for
# each one.
for move in moves:
# Make the move on a copy of the board.
newBoard = make_move(board, move)

# Recursively call the minimax function to find the best move
# for the opponent.
value = minimax(newBoard, depth-1, True)

# Update the bestValue variable if the current move leads to a
# lower score than the previous best move.
bestValue = min(bestValue, value)

# Once we have considered all of the possible moves, we can return
# the best score.
return bestValue

Этот код использует алгоритм minimax для поиска в пространстве возможных ходов и контр-движений, чтобы найти ход, который с наибольшей вероятностью приведёт к победе (или, по крайней мере, не к проигрышу). Функция evaluate() используется для присвоения числового значения каждой позиции на доске, а функции get_possible_moves() и make_move() используются для генерации и применения возможных ходов.

Опять же, имейте в виду, что это всего лишь один из возможных способов реализации минимаксного алгоритма, и есть много других способов, которыми мы могли бы это сделать. Кроме того, этот код не включает в себя все оптимизации, которые обычно используются в реальных шахматных программах, поэтому он будет не очень эффективен для больших шахматных позиций. Я надеюсь, что это даст вам общее представление о том, как работает алгоритм minimax и как он может быть реализован на Python, но дайте мне знать, если у вас возникнут какие-либо другие вопросы!

Вопрос: можете ли вы сгенерировать для меня функцию оценки?

Ответ: Конечно, вот пример того, как мы могли бы реализовать функцию оценки для шахматной программы на Python:

def evaluate(board):
# This function will evaluate the current position on the chess board and
# return a numerical score. The higher the score, the better the position
# is for the player who is currently moving.

# First, we will compute the material advantage for each player. This
# will give us a basic measure of the relative strength of each player’s
# pieces.
whiteMaterial = 0
blackMaterial = 0
for i in range(BOARD_SIZE):
for j in range(BOARD_SIZE):
piece = board[i][j]
if piece is not None:
if piece.isupper():
whiteMaterial += PIECE_VALUES[piece]
else:
blackMaterial += PIECE_VALUES[piece]

# Next, we will compute the mobility for each player. This will give us
# a measure of how many squares each player’s pieces can move to.
whiteMobility = 0
blackMobility = 0
for i in range(BOARD_SIZE):
for j in range(BOARD_SIZE):
piece = board[i][j]
if piece is not None:
if piece.isupper():
whiteMobility += len(get_possible_moves(board, i, j))
else:
blackMobility += len(get_possible_moves(board, i, j))

# Finally, we will combine the material advantage and mobility scores
# to compute the overall score for the position.
score = whiteMaterial + whiteMobility — blackMaterial — blackMobility

# Return the score for the position.
return score

Этот код использует два фактора для оценки текущего положения на шахматной доске: материальное преимущество каждого игрока (т.е. относительная сила их фигур) и мобильность фигур каждого игрока (т.е. на сколько квадратов они могут переместиться). Эти два фактора объединяются для вычисления общего балла за позицию, который возвращается функцией evaluate().

Конечно, это всего лишь один из возможных способов реализации функции оценки для шахматной программы, и есть много других факторов, которые мы могли бы включить в оценку (таких как структура пешки, безопасность короля и так далее). Кроме того, точные значения, которые мы присваиваем каждому фактору (например, значения для каждого типа шахматной фигуры), могут оказать большое влияние на производительность шахматной программы, поэтому важно тщательно учитывать эти значения и корректировать их по мере необходимости. Я надеюсь, что это даёт вам общее представление о том, как может работать функция оценки, но дайте мне знать, если у вас возникнут какие-либо другие вопросы!

Вопрос: теперь мне нужна функция make_move()

Ответ: Конечно, вот пример того, как мы могли бы реализовать функцию make_move() для шахматной программы на Python:

def make_move(board, move):
# This function will take a chess board and a move, and return a new
# chess board that reflects the move that has been made.

# First, we will make a copy of the original board, so that we can
# modify the copy without affecting the original.
newBoard = copy.deepcopy(board)

# Next, we will extract the source and destination coordinates for the
# move that is being made.
(x1, y1) = move[0]
(x2, y2) = move[1]

# We will then move the piece from the source square to the destination
# square on the new board.
newBoard[x2][y2] = newBoard[x1][y1]
newBoard[x1][y1] = None

# Finally, we will return the new board.
return newBoard

Этот код использует функцию copy.deepcopy() для создания глубокой копии оригинальной шахматной доски, чтобы мы могли изменять копию, не затрагивая оригинал. Затем он использует исходные и конечные координаты выполняемого хода для обновления доски, перемещая фигуру из исходного квадрата в конечный квадрат.

Заключение

Я нахожу результаты впечатляющими. Непростая задача была не только решена с помощью выходных данных кода, но и объяснена соответствующими и корректными текстами. Также были указаны ограничения и возможные расширения.

Что не сработало:

  • словарь PIECE_VALUES должен был быть добавлен
  • необходимо было добавить импортную копию.
  • Сгенерированная функция для создания всех ходов не соответствовала функции minimax , потому что она ожидала все ходы позиции, но созданная функция генерирует только все ходы фигуры позиции.

Однако все эти недостатки также могли быть вызваны тем, что я не задавал достаточно точных вопросов в диалоге. Я думаю, что для эффективного использования этого инструмента нам всем нужно больше практики!

Статья была взята из этого источника:

If you are having difficulties with the other code, I improved it with some minor fixes. If you want to know how to move your pieces, type a2-a4 as an example. Make sure to copy all of the code after this text:

«»»CONVENTIONS:
positions are done row-column from the bottom left and are both numbers.
This corresponds to the alpha-number system in traditional chess while being computationally useful.
They are specified as tuples
«»»
import itertools
import copy
import inspect
WHITE = «white»
BLACK = «black»
gameboard = None

def canSeeKing(kingpos,piecelist, overridegameboard = None):
global gameboard
overridegameboard = overridegameboard or gameboard
for piece,position in piecelist:
if piece.isValid(position,kingpos,piece.Color,overridegameboard):
return True

def isCheck(overridegameboard = None):
global gameboard
#ascertain where the kings are, check all pieces of opposing color against those kings,
# then if either get hit, check if its checkmate
overridegameboard = overridegameboard or gameboard
king = King
kingDict = {}
pieceDict = {BLACK : [], WHITE : []}
for position,piece in overridegameboard.items():
if type(piece) == king:
kingDict[piece.Color] = position
print(piece)
pieceDict[piece.Color].append((piece,position))
#white
if canSeeKing(kingDict[WHITE],pieceDict[BLACK], overridegameboard):
return WHITE
if canSeeKing(kingDict[BLACK],pieceDict[WHITE], overridegameboard):
return BLACK
return False

class Game:
def init(self):
global gameboard
self.playersturn = WHITE
self.message = «Input Your Move»
gameboard = self.gameboard = {}
self.placePieces()
print(«Chess program. Enter moves in algebraic notation separated by space. Example: a2-a4»)
self.main()

def placePieces(self):

    for i in range(0,8):
        self.gameboard[(i,1)] = Pawn(WHITE,uniDict[WHITE][Pawn],1)
        self.gameboard[(i,6)] = Pawn(BLACK,uniDict[BLACK][Pawn],-1)
        
    placers = [Rook, Knight, Bishop, Queen, King, Bishop, Knight, Rook]
    
    for i in range(0,8):
        self.gameboard[(i,0)] = placers[i](WHITE,uniDict[WHITE][placers[i]])
        self.gameboard[((7-i),7)] = placers[i](BLACK,uniDict[BLACK][placers[i]])
    placers.reverse()

    
def main(self):
    
    while True:
        self.printBoard()
        print(self.message)
        self.message = ""
        startpos,endpos = self.parseInput()
        try:
            target = self.gameboard[startpos]
        except:
            self.message = "could not find piece; index probably out of range"
            target = None
            
        if target:
            print("found "+str(target))
            if target.Color != self.playersturn:
                self.message = "This is Not Your Turn"
                continue
            if target.isValid(startpos,endpos,target.Color,self.gameboard):
                hasLegalMoves = False
                for position in self.gameboard:
                    piece = self.gameboard[position]
                    if (piece.Color == self.playersturn):
                        for move in piece.availableMoves(position[0], position[1], self.gameboard):
                            overridegameboard = copy.deepcopy(self.gameboard)
                            overridegameboard[move] = self.gameboard[position]
                            del overridegameboard[position]
                            if (isCheck(overridegameboard) != self.playersturn):
                                hasLegalMoves = True
                                break
                if (not hasLegalMoves):
                    if (isCheck() == self.playersturn) : print("You are in checkmate. " + ({WHITE: BLACK, BLACK: WHITE})[self.playersturn] + " wins!")
                    else : print("Stalemate. Nobody wins!")
                    return
                overridegameboard = copy.deepcopy(self.gameboard)
                overridegameboard[endpos] = self.gameboard[startpos]
                del overridegameboard[startpos]
                if (isCheck(overridegameboard) == self.playersturn) : self.message = "You are not allowed to put yourself in check!"
                else:
                    self.message = "That Move is Allowed"
                    self.gameboard[endpos] = self.gameboard[startpos]
                    del self.gameboard[startpos]
                    Check = isCheck()
                    if (Check):
                        self.message = "Player is in check"
                    if self.playersturn == BLACK:
                        self.playersturn = WHITE
                    else : self.playersturn = BLACK
            else : 
                self.message = "invalid move" + str(target.availableMoves(startpos[0],startpos[1],self.gameboard))
                print(target.availableMoves(startpos[0],startpos[1],self.gameboard))
        else : self.message = "There is no Piece in That Space"
            
def parseInput(self):
    try:
        a,b = input().split('-')
        a = ((ord(a[0])-97), int(a[1])-1)
        b = (ord(b[0])-97, int(b[1])-1)
        print(a,b)
        return (a,b)
    except:
        print("error decoding input. please try again")
        return((-1,-1),(-1,-1))

"""def validateInput(self, *kargs):
    for arg in kargs:
        if type(arg[0]) is not type(1) or type(arg[1]) is not type(1):
            return False
    return True"""
    
def printBoard(self):
    print("  1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |")
    for i in range(0,8):
        print("-"*32)
        print(chr(i+97),end="|")
        for j in range(0,8):
            item = self.gameboard.get((i,j)," ")
            print(str(item)+' |', end = " ")
        print()
    print("-"*32)
        
       
    
"""game class. contains the following members and methods:
two arrays of pieces for each player
8x8 piece array with references to these pieces
a parse function, which turns the input from the user into a list of two tuples denoting start and end points
a checkmateExists function which checks if either players are in checkmate
a checkExists function which checks if either players are in check
a main loop, which takes input, runs it through the parser, asks the piece if the move is valid, 
and moves the piece if it is.
If the move conflicts with another piece, that piece is removed. is check(mate) is run,
and if there is a checkmate, the game prints a message as to who wins.
"""

class Piece:

def __init__(self,color,name):
    self.name = name
    self.position = None
    self.Color = color
def isValid(self,startpos,endpos,Color,gameboard):
    if endpos in self.availableMoves(startpos[0],startpos[1],gameboard,Color):
        return True
    return False
def __repr__(self):
    return self.name

def __str__(self):
    return self.name

def availableMoves(self,x,y,gameboard,Color=None):
    print("ERROR: no movement for base class")
    
def AdNauseum(self,x,y,gameboard, Color, intervals):
    """Repeats the given interval until another piece is run into. 
    if that piece is not of the same color, that square is added and
     then the list is returned"""
    answers = []
    for xint,yint in intervals:
        xtemp,ytemp = x+xint,y+yint
        while self.noConflict(gameboard, Color, xtemp, ytemp):
            #print(str((xtemp,ytemp))+"is in bounds")
            
            target = gameboard.get((xtemp,ytemp),None)
            if target is None: answers.append((xtemp,ytemp))
            elif target.Color != Color: 
                answers.append((xtemp,ytemp))
                break
            else:
                break
            
            xtemp,ytemp = xtemp + xint,ytemp + yint
    return answers
            
def isInBounds(self,x,y):
    "Checks if a position is on the board"
    if x >= 0 and x < 8 and y >= 0 and y < 8:
        return True
    return False

def noConflict(self,gameboard,initialColor,x,y):
    "Checks if a single position poses no conflict to the rules of chess"
    if self.isInBounds(x,y) and (((x,y) not in gameboard) or gameboard[(x,y)].Color != initialColor) : return True
    return False

chessCardinals = [(1,0),(0,1),(-1,0),(0,-1)]
chessDiagonals = [(1,1),(-1,1),(1,-1),(-1,-1)]

def knightList(x,y,int1,int2):
«»»Sepcifically for the rook, permutes the values needed around a position for noConflict tests»»»
return [(x+int1,y+int2),(x-int1,y+int2),(x+int1,y-int2),(x-int1,y-int2),(x+int2,y+int1),(x-int2,y+int1),(x+int2,y-int1),(x-int2,y-int1)]
def kingList(x,y):
return [(x+1,y),(x+1,y+1),(x+1,y-1),(x,y+1),(x,y-1),(x-1,y),(x-1,y+1),(x-1,y-1)]

class Knight(Piece):
def availableMoves(self,x,y,gameboard, Color = None):
if Color is None : Color = self.Color
return [(xx,yy) for xx,yy in knightList(x,y,2,1) if self.noConflict(gameboard, Color, xx, yy)]

class Rook(Piece):
def availableMoves(self,x,y,gameboard ,Color = None):
if Color is None : Color = self.Color
return self.AdNauseum(x, y, gameboard, Color, chessCardinals)

class Bishop(Piece):
def availableMoves(self,x,y,gameboard, Color = None):
if Color is None : Color = self.Color
return self.AdNauseum(x, y, gameboard, Color, chessDiagonals)

class Queen(Piece):
def availableMoves(self,x,y,gameboard, Color = None):
if Color is None : Color = self.Color
return self.AdNauseum(x, y, gameboard, Color, chessCardinals+chessDiagonals)

class King(Piece):
def availableMoves(self,x,y,gameboard, Color = None):
if Color is None : Color = self.Color
return [(xx,yy) for xx,yy in kingList(x,y) if self.noConflict(gameboard, Color, xx, yy)]

class Pawn(Piece):
def init(self,color,name,direction):
self.name = name
self.Color = color
self.direction = direction
def availableMoves(self,x,y,gameboard, Color = None):
if Color is None : Color = self.Color
answers = []
if (x+1,y+self.direction) in gameboard and self.noConflict(gameboard, Color, x+1, y+self.direction) : answers.append((x+1,y+self.direction))
if (x-1,y+self.direction) in gameboard and self.noConflict(gameboard, Color, x-1, y+self.direction) : answers.append((x-1,y+self.direction))
if (x,y+self.direction) not in gameboard and Color == self.Color and self.noConflict(gameboard, Color, x, y+self.direction) : answers.append((x,y+self.direction))# the condition after the and is to make sure the non-0capturing movement is not used in the calculation of checkmate
if (x,y+self.direction2) not in gameboard and Color == self.Color and self.noConflict(gameboard, Color, x, y+self.direction2) : answers.append((x,y+self.direction*2))# the condition after the and is to make sure the non-0capturing movement is not used in the calculation of checkmate
return answers

uniDict = {WHITE : {Pawn : «♙», Rook : «♖», Knight : «♘», Bishop : «♗», King : «♔», Queen : «♕» }, BLACK : {Pawn : «♟», Rook : «♜», Knight : «♞», Bishop : «♝», King : «♚», Queen : «♛» }}

if (name == «main«):
input(«Press the Enter Key to Exit»)

Game()

Cover image for Creating a chess game with Python, pygame and chess (Pt. 1)

Prince

Prince

Posted on Nov 21, 2022

• Updated on Nov 23, 2022



 



 



 



 



 

 

Hey, I’m Prince and I’m going to be walking you through my process of creating a chess game with Python (this is my first project with pygame).
DISCLAIMER: This article is not for beginners but I will make an effort to make it accessible to those with just a little bit of Python knowledge. Some concepts involved here include; OOP and data structures.

The objectives of this program are to create a chess game that can be played with 2 players or against an AI.

Here’s a link to the project on Github, feel free to play around with it or contribute.

I relied heavily on the techniques covered in this article

So firstly create a new folder (for the purposes of this article we will call it chess-game) where you want to store the code and in that folder create a virtual environment (if you are not familiar with virtual environments take a look at this ), activate the virtual environment and install the following packages:

  1. chess
  2. pygame

We need the chess module to handle the chess rules and validations and pygame to make the actual game.

Ok, we are going to split this walkthrough into 3 sections:

  • The pieces, squares and the boards
  • Displaying the board and pieces on the pygame window and
  • Creating an AI player

The pieces, squares and the board

We will create a new package in our code, gui_components. To create a package just create a new folder (in this case gui_components) and in that new folder create a new file __init__.py)
We will also create a new folder in our project directory (chess-game) called skins. This is where we will store the images for our pieces. Feel free to copy the skins directory from the repository

The project should have the following structure:
chess-game/
—|gui_components/
—|skins/

  1. The pieces
    We will create a pieces.py file in our gui_components folder. In this file we will create a Piece class. For now the objects of this class will simply be used to display the image and get the value of the piece based on its notation (in chess the different pieces have notations k for King, q for Queen, r for Rook, b for bishop, n for Knight and p for Pawn) and whether or not it has been captured.
import os
import pygame

class Piece:
    colors_notations_and_values = {
        "w": {
            "p": 1,
            "n": 3,
            "b": 3,
            "r": 5,
            "q": 9,
            "k": 90
        }, 
        "b": {
            "p": -1,
            "n": -3,
            "b": -3,
            "r": -5,
            "q": -9,
            "k": -90
        }
    }

    def __init__(self, name, notation, color, skin_directory="skins/default", is_captured=False) -> None:
        self.name = name
        self.__notation = notation
        self.color = color
        self.skin_directory = skin_directory
        self.set_is_captured(is_captured)

        self.value = self.get_piece_value()

    def get_piece_value(self):
        return Piece.colors_notations_and_values[self.color][self.__notation.lower()]

    def get_piece_color_based_on_notation(notation) -> str:
        """
        The chess module displays black pieces' notations in lowercase and white in uppercase, so we can get the color based on this
        """
        return "w" if notation.isupper() else "b"

    def get_value_from_notation(notation: str, color: str) -> int:
        """
        A class method that gets the corresponding value for a particular notation and color
        """
        return Piece.colors_notations_and_values[color][notation.lower()]

    def set_is_captured(self, is_captured: bool):
        self.__is_captured = bool(is_captured)

    def get_image_path(self):
        """
        Gets the path to the image of the piece based on its notation and 
        whether or not it has been captured
        """
        if not self.__is_captured:
            path = os.path.join(self.skin_directory, self.color, f"{self.__notation.lower()}.png")
        else:
            path = os.path.join(self.skin_directory, self.color, "captured", f"{self.__notation.lower()}.png")

        return path

    def get_image(self):
        """
        Returns a pygame image object from the piece's corresponding image path
        """
        image_path = self.get_image_path()

        if os.path.exists(image_path):
            return pygame.image.load(image_path)
        else:
            raise FileNotFoundError(f"The image was not found in the {image_path}")

    def __str__(self):
        return f"{self.__notation} {self.color}"

    def get_notation(self) -> str:
        """
        Returns the notation of the piece, (pawns' notations are empty strings)
        """
        if self.__notation != 'p':
            return self.__notation.upper()

        return ''

    def __set_notation(self, notation):
        self.__notation = notation

    def promote(self, notation: str):
        """
        Promotes this piece to a piece with the notation notation.
        It is important to note that promotion does not increase the piece's value, 
        just its capabilities
        """
        if self.__notation.lower() != "p":
            raise ValueError("Cannot promote a piece other than a pawn")

        if notation not in ["q", "r", "n", "b"]:
            raise ValueError("Can only promote to queen, rook, bishop or knight pieces")
        self.__set_notation(notation)

Enter fullscreen mode

Exit fullscreen mode

  1. The squares and board
    When creating this game I thought about being able to have a checkers game with it, so the classes in this section kind of reflect that vision. First and foremost, create a new file boards.py. In this file create a Square class (a generic class for squares checkers or chess)
import chess

import pygame

from gui_components.pieces import Piece

class Square(pygame.Rect):
    def __init__(self, left: float, top: float, width: float, height: float, background_color: str, border_color: str, piece: Piece = None) -> None:
        super().__init__(left, top, width, height)
        self.background_color = background_color
        self.border_color = border_color
        self.piece = piece
        self.is_possible_move = False

    def toggle_is_possible_move(self):
        self.is_possible_move = not self.is_possible_move
        return self

    def empty(self):
        self.piece = None

        return self

    def set_is_possible_move(self, value: bool):
        self.is_possible_move = bool(value)
        return self

Enter fullscreen mode

Exit fullscreen mode

Now a square for chess pieces

class ChessSquare(Square):
    def __init__(self, left: float, top: float, width: float, height: float, background_color: str, border_color: str, file_number, rank_number, piece: Piece = None) -> None:
        super().__init__(left, top, width, height, background_color, border_color, piece)
        self.file_number = file_number
        self.rank_number = rank_number
        self.ranks = list( str(i) for i in range(1, 9) )
        self.files = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']

    def get_chess_square(self) -> chess.Square:
        """
        Returns a chess.Square object that corresponds to this one
        """
        return chess.square(self.file_number, self.rank_number)

    def is_identical_to_chess_square(self, square: chess.Square) -> bool:
        """
        Checks if this object corresponds to a chess.Square object
        """
        return (
            self.file_number == chess.square_file(square) and 
            self.rank_number == chess.square_rank(square)
        )

    def get_rank(self) -> str:
        """
        Gets the rank of the object. Ranks are the rows of the board and they range from 1 to 8
        """
        return self.ranks[ self.rank_number ]

    def get_file(self) -> str:
        """
        Gets the file of the object. Files are the columns of the board and range from A to H
        """
        return self.files[ self.file_number ]

    def get_notation(self) -> str:
        """
        Gets the notation of the square object. A squares notation is simply its file and rank
        """
        return f'{self.get_file()}{self.get_rank()}'

Enter fullscreen mode

Exit fullscreen mode

Now for the board. Same as the square we will create 2 board classes although the parent board class doesn’t do much for now. This class will help us keep track of the pieces on our squares, highlight a move made, display the possible moves, get a square that corresponds to particular coordinates and make a move.

class Board(pygame.sprite.Sprite):
    RANKS = [ i+1 for i in range(0, 8) ]
    FILES = [ chr(i) for i in range(65, 65+9) ]

    def __init__(self, number_of_rows, number_of_columns, left, top, width, height, horizontal_padding, vertical_padding, **kwargs) -> None:
        self.left = left
        self.top = top
        self.number_of_rows = number_of_rows
        self.number_of_columns = number_of_columns
        self.width = width
        self.height = height
        self.horizontal_padding = horizontal_padding
        self.vertical_padding = vertical_padding
        self.squares = []

    def create_squares(self):
        pass

class ChessBoard(Board):
    def __init__(
        self, left, top, width, height, 
        horizontal_padding=None, vertical_padding=None, 
        light_square_color: str=(245, 245, 245), dark_square_color: str=(100, 100, 100), 
        previous_square_highlight_color=(186, 202, 43),
        current_square_highlight_color=(246, 246, 105),
        board: chess.Board=None, move_hints=True, **kwargs
    ) -> None:
        super().__init__(
            8, 8, left, top, width, height, 
            horizontal_padding, vertical_padding, **kwargs
        )
        self.__set_square_size()
        self.light_square_color = light_square_color
        self.dark_square_color = dark_square_color
        self.board = board
        self.move_hints = move_hints
        print('The current board is')
        print(self.board)
        self.rect = pygame.Rect(left, top, width, height)

        self.create_squares()

        self.captured_pieces = {
            "w": [],
            "b": []
        }

        # the square the piece that made the latest move came from
        self.previous_move_square = None 
        self.current_move_square = None 

        self.previous_square_highlight_color = previous_square_highlight_color
        self.current_square_highlight_color = current_square_highlight_color

        self.is_flipped = bool(kwargs["flipped"]) if "flipped" in kwargs else False

        # set to True if a pawn has the right to promote and has to choose which piece it wants to promote to
        self.awaiting_promotion = False

        # self.flip()

    def __set_square_size(self):
        self.__square_size = self.height // 8

    @property
    def square_size(self) -> int:
        return self.__square_size


    def get_piece_from_notation(self, notation):
        """
        Returns a piece object based on a particular notation
        """
        if notation != '.':
            piece_color = "b" if notation.islower() else "w"
            notation = notation.lower()
            piece = Piece(name=notation, notation=notation, color=piece_color)

            return piece

        return None

    def get_square_from_chess_square(self, square: chess.Square) -> ChessSquare:
        """
        Returns a Square object that corresponds to a particular chess.Square object
        """
        square_file = chess.square_file(square)
        square_rank = chess.square_rank(square)

        rank = self.squares[ 7 - square_rank ]

        return rank[ square_file ]

    def create_squares(self):
        """
        Creates the squares oon the board and places pieces on them based on the state of the chess.Board object
        """
        string = self.board.__str__()
        ranks_inverted = string.split('n')#[::-1]

        for i in range(self.number_of_rows):
            self.squares.append( [] )

            rank = ranks_inverted[i].split(' ')

            for j in range(self.number_of_columns):
                square = rank[j]

                piece = self.get_piece_from_notation(square)

                color = self.light_square_color if (i+j) % 2 == 0 else self.dark_square_color

                board_square = ChessSquare( 
                    self.left + (j*self.square_size), self.top + (i*self.square_size), self.square_size, 
                    self.square_size, color, self.dark_square_color, j, 7 - i, piece=piece
                )

                self.squares[i].append( board_square )

    def flip(self):
        """
        Changes the coordinates of the squares in essence flipping them
        """
        board_rect = pygame.Rect(self.left, self.top, self.width, self.height)

        for (i, rank) in enumerate(self.squares):
            print(f"Flipping the squares on rank: {8 - i}")
            for (j, square) in enumerate(rank):
                square: ChessSquare = square
                _old = square.__repr__()

                square.x += (7 - j) * self.square_size
                square.y += (7 - i) * self.square_size

                if not square.colliderect(board_rect):
                    print("Square is out of bounds of the board")
                    print(f"The board rectangle is: {board_rect}. The square rectangle is: {square}")

                else:
                    print(f"Square was flipped successfully. Old coordinates: {_old}, new: {square}")

        self.is_flipped = not self.is_flipped

    def place_pieces(self):
        """
        places pieces on the board based on the progress of the board attribute 
        different from create_squares in that it doesn't create squares it instead 
        clears all the squares of existing pieces and positions the pieces on the board
        """
        string = self.board.__str__()
        ranks_inverted = string.split('n')#[::-1]

        for i in range( self.number_of_rows ):
            rank = ranks_inverted[i].split(' ')

            for j in range( self.number_of_columns ):
                self.squares[i][j].empty()
                board_square = rank[j]

                piece = self.get_piece_from_notation(board_square)

                self.squares[i][j].piece = piece

    def get_possible_moves(self, source_coordinates, remove_hints=False):
        """
        Gets the possible moves from some coordinates and marks the squares as possible moves if move_hints are enabled
        """
        # source_square = [ square.get_chess_square() for square in self.iter_squares() if square.collidepoint(source_coordinates) ]
        source_square = self.get_square_from_coordinates(source_coordinates)

        if source_square:
            destination_chess_squares = [ move.to_square for move in self.board.legal_moves if move.from_square == source_square ]
            destination_squares = [ square.set_is_possible_move(not remove_hints) for square in self.iter_squares() if square.get_chess_square() in destination_chess_squares ]

            return destination_squares

        return []

    def get_possible_moves_without_hint(self, source_coordinates):
        """
        Gets the possible moves from some coordinates
        """
        source_square = self.get_square_from_coordinates(source_coordinates)

        if source_square:
            destination_chess_squares = [ move.to_square for move in self.board.legal_moves if move.from_square == source_square ]
            destination_squares = [ square for square in self.iter_squares() if square.get_chess_square() in destination_chess_squares ]

            return destination_squares

        return []

    def hide_hints(self):
        """
        Hides the hints on the squares
        """
        [square.set_is_possible_move(False) for square in self.iter_squares()]

    def get_square_from_coordinates(self, coordinates, return_chess_square=True) -> ChessSquare:
        """
        Returns a square that corresponds to the coordinates passed
        """
        square = [ (square.get_chess_square() if return_chess_square else square) for square in self.iter_squares() if square.collidepoint(coordinates) ]

        if len(square) > 0:
            square = square[0]

            return square
        print(f"There is no square at the {coordinates} coordinates")
        return None

    def get_move_notation(self, source_square: ChessSquare, destination_square: ChessSquare):
        """
        Gets the notation for a particular move made from source_square to destination_square
        """
        move = ''

        if source_square.piece:
            other_pieces_of_the_same_type_that_can_make_move = self.get_pieces_that_can_make_move( [source_square.piece.get_notation()], source_square.piece.color, destination_square, [source_square] )
            same_rank = False
            same_file = False

            if source_square.piece.get_notation() != '':
                for square in other_pieces_of_the_same_type_that_can_make_move:
                    if square.rank_number == source_square.rank_number:
                        same_rank = True
                    if square.file_number == source_square.file_number:
                        same_file = True

                move = move + source_square.piece.get_notation()

                if same_file or same_rank:
                    if not same_file:
                        move = move + f"{source_square.get_file()}"
                    elif same_file and not same_rank:
                        move = move + f"{source_square.get_rank()}"
                    else:
                        move = move + f"{source_square.get_notation()}"

        if destination_square.piece:
            move = move + 'x'

            if source_square.piece and source_square.piece.get_notation() == '':
                move = source_square.get_file() + move


        move = move + f'{destination_square.get_notation()}'

        if source_square.piece.get_notation() == 'K' and source_square.get_file() == 'e' and destination_square.get_file() in [ 'c', 'g' ]:
            # castling
            if destination_square.get_file() == 'c':
                return '0-0-0'
            else:
                return '0-0'

        move = chess.Move(
            from_square=source_square.get_chess_square(), to_square=destination_square.get_chess_square()
        )

        return move

    def get_pieces_that_can_make_move(self, piece_notations: list, color, square: ChessSquare, squares_to_exclude: list):
        """
        Returns the pieces with notations in <piece_notations> list and of color <color> that can make a move the <square> square 
        while excluding the pieces on the <squares_to_exclude> list
        """
        squares_with_pieces_of_specified_types = [ _square for _square in self.iter_squares() if _square.piece and _square.piece.get_notation() in piece_notations and _square.piece.color == color and _square not in squares_to_exclude ]
        squares_that_can_make_move = [ _square for _square in squares_with_pieces_of_specified_types if square in self.get_possible_moves_without_hint(_square.center) ]

        return squares_that_can_make_move

    def play(self, source_coordinates, destination_coordinates):
        """
        Makes a move from source_coordinates to destination_coordinates
        """
        source_square = self.get_square_from_coordinates(source_coordinates, return_chess_square=False)
        destination_square = self.get_square_from_coordinates(destination_coordinates, return_chess_square=False)

        self._play(source_square, destination_square)

    def _play(self, source_square: ChessSquare=None, destination_square: ChessSquare=None, 
        source_chess_square: chess.Square=None, destination_chess_square: chess.Square=None,
        move: chess.Move=None
    ):
        """
        Makes a move based on the arguments.
        """
        if move:
            self.make_move(move)
            self.previous_move_square = self.get_square_from_chess_square(move.from_square)
            self.current_move_square = self.get_square_from_chess_square(move.to_square)

        elif source_square and destination_square:
            move = self.get_move_notation(source_square, destination_square)
            self.make_move(move)
            self.previous_move_square = source_square
            self.current_move_square = destination_square

        elif source_chess_square and destination_chess_square:
            move = chess.Move(from_square=source_chess_square, to_square=destination_chess_square)
            self.make_move(move)
            self.previous_move_square = self.get_square_from_chess_square(source_chess_square)
            self.current_move_square = self.get_square_from_chess_square(destination_chess_square)

        else:
            print("None of the conditions were fulfilled. No move is currently being made")

        self.place_pieces()

        print('The current board is')
        print(self.board)

    def make_move(self, move):
        """
        Makes a move either with an str object or a chess.Move object
        """
        if isinstance(move, str):
            self.board.push_san(move)
        elif isinstance(move, chess.Move):

            if self.board.is_capture(move):
                destination_square: ChessSquare = self.get_square_from_chess_square(move.to_square)
                piece: Piece = destination_square.piece

                print("The move was a capture")

                if piece is not None:
                    piece.set_is_captured(True)
                    color = piece.color

                    self.captured_pieces[color].append(piece)

            self.board.push(move)

    def iter_squares(self):
        """
        A generator that returns the different squares on the board
        """
        for rank in self.squares:
            for square in rank:
                yield square

Enter fullscreen mode

Exit fullscreen mode

Displaying the board in a pygame window

Before we move forward with this, let’s firstly create some classes we will use in this file. In our gui_components folder we will create a new file components.py. Put this code inside that file

import pygame

class BorderedRectangle():
    """
    An object that contains 2 pygame.Rect object, one put inside the other
    """
    def __init__(
        self, left: float, top: float, width: float, height: float, 
        background_color: str, border_color: str, border_width: int,
        outer_rectangle_border_width=2, inner_rectangle_border_width=2
    ) -> None:
        self.background_color = background_color
        self.border_color = border_color
        self.is_possible_move = False
        self.outer_rectangle_border_width = outer_rectangle_border_width
        self.inner_rectangle_border_width = inner_rectangle_border_width

        self.outer_rectangle = pygame.Rect(left, top, width, height)

        self.inner_rectangle = pygame.Rect(
            left+(border_width / 2), top+(border_width/2), 
            width - border_width, height - border_width
        )

Enter fullscreen mode

Exit fullscreen mode

Now in our root directory (chess-game), create a new file main.py. In this file we will write the code to display our board in a pygame window and even to play the game without AI and board flips.

import chess

import pygame

from pygame import mixer

mixer.init()

from gui_components.board import ChessBoard
from gui_components.components import BorderedRectangle

from ai import players as ai_players

pygame.init()

screen = pygame.display.set_mode([500, 500])

board = chess.Board()

# A dictionary of the different players in the game. True corresponds to white and 
# False to black
players = { 
    True: "user",
    False: "user"
}

turns_taken = {
    True: False, # set to True if white has already started playing 
    False: False # set to True if black has already started playing
}

# the different sounds for the moves
move_sound = mixer.Sound("sound_effects/piece_move.mp3")
check_sound = mixer.Sound("sound_effects/check.mp3")
checkmate_sound = mixer.Sound("sound_effects/checkmate.mp3")

SOURCE_POSITION = None
DESTINATION_POSITION = None
PREVIOUSLY_CLICKED_POSITION = None
POSSIBLE_MOVES = []
TURN = True
IS_FIRST_MOVE = True

running = True

LIGHT_COLOR = (245, 245, 245) # color of the light squares
DARK_COLOR = ( 100, 100, 100 ) # color of the dark squares
WHITE_COLOR = (255, 255, 255) # white
BLACK_COLOR = (0, 0, 0) # black

chess_board = ChessBoard(  # creating a new ChessBoard object
    50, 50, 400, 400, 0, 0, board=board
)

def draw_bordered_rectangle(rectangle: BorderedRectangle, screen):
    pygame.draw.rect( screen, rectangle.border_color, rectangle.outer_rectangle, width=rectangle.outer_rectangle_border_width )
    pygame.draw.rect( screen, rectangle.background_color, rectangle.inner_rectangle, width=rectangle.inner_rectangle_border_width )

def draw_chessboard(board: ChessBoard):
    """
    Draw the chess board on the pygame window
    """
    ranks = board.squares # get the rows of the board

    # a rectangle enclosing the board and the files and ranks labels
    board_bordered_rectangle = BorderedRectangle(25, 25, 450, 450, WHITE_COLOR, DARK_COLOR, 48)
    draw_bordered_rectangle(board_bordered_rectangle, screen)

    # draw the inner rectangle of the bordered rectangle with the same color 
    # as that of the dark squares
    pygame.draw.rect( 
        screen, board_bordered_rectangle.border_color, board_bordered_rectangle.inner_rectangle, 
        width=1
    )

    board_top_left = board.rect.topleft
    board_top_right = board.rect.topright
    board_bottom_left = board.rect.bottomleft

    for i, rank in enumerate(ranks):
        rank_number = ChessBoard.RANKS[ 7 - i ]
        file_letter = ChessBoard.RANKS[i]

        font_size = 15 # font size for the ranks and files

        # add the text rectangle on the left and right of the board
        font = pygame.font.SysFont('helvetica', font_size)

        # render the ranks (1-8)
        for _i in range(1):
            if _i == 0:
                _rect = pygame.Rect(
                    board_top_left[0] - font_size, board_top_left[1] + (i*board.square_size), 
                    font_size, board.square_size
                )
            else:
                _rect = pygame.Rect(
                    board_top_right[0], board_top_right[1] + (i*board.square_size),
                    font_size, board.square_size
                )

            text = font.render(f"{rank_number}", True, DARK_COLOR)
            text_rect = text.get_rect()
            text_rect.center = _rect.center

            screen.blit(text, text_rect)

        # render the files A-H
        for _i in range(1):
            if _i == 0:
                _rect = pygame.Rect(
                    board_top_left[0] + (i*board.square_size), board_top_left[1] - font_size, 
                    board.square_size, font_size
                )
            else:
                _rect = pygame.Rect(
                    board_top_left[0] + (i*board.square_size), board_bottom_left[1], 
                    board.square_size, font_size
                )

            text = font.render(f"{file_letter}", True, DARK_COLOR)
            text_rect = text.get_rect()
            text_rect.center = _rect.center

            screen.blit(text, text_rect)

        for j, square in enumerate(rank):
            if square is board.previous_move_square:
                # highlight source square of the latest move
                pygame.draw.rect( screen, board.previous_square_highlight_color, square )
            elif square is board.current_move_square:
                # highlight the destination square of the latest move
                pygame.draw.rect( screen, board.current_square_highlight_color, square )
            else:
                pygame.draw.rect( screen, square.background_color, square )

            if square.piece:
                # draw the piece on the square
                try:
                    image = square.piece.get_image()
                    image_rect = image.get_rect()
                    image_rect.center = square.center

                    screen.blit( image, image_rect )
                except TypeError as e:
                    raise e
                except FileNotFoundError as e:
                    print(f"Error on the square on the {i}th rank and the {j}th rank")
                    raise e

            if square.is_possible_move and board.move_hints:
                # draw a circle in the center of the square to highlight is as a possible move
                pygame.draw.circle( 
                    screen, (50, 50, 50), 
                    square.center,
                    board.square_size*0.25
                )

def play_sound(board):
    """
    Play sound after move based on move type
    """
    if board.is_checkmate():
        mixer.Sound.play(checkmate_sound)

    elif board.is_check():
        mixer.Sound.play(check_sound)

    elif board.is_stalemate():
        pass

    else:
        mixer.Sound.play(move_sound)

def play(source_coordinates: tuple=None, destination_coordinates: tuple=None):
    """
    Make a move on the board based on the source and destination coordinates if a user is playing
    """
    global board, TURN, IS_FIRST_MOVE, chess_board

    turn = board.turn

    player = players[turn]
    turns_taken[turn] = not turns_taken[turn]
    print(f"Setting {turns_taken[turn]} to {not turns_taken[turn]}")

    if not isinstance(player, str):
        # AI model to play
        player.make_move(chess_board)
        play_sound(board)

        TURN = not TURN

        if isinstance(players[TURN], ai_players.AIPlayer):
            # if the next player is an AI, automatically play
            print("Next player is AI, making a move for them automaically")
            # sleep(5)
    else:
        if source_coordinates and destination_coordinates:
            # user to play
            print("User is making move")
            chess_board.play(source_coordinates, destination_coordinates)
            play_sound(board)
            TURN = not TURN

    if IS_FIRST_MOVE:
        IS_FIRST_MOVE = False

    turns_taken[turn] = not turns_taken[turn]
    print(f"Setting {turns_taken[turn]} to {not turns_taken[turn]}")


def click_handler(position):
    """
    Handle the click events of the game
    """
    global SOURCE_POSITION, POSSIBLE_MOVES, TURN

    if chess_board.rect.collidepoint(position): # if position is in the board
        current_player = players[TURN]

        if isinstance(current_player, str):
            if SOURCE_POSITION is None:
                POSSIBLE_MOVES = chess_board.get_possible_moves(position)
                SOURCE_POSITION = position if POSSIBLE_MOVES else None
            else:
                # getting the squares in the possible destinations that correspond to the clicked point
                destination_square = [ square for square in POSSIBLE_MOVES if square.collidepoint(position) ]

                if not destination_square:
                    chess_board.get_possible_moves(SOURCE_POSITION, remove_hints=True)
                    SOURCE_POSITION = None
                else:
                    destination_square = destination_square[0]
                    print(f"In main.py, about to play, the source and destination are {SOURCE_POSITION} and {position} respectively")
                    chess_board.get_possible_moves(SOURCE_POSITION, remove_hints=True)

                    # chess_board.play( SOURCE_POSITION, position )
                    play(SOURCE_POSITION, position)
                    SOURCE_POSITION = None

                    current_player = players[TURN]

while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

        if event.type == pygame.MOUSEBUTTONDOWN:
            MOUSE_CLICKED_POSITION = pygame.mouse.get_pos()
            click_handler(MOUSE_CLICKED_POSITION)

    screen.fill( (255, 255, 255) )

    draw_chessboard(chess_board, True)

    pygame.display.flip()

pygame.quit()

Enter fullscreen mode

Exit fullscreen mode

Now if you activate your virtual environment and run the main.py file python main.py a GUI chess game should be displayed:

A new game GUI

Here’s a gif of a game between two users

User v User

In the next article, we are going to look at the creation of an AI player and how to integrate that with our existing code.

The chess module is a pure Python chess library with move generation, move validation and support for common formats. We can play chess with it. It will help us to move the king queen, pawn, bishops and knights. We need to know the basics of chess to play chess with it. This module does every task in python that is possible in the real game.

Installation:

pip install chess

We just have to import the chess library and with it, we can play chess. When we will import the chess library we have to call the function named board so that we can see the status of the chess board. 

Here is the code for making calling the function board of chess library.

Python3

import chess

board=chess.Board()

print(board)

The photo on left is gui representation and picture on left is the ASCII Board

 We can find out what are the legal moves using the below code:

Python3

Output:

<LegalMoveGenerator at 0x3586100 (Nh3, Nf3, Nc3, Na3, h3, g3, f3, e3, d3, c3, b3, a3, h4, g4, f4, e4, d4, c4, b4, a4)>

If we have to move any piece, we can check with above command that which moves we can do.

Moving players:

Python3

board.push_san("e4")

print(board)

Output:

Change after the move .

To check for a checkmate:

Python3

Output:

If There is checkmate then it will be TRUE else FALSE.It will be a boolean value.

To check If it is a stalemate:

Stalemate is a situation in the game of chess where the player whose turn it is to move is not in check but has no legal move. The rules of chess provide that when stalemate occurs, the game ends as a draw.

Python3

Output:

It will return a boolean value a TRUE or FALSE.

We can detect check also with help of above function:

Python3

Output:

It will return a boolean value a TRUE or FALSE.

With the new rules from July 2014, a game ends as a draw (even without a claim) once a fivefold repetition occurs or if there are 75 moves without a pawn push or capture. Other ways of ending a game take precedence. So there methods for checking these things also=

Python3

board.is_fivefold_repetition()

board.is_seventyfive_moves()

Output:

Both of it will return a boolean value a TRUE or FALSE.

I mentioned recently that I was learning Python. I’m working with a few friends on learning the basics. My background is mostly Objective-C and in recent years, Swift. I also programmed in PHP last year for several months working with Laravel, Lumen, Lighthouse, and GraphQL.

To learn Python we decided to begin looking at making a chess game using Pygame. This post shows my attempt to get the chessboard on the screen. I opted to store the coordinates of each square in a class along with their size and if they are black or white. I don’t yet know if this is an efficient way, but for now, it’s helping me learn the Python syntax and work with a few nested loops and a multidimensional list.

One thing to note before I move on to showing my work, I spent just a day on this and my aim wasn’t to separate classes into their own files or work too much on the structure of the program. I just wanted to get it done quickly to see if I could draw the chessboard and if all went well, then begin looking at the structure of the program.

To get more familiar with Pygame I searched and found this tutorial by Jon Fincher at realpython.com. This is a great tutorial and I recommend it if you want a good introduction to Pygame.

Getting going with Pygame

import pygame

from pygame.locals import (
    MOUSEBUTTONUP,
    K_ESCAPE,
    KEYDOWN,
    QUIT,
)

pygame.init()

SCREEN_WIDTH = 700
SCREEN_HEIGHT = 700

screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))

In main.py I used the following code. It first imports pygame. If you haven’t installed this module, then you need to do that first. Please post a comment below if you are stuck on this part.

Next, I import MOUSEBUTTONUP, K_ESCAPE, KEYDOWN, and QUIT from pygame.locals. These four inputs allow me to detect when a mouse button has been clicked when escape has been tapped when any key has been tapped, or if the program has been called. We’ll use these later on in the program.

Next, we initialise pygame. We then set some constants for the screen width and height. For now, I set these at 700 pixels each.

pygame.display.set_mode returns a surface that we can work with. We pass in the width and height as a tuple and this determines the size of our display.

The Board Squares

For this initial version, I thought I would create a custom object that represents each square. For that, I decided it needed x and y coordinates which are for the top left of each square. It would then have a width and height, although I combined this into a single property called width_height because all squares on the board are square; no surprise there, and then another property to show if the square is white or not. I was going to feed in black or white but decided a boolean would suffice.

class BoardSquare:
    def __init__(self, x_start, y_start, width_height, is_white):
        self.x_start = x_start
        self.y_start = y_start
        self.width_height = width_height
        self.is_white = is_white

The BoardSquare class at the moment just has a default initialiser that accepts x_start, y_start, width_height, and is_white.

The next step was to create a 2D list and fill it with each chessboard square. I created the following to handle this:

chess_board = []
is_white = False
for y in range(8):
    chess_row = []
    is_white = not is_white
    for x in range(8):
        chess_row.append(calculate_coordinates(x, y, is_white))
        is_white = not is_white
    chess_board.append(chess_row)

I first create an empty list called chess_board. At this point it is not multi-dimensional.

I then set is_white to false.

I then created a for loop with a range of 8. I called this y because it will be working through one row at a time starting at row 0, working through to row 7.

In this first, or outer, loop, I initialise an empty list and call it chess_row. This list is the second dimension and will hold a row of eight squares.

Because a chessboard starts with a white square at the top left, I set is_white to not is_white, effectively toggling that boolean back to white.

I then create a nested loop with x as the number. This is the x coordinate and also counts to 8, working left to right.

I create a BoardSquare object and append it. I’ll explain this in more details in a few moments. I then toggle white to black so that on the next iteration of the nested loop, it creates a black square, and then it toggles back to white.

When the nested loop has run eight times, I then append the chess_row to the chess_board array so that item 1 in chess_board, meaning the top row, contains 8 squares.

When this is appended it loops back around the outer loop and white is toggled back to black for the first square of row 2. On the first run of this, I just saw black and white bands across the screen. The reason for this was that I didn’t notice that when the last square on a row is black, the first square on the next row is also black. I added in this extra toggle so that it switches back to the same colour when the row finishes and the new one starts.

calculate_coordinates

This function is used to calculate the x and y coordinates of each square.

def calculate_coordinates(x_array, y_array, is_white):
    if SCREEN_WIDTH < SCREEN_HEIGHT or SCREEN_WIDTH == SCREEN_HEIGHT:
        width_height = SCREEN_WIDTH / 8
    else:
        width_height = SCREEN_HEIGHT / 8

    x_coordinate = x_array * width_height
    y_coordinate = y_array * width_height

    return BoardSquare(x_coordinate, y_coordinate, width_height, is_white)

This function is used to calculate the x and y coordinates of each square. Because the constants for SCREEN_WIDTH and SCREEN_HEIGHT can be changed in code before it runs, I decided to make the board flexible as well. If you decide you want a smaller board, the squares will be sized correctly on launching the game.

This function accepts the x and y values from the loops (ie, on the first iteration it would be 0, 0). It also accepts the boolean of the square being white or black.

The first if/else statement checks if the board is taller or wider or square. If taller, we use the shorter edge. If it’s wider, we use the shorter edge. This means that the board will always fit into the game. I store this in width_height which is the width/height of each square.

The next challenge is to calculate the top left corner of each square. To do this I get the x_array value and multiply it by width_height. If we are 4 squares along, the x passed in will be a 3 which will multiple by perhaps 80 making it 240 pixels. The same principle is used for y.

I then return an instantiated BoardSquare with the correct parameters passed into it.

Putting the Chessboard on Screen

Now that I have a multidimensional list of all of the chess squares I next put them on screen.

for row in chess_board:
    for square in row:
        surf = pygame.Surface((square.width_height, square.width_height))

        if square.is_white:
            surf.fill((255, 255, 255))
        else:
            surf.fill((0, 0, 0))

        rect = surf.get_rect()
        screen.blit(surf, (square.x_start, square.y_start))
        pygame.display.flip()

I loop around chess_board and call the value “row”. This means I am beginning on the top row.

I then create a nested loop that works across the row. I call the value “square” as it represents a square on the board.

I create a surface with the squares width_height variable. Because we know that chess squares are square, I just access the same square.width_height for both the width and length.

if square.is_white is true, I set the fill for the box to be white. If not, it gets set to black.

The last three lines of code are Pygame’s way of drawing the surface.

When running the program now, a chessboard will appear.

Main Loop

Now that the board is displayed I have added the main loop, although at the moment it just checks for escape, quit, and mouse clicks.

while running:
    for event in pygame.event.get():
        if event.type == KEYDOWN:
            if event.key == K_ESCAPE:
                running = False

        if event.type == MOUSEBUTTONUP:
            pos = pygame.mouse.get_pos()
            highlight_selected_square(get_square_for_position(pos))
        elif event.type == QUIT:
            running = False

The code above is partly from what I found at realpython.com, but also with MOUSEBUTTONUP added which I call a function and pass in the position of the mouse click. I haven’t finished this yet, but right now the position is passed into get_square_for_position and this function passes back the square object from the multidimensional list.

The other function, highlight_selected_square will be used to test if the correct square has been selected. My plan with that is to put a border around each square as it is clicked on.

get_square_for_position has the following code:

def get_square_for_position(pos):
    for row in chess_board:
        if row[0].y_start < pos[1] < row[0].y_start + row[0].width_height:
            for square in row:
                if square.x_start < pos[0] < square.x_start + square.width_height:
                    return square

The position is passed in which is an x, y coordinate of the mouse click. I learned tonight that this can be accessed with x, y = get_pos(), but in this example, I access pos[0] for x and pos[1] for y.

This looks through the first row of the chess_board list and checks if the y coordinates are within the first row. If true we know that the selected square is on a certain row.

When that is true, I loop through each square in the row and check if the x coordinates are within a certain square, and if true, I return the square.

This doesn’t feel too efficient looping through until the correct square is found. In big-o notation, this wouldn’t be the quickest way when iterating through every item until found, but for now, it will do.

Closing Comments

I am mostly happy with how this turned out. I need to break out the logic a bit more and create a separate file for the BoardSquare class. I don’t know for sure if I will continue down this path of programmatically drawing a chessboard. I may end up just using an image and then calculating which square is clicked a different way, but for now, it works.

The initial purpose of doing this was to become familiar with Python, which I haven’t used much at all. Now that I have some familiarity with it I will be drawing out the structure of the program first to decide what logic should be handled where. Right now, there is no MVC pattern with my example code. I assume people work with MVC when using Python, but that is something I need to read up on to see if it’s best practice for the language.

Понравилась статья? Поделить с друзьями:
  • Как написать шахматный движок
  • Как написать шарию личное сообщение
  • Как написать шапку характеристики
  • Как написать шапку реферата
  • Как написать шапку объяснительной