feat: add chess

This commit is contained in:
Nemo D'ACREMONT 2025-05-07 21:32:52 +02:00
parent 9aa93ffb79
commit 5f28800dbb
No known key found for this signature in database
GPG Key ID: 85F245EC3BB1E022
11 changed files with 363 additions and 0 deletions

3
chess/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
__pycache__
/venv
/.venv

0
chess/.gitkeep Normal file
View File

8
chess/README.md Normal file
View File

@ -0,0 +1,8 @@
# Chess TP
## VENV ACTIVATE
```
python -m venv venv
. ./venv/bin/activate
```

49
chess/heuristics.py Normal file
View File

@ -0,0 +1,49 @@
import chess
import math
def b(board: chess.Board, color: chess.Color):
piece_values = {
chess.PAWN: 1,
chess.KNIGHT: 3,
chess.BISHOP: 3,
chess.ROOK: 5,
chess.QUEEN: 9,
chess.KING: 0
}
score = 0
for square in chess.SQUARES:
piece = board.piece_at(square)
if piece is not None:
if piece.color == chess.WHITE:
score += piece_values[piece.piece_type]
else:
score -= piece_values[piece.piece_type]
return score
def h_shannon(board: chess.Board, color: chess.Color):
piece_values = {
chess.PAWN: 1,
chess.KNIGHT: 3,
chess.BISHOP: 3,
chess.ROOK: 5,
chess.QUEEN: 9,
chess.KING: 0
}
acc = 0
outcome = board.outcome()
if board.is_checkmate() and outcome is not None:
winner = outcome.winner
return math.inf if winner is not None and winner == color else -math.inf
if board.is_game_over():
return 0
for square in board.piece_map():
piece = board.piece_at(square)
if piece is not None:
n = 1 if piece.color == color else -1
acc += n * piece_values[piece.piece_type]
return acc

216
chess/player.py Normal file
View File

@ -0,0 +1,216 @@
from random import randint, choice
import math
from typing import Callable
import chess
from heuristics import h_shannon
class PlayerInterface:
board: chess.Board
color: chess.Color
# Returns your player name , as to be displayed during the game
def getPlayerName(self) -> str:
return " Not Defined "
# Returns your move. The move must be a valid string of coordinates ("A1",
# "D5", ...) on the grid or the special "PASS" move. A couple of two integers ,
# which are the coordinates of where you want to put your piece on the board .
# Coordinates are the coordinates given by the Goban . py method legal_moves ().
def getPlayerMove(self) -> chess.Move:
return chess.Move.null()
# Inform you that the oponent has played this move. You must play it with no
# search (just update your local variables to take it into account)
def playOpponentMove(self, move):
self.board.push(move)
# Starts a new game , and give you your color . As defined in Goban . py : color =1
# for BLACK , and color =2 for WHITE
def newGame(self, color):
self.board = chess.Board()
self.color = color
# You can get a feedback on the winner
# This function gives you the color of the winner
def endGame(self, color):
pass
class RandomPlayer(PlayerInterface):
# Returns your player name , as to be displayed during the game
def getPlayerName(self) -> str:
return "Random Player"
def playOpponentMove(self, move):
self.board.push(move)
def getPlayerMove(self):
"""
Renvoie un mouvement au hasard sur la liste des mouvements possibles. Pour avoir un choix au hasard, il faut
construire explicitement tous les mouvements. Or, generate_legal_moves() nous donne un itérateur.
"""
move = choice([m for m in self.board.generate_legal_moves()])
self.board.push(move)
return move
def minmax(
board: chess.Board,
heuristic: Callable[[chess.Board, chess.Color], float],
color: chess.Color,
depth: int = 3,
) -> chess.Move:
def aux(
board: chess.Board,
heuristic: Callable[[chess.Board, chess.Color], float],
color: chess.Color,
depth: int = 3,
) -> tuple[float, chess.Move]:
wantMax = board.turn == color
if depth == 0 or board.is_game_over():
return heuristic(board, color), board.peek()
if wantMax:
acc = -math.inf, chess.Move.null()
for move in board.generate_legal_moves():
board.push(move)
acc = max(
acc,
(
aux(board, heuristic=heuristic, color=color, depth=depth - 1)[
0
],
move,
),
key=lambda t: t[0],
)
board.pop()
else: # minimizing player
acc = math.inf, chess.Move.null()
for move in board.generate_legal_moves():
board.push(move)
acc = min(
acc,
(
aux(board, heuristic=heuristic, color=color, depth=depth - 1)[
0
],
move,
),
key=lambda t: t[0],
)
board.pop()
return acc
_, move = aux(board, heuristic, color, depth)
return move
class MinMaxPlayer(PlayerInterface):
depth = 3
# Returns your player name , as to be displayed during the game
def getPlayerName(self) -> str:
return "MinMax Player"
def getPlayerMove(self):
move = minmax(
self.board, heuristic=h_shannon, color=self.color, depth=self.depth
)
self.board.push(move)
return move
def alphabeta(
board: chess.Board,
heuristic: Callable[[chess.Board, chess.Color], float],
color: chess.Color,
depth: int = 3,
) -> chess.Move:
def aux(
board: chess.Board,
heuristic: Callable[[chess.Board, chess.Color], float],
color: chess.Color,
alpha=-math.inf,
beta=math.inf,
depth: int = 3,
) -> tuple[float, chess.Move]:
wantMax = board.turn == color
if depth == 0 or board.is_game_over():
return heuristic(board, color), board.peek()
if wantMax:
acc = -math.inf, chess.Move.null()
for move in board.generate_legal_moves():
board.push(move)
value = (
aux(
board,
alpha=alpha,
beta=beta,
heuristic=heuristic,
color=color,
depth=depth - 1,
)[0],
move,
)
acc = max(
acc,
value,
key=lambda t: t[0],
)
board.pop()
if acc[0] >= beta:
break # beta cutoff
alpha = max(alpha, value[0])
else:
acc = math.inf, chess.Move.null()
for move in board.generate_legal_moves():
board.push(move)
value = (
aux(
board,
alpha=alpha,
beta=beta,
heuristic=heuristic,
color=color,
depth=depth - 1,
)[0],
move,
)
acc = min(
acc,
value,
key=lambda t: t[0],
)
board.pop()
if acc[0] <= alpha:
break # alpha cutoff
beta = min(beta, value[0])
return acc
_, move = aux(board, heuristic=heuristic, color=color, depth=depth)
return move
class AlphaBetaPlayer(PlayerInterface):
depth = 5
# Returns your player name , as to be displayed during the game
def getPlayerName(self) -> str:
return "AlphaBeta Player"
def getPlayerMove(self):
move = alphabeta(
self.board, heuristic=h_shannon, color=self.color, depth=self.depth
)
self.board.push(move)
return move

