[feat]: Develop Two-Player Chess Game in Python

This commit is contained in:
Purna Shrestha 2023-10-28 23:35:19 +05:45
parent 3ad28fbf2f
commit aefe830777
24 changed files with 970 additions and 0 deletions

BIN
Chess/Images/Highlight.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

BIN
Chess/Images/bB.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
Chess/Images/bK.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
Chess/Images/bN.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
Chess/Images/bQ.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
Chess/Images/bR.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 725 B

BIN
Chess/Images/bp.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 797 B

BIN
Chess/Images/chess.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 777 KiB

BIN
Chess/Images/chess1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
Chess/Images/dtb.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

BIN
Chess/Images/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
Chess/Images/logo1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

BIN
Chess/Images/ltb.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

BIN
Chess/Images/royal.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
Chess/Images/wB.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
Chess/Images/wK.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
Chess/Images/wN.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
Chess/Images/wQ.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

BIN
Chess/Images/wR.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 933 B

BIN
Chess/Images/wp.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

45
Chess/README.md Normal file
View File

@ -0,0 +1,45 @@
# 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](https://github.com/purnasth)

599
Chess/engine.py Normal file
View File

@ -0,0 +1,599 @@
class gamestate():
def __init__(self):
# 2D 8x8 list, each element has 2 characters.
# The first character represents the color of the piece
# The second character represents the type of the piece
# "--" represents an empty space with no piece
# "wp" represents a white pawn
# "bR" represents a black rook
# "bK" represents a black king
# "wQ" represents a white queen
# and so on
self.board = [
["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"]]
# dictionary to map pieces to their respective move functions
# so that proper function is called for each piece
self.moveFunctions = {'p':self.getPawnMoves,'R':self.getRookMoves,'N':self.getKnightMoves,
'B':self.getBishopMoves,'Q':self.getQueenMoves,'K':self.getKingMoves}
# we dont have to write 6 different functions for each piece
self.whitemove=True
self.moveLog = []
self.whiteKingLocation = (7,4)
self.blackKingLocation = (0,4)
self.checkmate = False
self.stalemate = False
self.enpassantPossible = () # coordinates for the square where en passant capture is possible
self.currentCastlingRights = castleRights(True,True,True,True)
self.castleRightsLog = [castleRights(self.currentCastlingRights.wks,self.currentCastlingRights.bks,self.currentCastlingRights.wqs,self.currentCastlingRights.bqs)]
# pawn promotion is if white pawn reaches row 0
# or if a black pawn reaches row 7
def makePawnPromotion(self,move,user_choice):
if move.pawn_promotion:
# place queen of same color at pawn's place
self.board[move.endRow][move.endCol] = move.pieceMoved[0] + user_choice
def makeMove(self,move):
# make the move and update the board
self.board[move.startRow][move.startCol] = "--"
self.board[move.endRow][move.endCol] = move.pieceMoved
self.moveLog.append(move)
# swap turns after move
self.whitemove = not self.whitemove
if move.pieceMoved == 'wK':
self.whiteKingLocation = (move.endRow,move.endCol)
if move.pieceMoved == 'bK':
self.blackKingLocation = (move.endRow,move.endCol)
if move.enpassantPossible:
self.board[move.startRow][move.endCol] = "--"
# capturing the pawn
#update enpassantPossible variable
# only if the pawn moves two squares ahead
# used abs so that it works for both white and black pawns
# both up the board and down the board
if move.pieceMoved[1] == 'p' and abs(move.startRow - move.endRow) == 2:
self.enpassantPossible = ((move.startRow + move.endRow)//2,move.startCol)
else:
# reset enpassantPossible
self.enpassantPossible = ()
# updateCastleRights(move)
if move.isCastleMove:
if move.endCol - move.startCol == 2:
# king side castle move
self.board[move.endRow][move.endCol-1] = self.board[move.endRow][move.endCol+1]
self.board[move.endRow][move.endCol+1] = "--"
else:
# queen side castle move
self.board[move.endRow][move.endCol+1] = self.board[move.endRow][move.endCol-2]
self.board[move.endRow][move.endCol-2] = "--"
# castling
# if king moves two squares to the right
# then rook moves one square to the left
# and vice versa
self.updateCastleRights(move)
self.castleRightsLog.append(castleRights(self.currentCastlingRights.wks,self.currentCastlingRights.bks,self.currentCastlingRights.wqs,self.currentCastlingRights.bqs))
# update casting rights whenever it is a rook or a king move
# if a rook or a king moves from its starting position
# then we have to update the castling rights
# pawn promotion
def undoMove(self):
# to make sure that there is a move to undo
if len(self.moveLog) != 0:
# pop returns and removes the last element from the list
move = self.moveLog.pop()
self.board[move.startRow][move.startCol] = move.pieceMoved
# undoing the move
self.board[move.endRow][move.endCol] = move.pieceCaptured
# to make sure the piece captured is not empty
# switch turns back
self.whitemove = not self.whitemove
if move.pieceMoved == 'wK':
self.whiteKingLocation = (move.startRow,move.startCol)
if move.pieceMoved == 'bK':
self.blackKingLocation = (move.startRow,move.startCol)
# undo enpassantPossible
if move.enpassantPossible:
self.board[move.endRow][move.endCol] = "--"
# leave the landing square blank
self.board[move.startRow][move.endCol] = move.pieceCaptured
# redo the enpassant capture
# if i undo the move, i have to set the enpassantPossible to the square where the enpassant capture was possible
self.enpassantPossible = (move.endRow,move.endCol)
# undo 2 square pawn advance
if move.pieceMoved[1] == 'p' and abs(move.startRow - move.endRow) == 2:
self.enpassantPossible = ()
self.castleRightsLog.pop()
self.currentCastlingRights = self.castleRightsLog[-1]
self.currentCastlingRights = castleRights(self.currentCastlingRights.wks,self.currentCastlingRights.bks,self.currentCastlingRights.wqs,self.currentCastlingRights.bqs)
# undo castling rights
# if a rook or a king moves from its starting position
# then we have to update the castling rights
# if a rook or a king moves from its starting position
# then we have to update the castling rights
if move.isCastleMove:
if move.endCol - move.startCol == 2:
# king side castle move
self.board[move.endRow][move.endCol+1] = self.board[move.endRow][move.endCol-1]
self.board[move.endRow][move.endCol-1] = "--"
else:
# queen side castle move
self.board[move.endRow][move.endCol-2] = self.board[move.endRow][move.endCol+1]
self.board[move.endRow][move.endCol+1] = "--"
self.checkmate = False
self.stalemate = False
def updateCastleRights(self,move):
if move.pieceMoved == 'wK':
self.currentCastlingRights.wks = False
self.currentCastlingRights.wqs = False
elif move.pieceMoved == 'bK':
self.currentCastlingRights.bks = False
self.currentCastlingRights.bqs = False
elif move.pieceMoved == 'wR':
if move.startRow == 7:
if move.startCol == 0:
self.currentCastlingRights.wqs = False
elif move.startCol == 7:
self.currentCastlingRights.wks = False
elif move.pieceMoved == 'bR':
if move.startRow == 0:
if move.startCol == 0:
self.currentCastlingRights.bqs = False
elif move.startCol == 7:
self.currentCastlingRights.bks = False
def getvalidmoves(self):
for log in self.moveLog:
print(log.getChessNotation())
# to store a copy of the enpassantPossible variable
temp_enpassantPossible = self.enpassantPossible
tempCastleRights = castleRights(self.currentCastlingRights.wks,self.currentCastlingRights.bks,self.currentCastlingRights.wqs,self.currentCastlingRights.bqs)
# 1. generate all possible moves
moves = self.getAllPossibleMoves()
# 2. for each move, make the move
if self.whitemove:
self.getCastleMoves(self.whiteKingLocation[0],self.whiteKingLocation[1],moves)
else:
self.getCastleMoves(self.blackKingLocation[0],self.blackKingLocation[1],moves)
# while removing an element from a list, we have to traverse the list backwards
# because the indexes change after removing the element
for i in range(len(moves)-1,-1,-1):
# make move
self.makeMove(moves[i])
# 3. generate all possible moves for the opponent
# 4. for each of your opponent's move, see if they attack your king
self.whitemove = not self.whitemove
if self.inCheck():
moves.remove(moves[i])
self.whitemove = not self.whitemove
self.undoMove()
# 3. generate all possible moves for the opponent
# 4. for each of your opponent's move, see if they attack your king
if len(moves) == 0:
if self.inCheck():
self.checkmate = True
else:
self.stalemate = True
# if we undo the move, we have to set the checkmate and stalemate to false
else:
self.checkmate = False
self.stalemate = False
# because we are not making any move
# we have to reset the enpassantPossible variable
# to its original value
self.enpassantPossible = temp_enpassantPossible
# reset
self.currentCastlingRights = tempCastleRights
return moves
def inCheck(self):
if self.whitemove:
return self.squareUnderAttack(self.whiteKingLocation[0],self.whiteKingLocation[1])
else:
return self.squareUnderAttack(self.blackKingLocation[0],self.blackKingLocation[1])
def squareUnderAttack(self,r,c):
self.whitemove = not self.whitemove
opp_moves = self.getAllPossibleMoves()
self.whitemove = not self.whitemove
for move in opp_moves:
if move.endRow == r and move.endCol == c:
return True
return False
# all moves without considering checks
def getAllPossibleMoves(self):
#
# empty list for storing all possible moves
poss_moves = []
# loop through all the squares in the board using nested for loop
for rows in range(len(self.board)):
for columns in range(len(self.board[rows])):
# checking the first char of pieces
# assigning moves to pieces according to their color
turn = self.board[rows][columns][0]
if (turn == 'w' and self.whitemove) or (turn == 'b' and not self.whitemove):
# if the piece is a pawn
# because each piece has its own set of rules
piece = self.board[rows][columns][1]
self.moveFunctions[piece](rows,columns,poss_moves)
# calls the proper function for each piece
return poss_moves
def getPawnMoves(self,rows,columns,poss_moves):
# if white pawn
#
if self.whitemove:
# if the square in front of the pawn is empty
if self.board[rows-1][columns] == "--":
# going a row ahead not diagonal so column reamins the same
poss_moves.append(Move((rows,columns),(rows-1,columns),self.board))
# if the pawn is in its starting position
# for pawns first move
# we can move two squares ahead
# so append move rows-2
# check two conditions for this
# 1. the square two squares ahead is empty
# pawn is at row 6 (its starting position)
if rows == 6 and self.board[rows-2][columns] == "--":
poss_moves.append(Move((rows,columns),(rows-2,columns),self.board))
# mark this square by photo
if columns-1 >= 0:
if self.board[rows-1][columns-1][0] == 'b':
poss_moves.append(Move((rows,columns),(rows-1,columns-1),self.board))
elif (rows-1,columns-1) == self.enpassantPossible:
# if the square is the enpassant square
# then we can capture the pawn
# so we have to add the move
poss_moves.append(Move((rows,columns),(rows-1,columns-1),self.board,enpassantPossible = True))
if columns+1 <= 7:
if self.board[rows-1][columns+1][0] == 'b':
poss_moves.append(Move((rows,columns),(rows-1,columns+1),self.board))
elif (rows-1,columns+1) == self.enpassantPossible:
# if the square is the enpassant square
# then we can capture the pawn
# so we have to add the move
poss_moves.append(Move((rows,columns),(rows-1,columns+1),self.board,enpassantPossible = True))
else:
if self.board[rows+1][columns] == "--":
poss_moves.append(Move((rows,columns),(rows+1,columns),self.board))
if rows == 1 and self.board[rows+2][columns] == "--":
poss_moves.append(Move((rows,columns),(rows+2,columns),self.board))
if columns-1 >= 0:
if self.board[rows+1][columns-1][0] == 'w':
poss_moves.append(Move((rows,columns),(rows+1,columns-1),self.board))
elif (rows+1,columns-1) == self.enpassantPossible:
# if the square is the enpassant square
# then we can capture the pawn
# so we have to add the move
poss_moves.append(Move((rows,columns),(rows+1,columns-1),self.board,enpassantPossible = True))
if columns+1 <= 7:
if self.board[rows+1][columns+1][0] == 'w':
poss_moves.append(Move((rows,columns),(rows+1,columns+1),self.board))
elif (rows+1,columns+1) == self.enpassantPossible:
# if the square is the enpassant square
# then we can capture the pawn
# so we have to add the move
poss_moves.append(Move((rows,columns),(rows+1,columns+1),self.board,enpassantPossible = True))
def getRookMoves(self,rows,columns,poss_moves):
# to get the direction like vector without changing the value of the original tuple
# back row,back column,forward row,forward column
directions = ((-1,0),(0,-1),(1,0),(0,1))
# white rook --> wR
# black rook --> bR
# so we can use the first character to check the color of the piece
# and the second character to check the type of the piece
# so we can use the second character to check the type of the piece
# so we can use the second character to check the type of the piece
enemy_color = "b" if self.whitemove else "w"
for d in directions:
for i in range(1,8):
# direction into magnitude
endRow = rows + d[0]*i
# kaha into kitne se
endCol = columns + d[1]*i
# if the square is on the board
if 0 <= endRow < 8 and 0 <= endCol < 8:
# square is on the board
endPiece = self.board[endRow][endCol]
# if the square is empty
# append the moves till the squares are empty
if endPiece == "--":
poss_moves.append(Move((rows,columns),(endRow,endCol),self.board))
elif endPiece[0] == enemy_color:
# move to capture the piece
poss_moves.append(Move((rows,columns),(endRow,endCol),self.board))
break
# cant go over the enemy piece
else:
# cant capture alsi
break
# cant go over your own piece
else:
# all squares are out of board
break
def getKnightMoves(self,rows,columns,poss_moves):
knight_moves = ((-2,-1),(-2,1),(-1,-2),(-1,2),(1,-2),(1,2),(2,-1),(2,1))
ally_color = "w" if self.whitemove else "b"
for m in knight_moves:
endRow = rows + m[0]
endCol = columns + m[1]
if 0 <= endRow < 8 and 0 <= endCol < 8:
endPiece = self.board[endRow][endCol]
if endPiece[0] != ally_color:
# enemy hai kya
# nahi na
# toh move
poss_moves.append(Move((rows,columns),(endRow,endCol),self.board))
def getBishopMoves(self,rows,columns,poss_moves):
# bishop can move diagonally
# left diagonal down, left diagonal up, right diagonal down, right diagonal up
directions = ((-1,-1),(-1,1),(1,-1),(1,1))
enemy_color = "b" if self.whitemove else "w"
for d in directions:
# maximum square limit
for i in range(1,8):
endRow = rows + d[0]*i
endCol = columns + d[1]*i
if 0 <= endRow < 8 and 0 <= endCol < 8:
endPiece = self.board[endRow][endCol]
if endPiece == "--":
poss_moves.append(Move((rows,columns),(endRow,endCol),self.board))
elif endPiece[0] == enemy_color:
poss_moves.append(Move((rows,columns),(endRow,endCol),self.board))
break
else:
break
else:
break
def getQueenMoves(self,rows,columns,poss_moves):
self.getBishopMoves(rows,columns,poss_moves)
self.getRookMoves(rows,columns,poss_moves)
def getKingMoves(self,rows,columns,poss_moves):
# all squares surrounding the king
directions = ((-1,-1),(-1,0),(-1,1),(0,-1),(0,1),(1,-1),(1,0),(1,1))
# but just one move
ally_color = "w" if self.whitemove else "b"
for d in directions:
# traversing for all possible moves
endRow = rows + d[0]
endCol = columns + d[1]
# filtering out the moves that are out of board
if 0 <= endRow < 8 and 0 <= endCol < 8:
endPiece = self.board[endRow][endCol]
# not running on a ally piece
if endPiece[0] != ally_color:
poss_moves.append(Move((rows,columns),(endRow,endCol),self.board))
# self.getCastleMoves(rows,columns,poss_moves)
def getCastleMoves(self,rows,columns,poss_moves):
if self.squareUnderAttack(rows,columns):
return
if (self.whitemove and self.currentCastlingRights.wks) or (not self.whitemove and self.currentCastlingRights.bks):
self.getKingSideCastleMoves(rows,columns,poss_moves)
if (self.whitemove and self.currentCastlingRights.wqs) or (not self.whitemove and self.currentCastlingRights.bqs):
self.getQueenSideCastleMoves(rows,columns,poss_moves)
def getKingSideCastleMoves(self,rows,columns,poss_moves):
if self.board[rows][columns+1] == '--' and self.board[rows][columns+2] == '--':
if not self.squareUnderAttack(rows,columns+1) and not self.squareUnderAttack(rows,columns+2):
poss_moves.append(Move((rows,columns),(rows,columns+2),self.board,isCastleMove = True))
def getQueenSideCastleMoves(self,rows,columns,poss_moves):
if self.board[rows][columns-1] == '--' and self.board[rows][columns-2] == '--' and self.board[rows][columns-3] == '--':
if not self.squareUnderAttack(rows,columns-1) and not self.squareUnderAttack(rows,columns-2):
poss_moves.append(Move((rows,columns),(rows,columns-2),self.board,isCastleMove = True))
class castleRights():
def __init__(self,wks,bks,wqs,bqs):
self.wks = wks
self.bks = bks
self.wqs = wqs
self.bqs = bqs
# white king side, black king side, white queen side, black queen side
class Move():
# map position from rows and columns to ranks and files in chess
# so using dictionaries to map
ranksToRows = {"1":7,"2":6,"3":5,"4":4,
"5":3,"6":2,"7":1,"8":0}
# reversing the above dictionary
rowsToRanks = {v:k for k,v in ranksToRows.items()}
# using for converting columns to files
filesToCols = {"a":0,"b":1,"c":2,"d":3,
"e":4,"f":5,"g":6,"h":7}
colsToFiles = {v:k for k,v in filesToCols.items()}
# fn with optional parameter
def __init__(self,start_sq,end_sq,board,enpassantPossible = False,isCastleMove = False):
# start_sq
# source
# end_sq
# destination
# board state passed to validate the move and store information about the move
# what piece was captured? --> information
# for first tuple that is sq_selected in player_clicks
self.startRow = start_sq[0]
self.startCol = start_sq[1]
# for second tuple that is sq_selected in player_clicks
self.endRow = end_sq[0]
self.endCol = end_sq[1]
# refers to pos in board
self.pieceMoved = board[self.startRow][self.startCol] # piece moved
# refers to pos in board
self.pieceCaptured = board[self.endRow][self.endCol] # piece captured
self.moveID = self.startRow*1000 + self.startCol*100 + self.endRow*10 + self.endCol
# default value for flag
self.pawn_promotion = False
# conditions of location and piece for pawn promotion
if (self.pieceMoved == "wp" and self.endRow == 0) or (self.pieceMoved == "bp" and self.endRow == 7):
self.pawn_promotion = True
# flag for en passant move
self.enpassantPossible = enpassantPossible
if self.enpassantPossible:
self.pieceCaptured = 'wp' if self.pieceMoved == 'bp' else 'bp'
self.isCastleMove = isCastleMove
# if pawn is moving two squares ahead
# if self.pieceMoved[1] == 'p' and abs(self.startRow - self.endRow) == 2:
# self.isEnpassantMove = True
# if pawn is moving two squares ahead
# if the pawn moves two squares ahead
# self.promotionChoice = "Q"
# we could write pawn promotion flags in the getpawnmoves itself but we chose this because of less new code to be written here
print(self.moveID)
def __eq__(self, other):
# comparing this object to another object
# to ensure that we are comparing two move objects and not some other class object
if isinstance(other,Move):
return self.moveID == other.moveID
return False
def getChessNotation(self):
# you can add to make this like real chess notation
return self.pieceMoved + "to" + self.getRankFile(self.startRow,self.startCol) + self.getRankFile(self.endRow,self.endCol)
def getRankFile(self,rows,columns):
return self.colsToFiles[columns] + self.rowsToRanks[rows]
# castling
# king cannot move to a square that is under attack
# sqs clear
# sqs cannot be under attack
# king didnt move
# king not in check
# first move of king and rook

326
Chess/main.py Normal file
View File

@ -0,0 +1,326 @@
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()