Chess

Two Player Chess

This is a simple two-player chess game built in Python. It uses the standard rules of chess and allows two players to play against each other on the same computer.

Installation

To install and run the game, follow these steps:

  1. Clone the repository to your local machine.

  2. Navigate to the project directory in your terminal.

  3. Install pygame , a Python library for creating games, by running the following command:

    pip install pygame
  4. Start the game by running the following command:

    python main.py

How to Play

The game is played using the standard rules of chess. Each player takes turns moving their pieces on the board until one player is in checkmate or a draw is declared.

To move a piece, select it with your mouse and drag it to the desired square. If the move is legal, the piece will be placed on the new square. If the move is not legal, the piece will return to its original position.

Features

  • En Passant: Special pawn capture move inclusion
  • Castling: Ability to perform the castling maneuver
  • Checkmate and Stalemate Detection: Logic for detecting game-ending states
  • User Interface: Graphical representation of the board with mouse controls
  • Standard Chess Rules: Adherence to traditional chess rules
  • Two-Player Mode: Enable two human players to compete on the same device

Code Overview

The game is built using two Python files:

  • engine.py: This file contains the logic for the chess game, including the rules for moving pieces and checking for checkmate and stalemate.
  • main.py: This file contains the user interface for the game, including the graphical representation of the board and the mouse controls for moving pieces.

Build with :heart: by Purna Shrestha

Source Code: main.py

import pygame as p
import engine


# square window for our game.
# can change screen size from here
screen_width = screen_height = 550
screen_caption = "Two Player Chess by Purna"
icon = p.image.load(r"Images\icon.png")

# rows and columns

dimensions = 8

# making sqaures in the screen to display chess board boxes
sq_size = screen_height // dimensions

fps = 30
# to pass as an argument in clock.tick
# adjust if game become laggy

images = {}

def load_images():

    # load all images once as it is cpu heavy task
    pieces = ["wp", "wR", "wN", "wB", "wQ", "wK", "bp", "bR", "bN", "bB", "bQ", "bK"]
    for piece in pieces:
        image_path = r"Images" + "\\" + piece + ".png"

        images[piece] = p.transform.scale(p.image.load(image_path).convert_alpha(), (sq_size, sq_size))

        # pygame.transform.scale to adjust the image

def main():
    p.init()
    
    

    # os.system("welcome.mp3")
    

    # setting screen with sizes

    # closing our face detection window
   

    screen = p.display.set_mode((screen_width,screen_height), p.HWSURFACE | p.DOUBLEBUF)
    
   
    
    p.display.set_caption(screen_caption)
    p.display.set_icon(icon)
    p.display.update()
    # clock object 
    clock = p.time.Clock()
    # fps change karega to limit CPU in clock.tick(15)

    screen.fill(p.Color("white"))
    # aise hi

    # creating a gamestate object joh ki constructor ko call karega apne
    # dot operator to call gamestate() in engine
    
    gs = engine.gamestate()


    # to store valid moves
    valid_moves = gs.getvalidmoves()


    # print(gs.board)
    move_made = False
    # to update valid moves only when a move is made
    
    # flag variable for when a move is made
    # loading the images "once"
    load_images()

    # running variable to check start and quit
    running = True
    # tuple to keep the last square selected
    sq_selected = ()
    # no square is selected at start
    # tuple: (row,col)
    # playerClicks = []

    # list to keep two inputs
    player_clicks = []
    # keep track of player clicks (two tuples: [(6, 4), (4, 4)])


    done = True
    
    chess = p.transform.scale_by(p.image.load(r"Images\chess.jpg"),0.25)
    screen.fill(p.Color("black"))
    while done:

        screen.blit(chess,p.Rect(200-5*sq_size + 180,200-5*sq_size + 200,10,10))
        screen.blit(p.transform.scale_by(icon,0.5),p.Rect(470-5*sq_size+15,screen_height/2-270,10,10))
        showtext(screen, "Welcome to Chess", (screen_height/2 - 230,screen_height/2 - 10), 40)
        showtext(screen, "Press any key to start the game", (screen_height/2 - 220,screen_height/2+50),25)
        
        
        p.display.flip()
        for event in p.event.get():
            if event.type == p.QUIT:
                p.quit()
            if event.type == p.KEYDOWN:
                done = False
                # showtext(screen, predicted_name + " is playing")




    
    # start of my gameloop
    while running:

        # lets keep a for loop to get events
        for event in p.event.get():
            # print(p.display.Info())
            # if the type of event is this
            
            if event.type == p.QUIT:
                # to exit the whileloop
                running = False
            elif event.type == p.MOUSEBUTTONDOWN:
                # mouse kaha h?
                mouse_location = p.mouse.get_pos() # (x,y) location of mouse
                # get x and y from list
                column = mouse_location[0]//sq_size
                row  = mouse_location[1]//sq_size
                
                
                # first click is select, second click is undo
                if sq_selected == (row,column):
                    # user clicks same sqaure again
                    sq_selected = () # undo
                    player_clicks = []
                else:
                    # store the square selected by the user now
                    sq_selected = (row,column)
                    player_clicks.append(sq_selected)
                    # first time it will append to empty list then it appends to list[0]
                    

                    # hume pata karna hai user ka first click hai ya second
                    if len(player_clicks)==2:

                        # do clicks hogye toh bolenge make move
                        # so call the move class constructor
                        move = engine.Move(player_clicks[0],player_clicks[1],gs.board)
                        print(move.getChessNotation())

                        # player_clicks[0] is our source
                        # player_clicks[1] is our piece's destination
                        for i in range(len(valid_moves)):

                        # only get valid move object
                        # so check for it

                            if move == valid_moves[i]:
                                
                                gs.makeMove(valid_moves[i])
                                user_choice = "Q"
                                while move.pawn_promotion:
                                    p.display.set_caption("Choose a piece to promote to")
                                    screen.fill(p.Color("black"))
                                    screen.blit(p.transform.scale_by(p.image.load(r"Images\PromotionMenu.jpg"),0.2),p.Rect(200-sq_size,200-sq_size,10,10))
                                    showtext(screen, "Enter the corresponding character of the piece you want to promote to :", (200,200),12)
                                    p.display.flip()
                                    user_choice = ""
                                    for event in p.event.get():
                                        if event.type == p.KEYDOWN:
                                            if event.key == p.K_q:
                                                gs.makePawnPromotion(move,"Q")
                                                move.pawn_promotion=False
                                                
                                            elif event.key == p.K_r:
                                                gs.makePawnPromotion(move,"R")
                                                move.pawn_promotion=False
                                                
                                            elif event.key == p.K_b:
                                                gs.makePawnPromotion(move,"B")
                                                move.pawn_promotion=False
                                            elif event.key == p.K_n:
                                                gs.makePawnPromotion(move,"N")
                                                move.pawn_promotion=False
                                            else:
                                                gs.makePawnPromotion(move,"Q")
                                                move.pawn_promotion=False
                                            p.display.set_caption("ChessAI")
                            
                                
                                
                                    
                                # argument to makemove is generated by the engine
                            
                                

                                move_made = True
                               
                                sq_selected = () # reset user clicks
                                player_clicks = []
                            
                            
                                
                                # reset the user clicks after making the move each time 
                        if not move_made:
                            player_clicks = [sq_selected]
                        
                            
                        #gs.makeMove(move)
                        # to make the move
                            
            elif event.type == p.KEYDOWN:
                if event.key == p.K_z:
                    gs.undoMove()

                    move_made = True
                    # when the user undoes a move the valid moves change
                    # so change the flag variable to true

                    # to update the valid moves
        if move_made:
            valid_moves = gs.getvalidmoves()
            move_made = False
    


        # calling the draw boardand pieces fn
        draw_game_state(screen,gs)
        clock.tick(fps)
        p.display.flip()
        # to update the display