1
chess/requirements.txt Normal file
View File

@ -0,0 +1 @@
chess

80
chess/starter-chess.py Executable file
View File

@ -0,0 +1,80 @@
#!python
import sys
import time
import chess
import player
def color2str(color: chess.Color):
return "WHITE" if color is chess.WHITE else "BLACK"
def play(white: player.PlayerInterface, black: player.PlayerInterface):
"""
Déroulement d'une partie d'échecs au hasard des coups possibles. Cela va donner presque exclusivement
des parties très longues et sans gagnant. Cela illustre cependant comment on peut jouer avec la librairie
très simplement.
"""
def step(board: chess.Board, current: player.PlayerInterface, opponent: player.PlayerInterface):
move = current.getPlayerMove()
assert move is not chess.Move.null()
board.push(move)
opponent.playOpponentMove(move)
return move
board = chess.Board()
white.newGame(chess.WHITE)
black.newGame(chess.BLACK)
i = 1
while not board.is_game_over():
try:
if board.turn == chess.WHITE:
move = step(board, white, black)
else:
move = step(board, black, white)
except:
print(board)
print(f"{color2str(board.turn)} cheated, end the game")
print(sys.exception())
break;
print("----------------")
print(board)
print(f"Move {move}")
print(f"Turn {i}")
i += 1
print(board.outcome())
return board
def enumerate_moves(board: chess.Board, depth=1):
if depth == 0:
return []
boards = []
for move in board.generate_legal_moves():
board.push(move)
boards.append(board)
boards += enumerate_moves(board, depth - 1)
board.pop()
return boards
def t_enum(board, depth):
st = time.time()
n = len(enumerate_moves(board, depth=depth))
nd = time.time()
print(f"{nd-st:.6f} {n}")
if __name__ == "__main__":
black = player.RandomPlayer()
white = player.AlphaBetaPlayer()
board = play(white, black)
print(board)

3
go_ia/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
__pycache__
/venv
/.venv

0
go_ia/.gitkeep Normal file
View File

3
go_player/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
__pycache__
/venv
/.venv

0
go_player/.gitkeep Normal file
View File