معرفی
این پروژه یک بازی شطرنج کامل است که با پایتون و کتابخانه
Pygame توسعه یافته است. بازی شامل تمام قوانین استاندارد
شطرنج، تشخیص حرکات قانونی، کیش و مات، و انیمیشنهای حرکتی میباشد. پروژه
از معماری دو بخشی شامل موتور بازی و رابط کاربری تشکیل شده است.
نصب و پیشنیازها
برای اجرای این پروژه نیاز به پایتون نسخه 3.6 یا بالاتر دارید. کتابخانههای مورد نیاز:
-
pygame- برای رابط گرافیکی و مدیریت رویدادها (نیاز به نصب: pip install pygame) os- برای مدیریت مسیر فایلها (پیشفرض پایتون)
pip install pygame
ساختار فایلهای پروژه
پروژه از دو فایل اصلی و یک پوشه تصاویر تشکیل شده است:
chess-game/
├── ChessGame.py # فایل اصلی رابط کاربری
├── ChessEngine.py # موتور بازی و منطق شطرنج
└── images/ # پوشه تصاویر مهرهها
├── wp.png # سرباز سفید
├── wR.png # رخ سفید
├── wN.png # اسب سفید
├── wB.png # فیل سفید
├── wQ.png # وزیر سفید
├── wK.png # شاه سفید
├── bp.png # سرباز سیاه
├── bR.png # رخ سیاه
├── bN.png # اسب سیاه
├── bB.png # فیل سیاه
├── bQ.png # وزیر سیاه
├── bK.png # شاه سیاه
└── icon.ico # آیکون برنامه
منبع تصاویر: تصاویر مهرههای شطرنج از وبسایت
https://greenchess.net/info.php?item=downloads
دانلود شدهاند.
معماری پروژه
پروژه از دو بخش اصلی تشکیل شده است:
- ChessGame.py - رابط کاربری گرافیکی و مدیریت رویدادها
- ChessEngine.py - موتور بازی شامل منطق شطرنج و حرکات
کلاسهای اصلی شامل:
- GameState - مدیریت وضعیت بازی و حرکات
- Move - نمایش و مدیریت حرکات
- توابع رابط کاربری برای نمایش و انیمیشن
ویژگیهای اصلی
این بازی شطرنج دارای قابلیتهای متعددی است:
- حرکات قانونی تمام مهرههای شطرنج
- تشخیص کیش و مات
- تشخیص پات (تساوی)
- انیمیشن حرکات مهرهها
- هایلایت خانههای قابل حرکت
- قابلیت undo حرکت (Ctrl+Z)
- بازنشانی بازی (کلید R)
- ارتقاء سرباز به وزیر
- نمایش وضعیت بازی و امتیازات
کدنویسی پروژه
بخش اول: ChessGame.py (رابط کاربری گرافیکی)
این فایل مسئول مدیریت رابط کاربری، رویدادهای ماوس و صفحهکلید، و نمایش بصری بازی است. شامل توابع اصلی برای رسم صفحه شطرنج، مهرهها، و مدیریت تعامل کاربر میباشد.
کد کامل ChessGame.py:
import pygame as p
import ChessEngine
import os
WIDTH = HEIGHT = 512
DIMENSIONS = 8
SQ_SIZE = HEIGHT // DIMENSIONS
MAX_FPS = 15
IMAGES = {}
try:
icon_path = os.path.join("images", "icon.ico")
icon = p.image.load(icon_path)
p.display.set_icon(icon)
except FileNotFoundError:
print("Warning: Icon file not found. Continuing without icon.")
except Exception as e:
print(f"Warning: Could not load icon: {e}")
p.display.set_caption("Chess Game")
def loadImages():
pieces = ['wp', 'wR', 'wN', 'wB', 'wQ', 'wK', 'bp', 'bR', 'bN', 'bB', 'bQ', 'bK']
for piece in pieces:
try:
image_path = os.path.join("images", piece + ".png")
IMAGES[piece] = p.transform.scale(p.image.load(image_path), (SQ_SIZE, SQ_SIZE))
except FileNotFoundError:
print(f"Warning: Image for {piece} not found at {image_path}")
except Exception as e:
print(f"Warning: Could not load image for {piece}: {e}")
def main():
p.init()
screen = p.display.set_mode((WIDTH, HEIGHT + 50))
clock = p.time.Clock()
screen.fill(p.Color("white"))
gs = ChessEngine.GameState()
validMoves = gs.getValidMoves()
moveMade = False
animate = False
loadImages()
running = True
sqSelected = ()
playerClicks = []
gameOver = False
if not IMAGES:
print("Error: No game piece images were loaded. The game may not display correctly.")
while running:
for e in p.event.get():
if e.type == p.QUIT:
running = False
elif e.type == p.MOUSEBUTTONDOWN:
if not gameOver:
location = p.mouse.get_pos()
col = location[0] // SQ_SIZE
row = (location[1] - 50) // SQ_SIZE
if 0 <= row < 8 and 0 <= col < 8:
if sqSelected == (row, col):
sqSelected = ()
playerClicks = []
else:
sqSelected = (row, col)
playerClicks.append(sqSelected)
if len(playerClicks) == 1 and (gs.board[row][col] == "--"):
sqSelected = ()
playerClicks = []
if len(playerClicks) == 2:
move = ChessEngine.Move(playerClicks[0], playerClicks[1], gs.board)
for i in range(len(validMoves)):
if move == validMoves[i]:
gs.makeMove(move)
moveMade = True
animate = True
sqSelected = ()
playerClicks = []
if not moveMade:
playerClicks = [sqSelected]
elif e.type == p.KEYDOWN:
if e.key == p.K_z:
gs.undoMove()
moveMade = True
animate = False
if e.key == p.K_r:
gs = ChessEngine.GameState()
validMoves = gs.getValidMoves()
sqSelected = ()
playerClicks = []
moveMade = False
animate = False
if moveMade:
if animate:
animatedMoves(gs.moveLog[-1], screen, gs.board, clock)
validMoves = gs.getValidMoves()
moveMade = False
animate = False
score = calculateScore(gs.board)
drawScoreAndTurn(screen, score, gs.whiteToMove, gs.checkMate, gs.staleMate)
drawGameState(screen, gs, validMoves, sqSelected)
if gs.checkMate:
gameOver = True
if gs.whiteToMove:
drawText(screen, 'Black wins by checkmate')
else:
drawText(screen, 'White wins by checkmate')
elif gs.staleMate:
gameOver = True
drawText(screen, 'Stalemate')
clock.tick(MAX_FPS)
p.display.flip()
def highlightSquares(screen, gs, validMoves, sqSelected):
if sqSelected != ():
r, c = sqSelected
if gs.board[r][c][0] == ('w' if gs.whiteToMove else 'b'):
s = p.Surface((SQ_SIZE, SQ_SIZE))
s.set_alpha(100)
s.fill(p.Color('blue'))
screen.blit(s, (c*SQ_SIZE, r*SQ_SIZE + 50))
s.fill(p.Color("yellow"))
for moves in validMoves:
if moves.startRow == r and moves.startCol == c:
screen.blit(s, (SQ_SIZE*moves.endCol, SQ_SIZE*moves.endRow + 50))
def drawGameState(screen, gs, validMoves, sqSelected):
drawBoard(screen)
highlightSquares(screen, gs, validMoves, sqSelected)
drawPieces(screen, gs.board)
def drawBoard(screen):
global colors
colors = [p.Color("white"), p.Color("grey")]
for r in range(DIMENSIONS):
for c in range(DIMENSIONS):
color = colors[(r+c) % 2]
p.draw.rect(screen, color, p.Rect(c*SQ_SIZE, r*SQ_SIZE + 50, SQ_SIZE, SQ_SIZE))
def drawPieces(screen, board):
for r in range(DIMENSIONS):
for c in range(DIMENSIONS):
piece = board[r][c]
if piece != "--" and piece in IMAGES:
screen.blit(IMAGES[piece], p.Rect(c*SQ_SIZE, r*SQ_SIZE + 50, SQ_SIZE, SQ_SIZE))
def animatedMoves(move, screen, board, clock):
global colors
dR = move.endRow - move.startRow
dC = move.endCol - move.startCol
framesPerSquare = 5
frameCount = (abs(dR) + abs(dC)) * framesPerSquare
for frame in range(frameCount + 1):
r, c = (move.startRow + dR*frame/frameCount, move.startCol + dC*frame/frameCount)
drawBoard(screen)
drawPieces(screen, board)
color = colors[(move.endRow + move.endCol) % 2]
endSquare = p.Rect(move.endCol*SQ_SIZE, move.endRow*SQ_SIZE + 50, SQ_SIZE, SQ_SIZE)
p.draw.rect(screen, color, endSquare)
if move.pieceCaptured != "--" and move.pieceCaptured in IMAGES:
screen.blit(IMAGES[move.pieceCaptured], endSquare)
if move.pieceMoved in IMAGES:
screen.blit(IMAGES[move.pieceMoved], p.Rect(c*SQ_SIZE, r*SQ_SIZE + 50, SQ_SIZE, SQ_SIZE))
p.display.flip()
clock.tick(60)
def drawText(screen, text):
font = p.font.SysFont("Helvetica", 32, True, False)
textObject = font.render(text, True, p.Color('Gray'))
textLocation = p.Rect(0, 50, WIDTH, HEIGHT).move(WIDTH/2 - textObject.get_width()/2, HEIGHT/2 - textObject.get_height()/2)
screen.blit(textObject, textLocation)
textObject = font.render(text, True, p.Color("Black"))
screen.blit(textObject, textLocation.move(2, 2))
def calculateScore(board):
pieceValues = {
'p': 1, # pawn
'N': 3, # knight
'B': 3, # bishop
'R': 5, # rook
'Q': 9, # queen
'K': 0 # king (not counted in material score)
}
whiteScore = 0
blackScore = 0
for row in board:
for piece in row:
if piece != "--":
color = piece[0]
pieceType = piece[1]
value = pieceValues.get(pieceType, 0)
if color == 'w':
whiteScore += value
else:
blackScore += value
return whiteScore, blackScore
def drawScoreAndTurn(screen, score, whiteToMove, checkMate, staleMate):
whiteScore, blackScore = score
scoreDifference = whiteScore - blackScore
infoHeight = 50
infoSurface = p.Surface((WIDTH, infoHeight))
infoSurface.fill(p.Color("lightgray"))
smallFont = p.font.SysFont("Helvetica", 16)
boldFont = p.font.SysFont("Helvetica", 16, True)
scoreText = f"White: {whiteScore} | Black: {blackScore}"
scoreSurface = smallFont.render(scoreText, True, p.Color("black"))
infoSurface.blit(scoreSurface, (10, 5))
if scoreDifference > 0:
advantageText = f"White +{scoreDifference}"
advantageColor = p.Color("darkblue")
elif scoreDifference < 0:
advantageText = f"Black +{abs(scoreDifference)}"
advantageColor = p.Color("darkred")
else:
advantageText = "Material equal"
advantageColor = p.Color("darkgreen")
advantageSurface = boldFont.render(advantageText, True, advantageColor)
infoSurface.blit(advantageSurface, (10, 25))
if not checkMate and not staleMate:
turnText = "White's turn" if whiteToMove else "Black's turn"
turnColor = p.Color("white")
turnSurface = smallFont.render(turnText, True, turnColor)
turnBgWidth = turnSurface.get_width() + 20
turnBgHeight = turnSurface.get_height() + 10
turnBg = p.Surface((turnBgWidth, turnBgHeight))
turnBg.fill(p.Color("darkgreen" if whiteToMove else "darkred"))
turnBgX = WIDTH - turnBgWidth - 10
turnBgY = (infoHeight - turnBgHeight) // 2
infoSurface.blit(turnBg, (turnBgX, turnBgY))
infoSurface.blit(turnSurface, (turnBgX + 10, turnBgY + 5))
screen.blit(infoSurface, (0, 0))
if __name__ == "__main__":
main()
توضیح بخشهای اصلی ChessGame.py:
۱. تنظیمات اولیه و بارگذاری تصاویر: در ابتدای برنامه،
پارامترهای اصلی بازی مانند ابعاد صفحه (512x512 پیکسل)، اندازه خانهها (64
پیکسل)، و نرخ فریم (15 فریم بر ثانیه) تنظیم میشوند. تابع
loadImages تصاویر تمام مهرهها را از پوشه images بارگذاری
کرده و به اندازه مناسب مقیاس میدهد. برای مدیریت صحیح مسیر فایلها در
سیستمعاملهای مختلف از os.path.join استفاده شده است.
۲. تابع اصلی و حلقه بازی: تابع main نقطه
شروع برنامه است که حلقه اصلی بازی را اجرا میکند. این حلقه به طور مداوم سه
نوع رویداد را پردازش میکند:
- رویداد خروج (QUIT): زمانی که کاربر پنجره را میبندد.
- کلیکهای ماوس (MOUSEBUTTONDOWN): برای انتخاب و حرکت مهرهها با سیستم دو کلیکی.
- فشار کلیدهای صفحهکلید (KEYDOWN): کلید Z برای بازگشت به حرکت قبلی (undo) و کلید R برای بازنشانی بازی (reset).
۳. منطق انتخاب مهره: زمانی که کاربر روی صفحه کلیک میکند،
موقعیت کلیک به مختصات شطرنجی (ردیف و ستون) تبدیل میشود. سیستم از یک لیست
به نام playerClicks برای ذخیره دو کلیک متوالی استفاده میکند:
کلیک اول خانه مبدا (مهره انتخاب شده) و کلیک دوم خانه مقصد را مشخص میکند.
۴. توابع رسم و نمایش: مجموعهای از توابع گرافیکی مسئول نمایش بازی هستند:
-
drawBoard: صفحه شطرنج را با الگوی شطرنجی سفید و خاکستری رسم میکند. -
drawPieces: مهرهها را بر اساس وضعیت فعلی بازی از کلاس GameState نمایش میدهد. -
highlightSquares: خانه انتخاب شده و خانههای قابل حرکت را با رنگهای آبی و زرد هایلایت میکند. -
drawGameState: توابع فوق را ترکیب کرده و حالت کامل بازی را رسم میکند.
۵. انیمیشن حرکات: تابع animatedMoves حرکت
مهرهها را به صورت روان و پیوسته نمایش میدهد. این تابع با محاسبه
موقعیتهای میانی بین نقطه شروع و پایان، حرکت را به چندین فریم تقسیم کرده و
در هر فریم مهره را در موقعیت جدیدی رسم میکند. تعداد فریمها بر اساس فاصله
حرکت تعیین میشود تا سرعت حرکت در تمام مسافتها یکسان باشد.
۶. سیستم امتیازدهی و نوار اطلاعات: این بخش در بالای صفحه شطرنج نمایش داده میشود و شامل:
- امتیاز هر بازیکن بر اساس ارزش مهرههای باقیمانده (سرباز: ۱، اسب/فیل: ۳، رخ: ۵، وزیر: ۹)
- تفاوت امتیاز و نشان دادن مزیت مادی هر طرف
- نشانگر نوبت بازیکن با رنگ سبز برای سفید و قرمز برای سیاه
- پیامهای پایان بازی (کیش و مات یا پات)
۷. مدیریت حالتهای پایان بازی: بازی دو حالت پایان دارد: کیش و مات (وقتی یک بازیکن در کیش است و هیچ حرکت قانونی ندارد) و پات (وقتی بازیکن در کیش نیست اما هیچ حرکت قانونی ندارد). در هر دو حالت، بازی متوقف شده و پیام مناسب نمایش داده میشود.
بخش دوم: ChessEngine.py (موتور بازی و منطق شطرنج)
این فایل شامل تمام منطق بازی شطرنج از جمله قوانین حرکات مهرهها، تشخیص کیش و مات، و مدیریت وضعیت بازی است. این بخش هسته اصلی برنامه است که مستقل از رابط کاربری عمل میکند.
کد کامل ChessEngine.py:
class GameState:
def __init__(self):
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"]]
self.moveFunctions = {'p': self.getPawnMoves, 'R': self.getRookMoves, 'N': self.getKnightMoves,
'B': self.getBishopMoves, 'Q': self.getQueenMoves, 'K': self.getKingMoves}
self.whiteToMove = True,
self.moveLog = []
self.whiteKingLocation = (7, 4)
self.blackKingLocation = (0, 4)
self.checkMate = False
self.staleMate = False
def makeMove(self, move):
self.board[move.startRow][move.startCol] = "--"
self.board[move.endRow][move.endCol] = move.pieceMoved
self.moveLog.append(move)
self.whiteToMove = not self.whiteToMove
if move.pieceMoved == "wK":
self.whiteKingLocation = (move.endRow, move.endCol)
elif move.pieceMoved == "bK":
self.blackKingLocation = (move.endRow, move.endCol)
if move.isPawnPromotion:
self.board[move.endRow][move.endCol] = move.pieceMoved[0] + "Q"
def undoMove(self):
if len(self.moveLog) != 0:
move = self.moveLog.pop()
self.board[move.startRow][move.startCol] = move.pieceMoved
self.board[move.endRow][move.endCol] = move.pieceCaptured
self.whiteToMove = not self.whiteToMove
if move.pieceMoved == "wK":
self.whiteKingLocation = (move.startRow, move.startCol)
if move.pieceMoved == "bK":
self.blackKingLocation = (move.startRow, move.startCol)
"""
All move considering checks
"""
def getValidMoves(self):
moves = self.getAllPossibleMoves()
for i in range(len(moves)-1, -1, -1):
self.makeMove(moves[i])
self.whiteToMove = not self.whiteToMove
if self.inCheck():
moves.remove(moves[i])
self.whiteToMove = not self.whiteToMove
self.undoMove()
if len(moves) == 0:
if self.inCheck():
self.checkMate = True
else:
self.staleMate = True
else:
self.checkMate = False
self.staleMate = False
return moves
def inCheck(self):
if self.whiteToMove:
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.whiteToMove = not self.whiteToMove
oppMoves = self.getAllPossibleMoves()
self.whiteToMove = not self.whiteToMove
for move in oppMoves:
if move.endRow == r and move.endCol == c:
return True
return False
"""
All move without considering checks
"""
def getAllPossibleMoves(self):
moves = []
for r in range(len(self.board)):
for c in range(len(self.board[r])):
turn = self.board[r][c][0] # b or w based on turn
if(turn == 'w' and self.whiteToMove) or (turn == 'b' and not self.whiteToMove):
piece = self.board[r][c][1]
self.moveFunctions[piece](r,c, moves)
return moves
def getPawnMoves(self, r, c, moves):
if self.whiteToMove:
if self.board[r-1][c] == "--":
moves.append(Move((r, c),(r-1, c), self.board))
if r == 6 and self.board[r-2][c] == "--":
moves.append(Move((r, c),(r-2, c), self.board))
if c-1 >= 0:
if self.board[r-1][c-1][0] == 'b':
moves.append(Move((r, c),(r-1, c-1), self.board))
if c+1 <= 7:
if self.board[r-1][c+1][0] == 'b':
moves.append(Move((r, c),(r-1, c+1), self.board))
else:
if self.board[r+1][c] == "--":
moves.append(Move((r, c),(r+1, c), self.board))
if r == 1 and self.board[r+2][c] == "--":
moves.append(Move((r, c),(r+2, c), self.board))
if c-1 >= 0:
if self.board[r+1][c-1][0] == 'w':
moves.append(Move((r, c),(r+1, c-1), self.board))
if c+1 <= 7:
if self.board[r+1][c+1][0] == 'w':
moves.append(Move((r, c),(r+1, c+1), self.board))
def getRookMoves(self, r, c, moves):
directions = ((-1, 0), (0, -1), (1, 0), (0, 1))
enemyColor = "b" if self.whiteToMove else "w"
for d in directions:
for i in range(1, 8):
endRow = r + d[0] * i
endCol = c + d[1] * i
if 0 <= endRow < 8 and 0 <= endCol < 8:
endPiece = self.board[endRow][endCol]
if endPiece == "--":
moves.append(Move((r,c), (endRow, endCol), self.board))
elif endPiece[0] == enemyColor:
moves.append(Move((r,c), (endRow, endCol), self.board))
break
else:
break
else:
break
def getKnightMoves(self, r,c,moves):
knightMoves = ((-2, -1), (-2, 1), (-1, -2), (-1, 2), (1, -2), (1, 2), (2, -1), (2,1))
allyColor = "w" if self.whiteToMove else "b"
for m in knightMoves:
endRow = r + m[0]
endCol = c + m[1]
if 0 <= endRow < 8 and 0 <= endCol < 8:
endPiece = self.board[endRow][endCol]
if endPiece[0] != allyColor:
moves.append(Move((r,c), (endRow, endCol), self.board))
def getBishopMoves(self, r,c,moves):
directions = ((-1, -1), (-1, 1), (1, -1), (1, 1))
enemyColor = "b" if self.whiteToMove else "w"
for d in directions:
for i in range(1, 8):
endRow = r + d[0] * i
endCol = c + d[1] * i
if 0 <= endRow < 8 and 0 <= endCol < 8:
endPiece = self.board[endRow][endCol]
if endPiece == "--":
moves.append(Move((r,c), (endRow, endCol), self.board))
elif endPiece[0] == enemyColor:
moves.append(Move((r,c), (endRow, endCol), self.board))
break
else:
break
else:
break
def getQueenMoves(self, r,c,moves):
self.getRookMoves(r, c, moves)
self.getBishopMoves(r, c, moves)
def getKingMoves(self, r,c,moves):
kingMoves = ((-1, -1), (-1, 0), (-1, 1), (0, -1), (0, 1), (1, -1), (1, 0), (1,1) )
allyColor = "w" if self.whiteToMove else "b"
for i in range(8):
endRow = r + kingMoves[i][0]
endCol = c + kingMoves[i][1]
if 0 <= endRow < 8 and 0 <= endCol < 8:
endPiece = self.board[endRow][endCol]
if endPiece[0] != allyColor:
moves.append(Move((r,c), (endRow, endCol), self.board))
class Move():
ranksToRow = {"1": 7, "2": 6, "3": 5, "4": 4,
"5": 3, "6": 2, "7": 1, "8": 0}
rowsToRanks = {v: k for k, v in ranksToRow.items()}
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()}
def __init__(self, startSq, endSq, board):
self.startRow = startSq[0]
self.startCol = startSq[1]
self.endRow = endSq[0]
self.endCol = endSq[1]
self.pieceMoved = board[self.startRow][self.startCol]
self.pieceCaptured = board[self.endRow][self.endCol]
self.isPawnPromotion = False
if (self.pieceMoved == 'wp' and self.endRow == 0) or (self.pieceMoved == 'bp' and self.endRow == 7):
self.isPawnPromotion = True
self.moveID = self.startRow * 1000 + self.startCol * 100 + self.endRow * 10 + self.endCol
def __eq__(self, other):
if isinstance(other, Move):
return self.moveID == other.moveID
return False
def getChessNotation(self):
return self.getRankFile(self.startRow, self.startCol) + self.getRankFile(self.endRow, self.endCol)
def getRankFile(self, r, c):
return self.colsToFiles[c] + self.rowsToRanks[r]
توضیح بخشهای اصلی ChessEngine.py:
۱. کلاس GameState (مدیریت وضعیت بازی): این کلاس وضعیت کامل بازی را در هر لحظه نگهداری میکند. مهمترین attributeهای آن عبارتند از:
-
board: یک آرایه دو بعدی 8×8 که موقعیت تمام مهرهها را ذخیره میکند. -
whiteToMove: یک مقدار بولین که نشان میدهد نوبت کدام بازیکن است. moveLog: لیستی از تمام حرکات انجام شده در بازی.-
whiteKingLocationوblackKingLocation: موقعیت فعلی شاهها برای تشخیص سریع کیش. -
checkMateوstaleMate: نشاندهنده وضعیت پایان بازی.
۲. ساختار دادهای صفحه: صفحه شطرنج به صورت یک آرایه دو بعدی از رشتهها پیادهسازی شده است. هر خانه میتواند یکی از مقادیر زیر را داشته باشد:
- "wp": سرباز سفید (white pawn)
- "wR": رخ سفید (white rook)
- "wN": اسب سفید (white knight)
- "wB": فیل سفید (white bishop)
- "wQ": وزیر سفید (white queen)
- "wK": شاه سفید (white king)
- مقادیر مشابه با پیشوند "b" برای مهرههای سیاه
- "--": خانه خالی
۳. متدهای مدیریت حرکات:
-
makeMove: یک حرکت معتبر را روی صفحه اعمال میکند، وضعیت بازی را به روز میکند، و حرکت را به تاریخچه اضافه میکند. -
undoMove: آخرین حرکت را از تاریخچه حذف کرده و وضعیت بازی را به حالت قبل برمیگرداند. - هر دو متد موقعیت شاهها را در صورت جابجایی بهروزرسانی میکنند و ارتقاء سرباز را مدیریت میکنند.
۴. سیستم تشخیص حرکات معتبر و کیش: این بخش پیچیدهترین قسمت موتور بازی است:
-
getValidMoves: از الگوریتم "تولید و آزمایش" استفاده میکند. ابتدا تمام حرکات ممکن تولید میشوند، سپس هر حرکت به طور موقت اجرا شده و بررسی میشود که آیا بازیکن را در وضعیت کیش قرار میدهد یا خیر. -
inCheck: بررسی میکند که آیا بازیکن فعلی در کیش است. -
squareUnderAttack: بررسی میکند که آیا یک خانه خاص مورد حمله مهرههای حریف است. - اگر پس از فیلتر کردن حرکات منجر به کیش، هیچ حرکت معتبری باقی نماند، بازی به وضعیت کیش و مات (اگر بازیکن در کیش باشد) یا پات (اگر در کیش نباشد) میرسد.
۵. تولید حرکات پایه برای هر مهره: هر نوع مهره تابع حرکت مخصوص به خود را دارد:
- سرباز (Pawn): میتواند یک خانه به جلو حرکت کند (یا دو خانه در حرکت اول). فقط به صورت مورب میتواند حمله کند. جهت حرکت بستگی به رنگ مهره دارد (سفید به سمت بالا، سیاه به سمت پایین).
- رخ (Rook): در چهار جهت اصلی (بالا، پایین، چپ، راست) به صورت خطی حرکت میکند تا زمانی که به مانع برخورد کند.
- فیل (Bishop): در چهار جهت مورب حرکت میکند.
- وزیر (Queen): ترکیبی از رخ و فیل، در هشت جهت حرکت میکند.
- اسب (Knight): با الگوی "L" حرکت میکند (دو خانه در یک جهت و یک خانه در جهت عمود بر آن). تنها مهرهای است که میتواند از روی مهرههای دیگر بپرد.
- شاه (King): یک خانه در هر جهت (هشت خانه اطراف خود) حرکت میکند.
۶. کلاس Move (نمایش و مدیریت حرکات): این کلاس تمام اطلاعات مربوط به یک حرکت شطرنج را encapsulate میکند:
- مختصات شروع و پایان
- مهره حرکت داده شده و مهره captured (در صورت وجود)
- آیا حرکت یک ارتقاء سرباز است یا خیر
- یک شناسه یکتا برای مقایسه حرکات
- متدهایی برای تبدیل مختصات عددی به نوتاسیون شطرنج (مانند (0,0) به "a8")
۷. سیستم نوتاسیون شطرنج: کلاس Move شامل دیکشنریهایی برای تبدیل بین سیستم مختصات عددی (0-7 برای سطر و ستون) و نوتاسیون استاندارد شطرنج (a-h برای ستونها و 1-8 برای سطرها) است. این امکان نمایش حرکات به فرمت قابل خواندن مانند "e2e4" را فراهم میکند.
۸. ارتقاء سرباز: زمانی که یک سرباز به آخرین ردیف صفحه
حریف میرسد، به طور خودکار به وزیر ارتقاء مییابد. این منطق در متد
makeMove و سازنده کلاس Move پیادهسازی شده است.
نحوه اجرا
برای اجرای بازی ابتدا مطمئن شوید تمام فایلها در پوشه مناسب قرار دارند، سپس فایل اصلی را اجرا کنید:
python ChessGame.py
کنترلهای بازی:
- کلیک ماوس برای انتخاب و حرکت مهرهها
- Ctrl+Z برای بازگشت به حرکت قبلی
- کلید R برای بازنشانی بازی
توسعه و بهبود
ایدههایی برای توسعه بیشتر بازی:
- افزودن هوش مصنوعی برای بازی کامپیوتر
- قابلیت ذخیره و بارگذاری بازی
- افزودن تایمر برای هر بازیکن
- پشتیبانی از حرکات ویژه (قلعهگیری، آنپاسان)
- افزودن صدا و موسیقی متن
- ایجاد حالتهای مختلف بازی
- قابلیت بازی شبکهای
خلاصه پروژه
این پروژه یک پیادهسازی کامل و کاربردی از بازی شطرنج است که با پایتون و Pygame توسعه یافته است. ویژگیهای برجسته این پروژه شامل:
- معماری تمیز با جداسازی منطق بازی و رابط کاربری
- پیادهسازی کامل تمام حرکات قانونی شطرنج
- سیستم تشخیص کیش، مات و پات
- انیمیشنهای روان برای حرکات مهرهها
- رابط کاربری intuitive با هایلایت خانههای قابل حرکت
- قابلیتهای undo و reset بازی
- مدیریت صحیح ارتقاء سرباز
- سیستم نوتاسیون شطرنج برای حرکات
- سیستم امتیازدهی و نمایش وضعیت بازی
این پروژه نمونهای عالی از ترکیب برنامهنویسی شیگرا، الگوریتمهای بازی و طراحی رابط کاربری در پایتون است.
منابع و مستندات
منبع :
https://greenchess.net/info.php?item=downloads
منبع :
https://www.pygame.org/docs/