# method to draw sqs on board and graphics of a current gamestate
def draw_game_state(screen,gs):

    # to draw squares on the board
    drawboard(screen)

    #board-->pieces order ofc matter karega nhi toh pieces piche chip jayenge 

    # to draw pieces
    drawpieces(screen,gs.board) # board from engine gamestate ka object gs , isliye dot

def drawboard(screen):
    # lets draw squares
    # white and grey alternate
    # make list to store white and grey switch karna easy hoga
    # colors = [p.Color("white"), p.Color("dark gray")]
    images = [p.image.load(r"images\ltb.jpg").convert_alpha(),p.image.load(r"images\dtb.jpg").convert_alpha()]

    for rows in range(dimensions):
        for columns in range(dimensions):
            # [00,10,20,30,40,50,60,70]
            # [01,11,21,31,41,51,61,71]
            # [02,12,22,32,42,52,62,72]
            # [03,13,23,33,43,53,63,73]
            # [04,14,24,34,44,54,64,74]
            # [05,15,25,35,45,55,65,75]
            # [06,16,26,36,46,56,66,76]
            # [07,17,27,37,47,57,67,77]

            # trend we see here is that if we add rows and columns
            # dark sqaures are odd
            # light sqaures are even

            # color = colors[(rows+columns)%2]
            image = images[(rows+columns)%2]
            # even --> colors[0] --> white
            # odd --> colors[1] --> black
            
            # smpart

            # just draw rectangle (surface,color,)
            custom_img = p.Surface((sq_size,sq_size))
            
            screen.blit(image,p.Rect(columns*sq_size,rows*sq_size,sq_size,sq_size))
            
            # p.draw.rect(screen, color, p.Rect(columns*sq_size,rows*sq_size, sq_size, sq_size))

def drawpieces(screen,board):
    for rows in range(dimensions):
        for columns in range(dimensions):
            pieces = board[rows][columns]
            if pieces != "--":
                screen.blit(images[pieces],p.Rect(columns*sq_size,rows*sq_size,sq_size,sq_size))
            # accessing our gs.board multi dim list by using [][]
            # to assign each square a piece

# function to show a menu an ask the user the piece to promote to in pawn promotion



    
            
    


def showtext(screen,text,location,fontsize):
    font = p.font.SysFont("Copperplate gothic", fontsize, True, False)
    textObject = font.render(text, 0, p.Color('White'))
    location1 = p.Rect(location, location)
    # textLocation = p.Rect(0, 0, screen_width, screen_height).move(screen_width / 2 - textObject.get_width() / 2, screen_height / 2 - textObject.get_height() / 2)
    # white = p.Color("black")
    # screen.blit(white,p.rect(textLocation,textLocation,200,200))
    screen.blit(textObject, location1)

    






# if we import something in the main code we need to do this cause it wont run otherwise
# THIS CODE WE HAVE TO RUN AS THIS IS OUR MAIN CODE AND WE IMPORT OTHER MODULES IN THIS CODE
# SO WE WRITE THIS
# The if __name__ == "__main__": construct is used to 
# ensure that a specific block of code only runs when the Python script is executed directly,
# not when it's imported as a module in another script.
if __name__=="__main__":
    main()