├── __init__.py ├── requirements.txt ├── checkers ├── __init__.py ├── __pycache__ │ ├── board.cpython-38.pyc │ ├── game.cpython-38.pyc │ ├── piece.cpython-38.pyc │ ├── __init__.cpython-38.pyc │ └── constants.cpython-38.pyc ├── constants.py ├── piece.py ├── game.py └── board.py ├── minimax ├── __init__.py └── algorithm.py ├── assets └── crown.png ├── README.md └── main.py /__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /checkers/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /minimax/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /assets/crown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techwithtim/Python-Checkers-AI/HEAD/assets/crown.png -------------------------------------------------------------------------------- /checkers/__pycache__/board.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techwithtim/Python-Checkers-AI/HEAD/checkers/__pycache__/board.cpython-38.pyc -------------------------------------------------------------------------------- /checkers/__pycache__/game.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techwithtim/Python-Checkers-AI/HEAD/checkers/__pycache__/game.cpython-38.pyc -------------------------------------------------------------------------------- /checkers/__pycache__/piece.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techwithtim/Python-Checkers-AI/HEAD/checkers/__pycache__/piece.cpython-38.pyc -------------------------------------------------------------------------------- /checkers/__pycache__/__init__.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techwithtim/Python-Checkers-AI/HEAD/checkers/__pycache__/__init__.cpython-38.pyc -------------------------------------------------------------------------------- /checkers/__pycache__/constants.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techwithtim/Python-Checkers-AI/HEAD/checkers/__pycache__/constants.cpython-38.pyc -------------------------------------------------------------------------------- /checkers/constants.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | 3 | WIDTH, HEIGHT = 800, 800 4 | ROWS, COLS = 8, 8 5 | SQUARE_SIZE = WIDTH//COLS 6 | 7 | # rgb 8 | RED = (255, 0, 0) 9 | WHITE = (255, 255, 255) 10 | BLACK = (0, 0, 0) 11 | BLUE = (0, 0, 255) 12 | GREY = (128,128,128) 13 | 14 | CROWN = pygame.transform.scale(pygame.image.load('assets/crown.png'), (44, 25)) 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Python-Checkers-AI 2 | A checkers AI using the minimax algorithm. 3 | 4 | 5 | # 💻 Launch Your Software Development Career Today! 6 | 7 | 🎓 **No degree? No problem!** My program equips you with everything you need to break into tech and land an entry-level software development role. 8 | 9 | 🚀 **Why Join?** 10 | - 💼 **$70k+ starting salary potential** 11 | - 🕐 **Self-paced:** Complete on your own time 12 | - 🤑 **Affordable:** Low risk compared to expensive bootcamps or degrees 13 | - 🎯 **45,000+ job openings** in the market 14 | 15 | 👉 **[Start your journey today!](https://techwithtim.net/dev)** 16 | No experience needed—just your determination. Future-proof your career and unlock six-figure potential like many of our students have! 17 | -------------------------------------------------------------------------------- /checkers/piece.py: -------------------------------------------------------------------------------- 1 | from .constants import RED, WHITE, SQUARE_SIZE, GREY, CROWN 2 | import pygame 3 | 4 | class Piece: 5 | PADDING = 15 6 | OUTLINE = 2 7 | 8 | def __init__(self, row, col, color): 9 | self.row = row 10 | self.col = col 11 | self.color = color 12 | self.king = False 13 | self.x = 0 14 | self.y = 0 15 | self.calc_pos() 16 | 17 | def calc_pos(self): 18 | self.x = SQUARE_SIZE * self.col + SQUARE_SIZE // 2 19 | self.y = SQUARE_SIZE * self.row + SQUARE_SIZE // 2 20 | 21 | def make_king(self): 22 | self.king = True 23 | 24 | def draw(self, win): 25 | radius = SQUARE_SIZE//2 - self.PADDING 26 | pygame.draw.circle(win, GREY, (self.x, self.y), radius + self.OUTLINE) 27 | pygame.draw.circle(win, self.color, (self.x, self.y), radius) 28 | if self.king: 29 | win.blit(CROWN, (self.x - CROWN.get_width()//2, self.y - CROWN.get_height()//2)) 30 | 31 | def move(self, row, col): 32 | self.row = row 33 | self.col = col 34 | self.calc_pos() 35 | 36 | def __repr__(self): 37 | return str(self.color) -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | # Assets: https://techwithtim.net/wp-content/uploads/2020/09/assets.zip 2 | import pygame 3 | from checkers.constants import WIDTH, HEIGHT, SQUARE_SIZE, RED, WHITE 4 | from checkers.game import Game 5 | from minimax.algorithm import minimax 6 | 7 | FPS = 60 8 | 9 | WIN = pygame.display.set_mode((WIDTH, HEIGHT)) 10 | pygame.display.set_caption('Checkers') 11 | 12 | def get_row_col_from_mouse(pos): 13 | x, y = pos 14 | row = y // SQUARE_SIZE 15 | col = x // SQUARE_SIZE 16 | return row, col 17 | 18 | def main(): 19 | run = True 20 | clock = pygame.time.Clock() 21 | game = Game(WIN) 22 | 23 | while run: 24 | clock.tick(FPS) 25 | 26 | if game.turn == WHITE: 27 | value, new_board = minimax(game.get_board(), 4, WHITE, game) 28 | game.ai_move(new_board) 29 | 30 | if game.winner() != None: 31 | print(game.winner()) 32 | run = False 33 | 34 | for event in pygame.event.get(): 35 | if event.type == pygame.QUIT: 36 | run = False 37 | 38 | if event.type == pygame.MOUSEBUTTONDOWN: 39 | pos = pygame.mouse.get_pos() 40 | row, col = get_row_col_from_mouse(pos) 41 | game.select(row, col) 42 | 43 | game.update() 44 | 45 | pygame.quit() 46 | 47 | main() -------------------------------------------------------------------------------- /minimax/algorithm.py: -------------------------------------------------------------------------------- 1 | from copy import deepcopy 2 | import pygame 3 | 4 | RED = (255,0,0) 5 | WHITE = (255, 255, 255) 6 | 7 | def minimax(position, depth, max_player, game): 8 | if depth == 0 or position.winner() != None: 9 | return position.evaluate(), position 10 | 11 | if max_player: 12 | maxEval = float('-inf') 13 | best_move = None 14 | for move in get_all_moves(position, WHITE, game): 15 | evaluation = minimax(move, depth-1, False, game)[0] 16 | maxEval = max(maxEval, evaluation) 17 | if maxEval == evaluation: 18 | best_move = move 19 | 20 | return maxEval, best_move 21 | else: 22 | minEval = float('inf') 23 | best_move = None 24 | for move in get_all_moves(position, RED, game): 25 | evaluation = minimax(move, depth-1, True, game)[0] 26 | minEval = min(minEval, evaluation) 27 | if minEval == evaluation: 28 | best_move = move 29 | 30 | return minEval, best_move 31 | 32 | 33 | def simulate_move(piece, move, board, game, skip): 34 | board.move(piece, move[0], move[1]) 35 | if skip: 36 | board.remove(skip) 37 | 38 | return board 39 | 40 | 41 | def get_all_moves(board, color, game): 42 | moves = [] 43 | 44 | for piece in board.get_all_pieces(color): 45 | valid_moves = board.get_valid_moves(piece) 46 | for move, skip in valid_moves.items(): 47 | draw_moves(game, board, piece) 48 | temp_board = deepcopy(board) 49 | temp_piece = temp_board.get_piece(piece.row, piece.col) 50 | new_board = simulate_move(temp_piece, move, temp_board, game, skip) 51 | moves.append(new_board) 52 | 53 | return moves 54 | 55 | 56 | def draw_moves(game, board, piece): 57 | valid_moves = board.get_valid_moves(piece) 58 | board.draw(game.win) 59 | pygame.draw.circle(game.win, (0,255,0), (piece.x, piece.y), 50, 5) 60 | game.draw_valid_moves(valid_moves.keys()) 61 | pygame.display.update() 62 | #pygame.time.delay(100) 63 | 64 | -------------------------------------------------------------------------------- /checkers/game.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | from .constants import RED, WHITE, BLUE, SQUARE_SIZE 3 | from checkers.board import Board 4 | 5 | class Game: 6 | def __init__(self, win): 7 | self._init() 8 | self.win = win 9 | 10 | def update(self): 11 | self.board.draw(self.win) 12 | self.draw_valid_moves(self.valid_moves) 13 | pygame.display.update() 14 | 15 | def _init(self): 16 | self.selected = None 17 | self.board = Board() 18 | self.turn = RED 19 | self.valid_moves = {} 20 | 21 | def winner(self): 22 | return self.board.winner() 23 | 24 | def reset(self): 25 | self._init() 26 | 27 | def select(self, row, col): 28 | if self.selected: 29 | result = self._move(row, col) 30 | if not result: 31 | self.selected = None 32 | self.select(row, col) 33 | 34 | piece = self.board.get_piece(row, col) 35 | if piece != 0 and piece.color == self.turn: 36 | self.selected = piece 37 | self.valid_moves = self.board.get_valid_moves(piece) 38 | return True 39 | 40 | return False 41 | 42 | def _move(self, row, col): 43 | piece = self.board.get_piece(row, col) 44 | if self.selected and piece == 0 and (row, col) in self.valid_moves: 45 | self.board.move(self.selected, row, col) 46 | skipped = self.valid_moves[(row, col)] 47 | if skipped: 48 | self.board.remove(skipped) 49 | self.change_turn() 50 | else: 51 | return False 52 | 53 | return True 54 | 55 | def draw_valid_moves(self, moves): 56 | for move in moves: 57 | row, col = move 58 | pygame.draw.circle(self.win, BLUE, (col * SQUARE_SIZE + SQUARE_SIZE//2, row * SQUARE_SIZE + SQUARE_SIZE//2), 15) 59 | 60 | def change_turn(self): 61 | self.valid_moves = {} 62 | if self.turn == RED: 63 | self.turn = WHITE 64 | else: 65 | self.turn = RED 66 | 67 | def get_board(self): 68 | return self.board 69 | 70 | def ai_move(self, board): 71 | self.board = board 72 | self.change_turn() -------------------------------------------------------------------------------- /checkers/board.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | from .constants import BLACK, ROWS, RED, SQUARE_SIZE, COLS, WHITE 3 | from .piece import Piece 4 | 5 | class Board: 6 | def __init__(self): 7 | self.board = [] 8 | self.red_left = self.white_left = 12 9 | self.red_kings = self.white_kings = 0 10 | self.create_board() 11 | 12 | def draw_squares(self, win): 13 | win.fill(BLACK) 14 | for row in range(ROWS): 15 | for col in range(row % 2, COLS, 2): 16 | pygame.draw.rect(win, RED, (row*SQUARE_SIZE, col *SQUARE_SIZE, SQUARE_SIZE, SQUARE_SIZE)) 17 | 18 | def evaluate(self): 19 | return self.white_left - self.red_left + (self.white_kings * 0.5 - self.red_kings * 0.5) 20 | 21 | def get_all_pieces(self, color): 22 | pieces = [] 23 | for row in self.board: 24 | for piece in row: 25 | if piece != 0 and piece.color == color: 26 | pieces.append(piece) 27 | return pieces 28 | 29 | def move(self, piece, row, col): 30 | self.board[piece.row][piece.col], self.board[row][col] = self.board[row][col], self.board[piece.row][piece.col] 31 | piece.move(row, col) 32 | 33 | if row == ROWS - 1 or row == 0: 34 | piece.make_king() 35 | if piece.color == WHITE: 36 | self.white_kings += 1 37 | else: 38 | self.red_kings += 1 39 | 40 | def get_piece(self, row, col): 41 | return self.board[row][col] 42 | 43 | def create_board(self): 44 | for row in range(ROWS): 45 | self.board.append([]) 46 | for col in range(COLS): 47 | if col % 2 == ((row + 1) % 2): 48 | if row < 3: 49 | self.board[row].append(Piece(row, col, WHITE)) 50 | elif row > 4: 51 | self.board[row].append(Piece(row, col, RED)) 52 | else: 53 | self.board[row].append(0) 54 | else: 55 | self.board[row].append(0) 56 | 57 | def draw(self, win): 58 | self.draw_squares(win) 59 | for row in range(ROWS): 60 | for col in range(COLS): 61 | piece = self.board[row][col] 62 | if piece != 0: 63 | piece.draw(win) 64 | 65 | def remove(self, pieces): 66 | for piece in pieces: 67 | self.board[piece.row][piece.col] = 0 68 | if piece != 0: 69 | if piece.color == RED: 70 | self.red_left -= 1 71 | else: 72 | self.white_left -= 1 73 | 74 | def winner(self): 75 | if self.red_left <= 0: 76 | return WHITE 77 | elif self.white_left <= 0: 78 | return RED 79 | 80 | return None 81 | 82 | def get_valid_moves(self, piece): 83 | moves = {} 84 | left = piece.col - 1 85 | right = piece.col + 1 86 | row = piece.row 87 | 88 | if piece.color == RED or piece.king: 89 | moves.update(self._traverse_left(row -1, max(row-3, -1), -1, piece.color, left)) 90 | moves.update(self._traverse_right(row -1, max(row-3, -1), -1, piece.color, right)) 91 | if piece.color == WHITE or piece.king: 92 | moves.update(self._traverse_left(row +1, min(row+3, ROWS), 1, piece.color, left)) 93 | moves.update(self._traverse_right(row +1, min(row+3, ROWS), 1, piece.color, right)) 94 | 95 | return moves 96 | 97 | def _traverse_left(self, start, stop, step, color, left, skipped=[]): 98 | moves = {} 99 | last = [] 100 | for r in range(start, stop, step): 101 | if left < 0: 102 | break 103 | 104 | current = self.board[r][left] 105 | if current == 0: 106 | if skipped and not last: 107 | break 108 | elif skipped: 109 | moves[(r, left)] = last + skipped 110 | else: 111 | moves[(r, left)] = last 112 | 113 | if last: 114 | if step == -1: 115 | row = max(r-3, 0) 116 | else: 117 | row = min(r+3, ROWS) 118 | moves.update(self._traverse_left(r+step, row, step, color, left-1,skipped=last)) 119 | moves.update(self._traverse_right(r+step, row, step, color, left+1,skipped=last)) 120 | break 121 | elif current.color == color: 122 | break 123 | else: 124 | last = [current] 125 | 126 | left -= 1 127 | 128 | return moves 129 | 130 | def _traverse_right(self, start, stop, step, color, right, skipped=[]): 131 | moves = {} 132 | last = [] 133 | for r in range(start, stop, step): 134 | if right >= COLS: 135 | break 136 | 137 | current = self.board[r][right] 138 | if current == 0: 139 | if skipped and not last: 140 | break 141 | elif skipped: 142 | moves[(r,right)] = last + skipped 143 | else: 144 | moves[(r, right)] = last 145 | 146 | if last: 147 | if step == -1: 148 | row = max(r-3, 0) 149 | else: 150 | row = min(r+3, ROWS) 151 | moves.update(self._traverse_left(r+step, row, step, color, right-1,skipped=last)) 152 | moves.update(self._traverse_right(r+step, row, step, color, right+1,skipped=last)) 153 | break 154 | elif current.color == color: 155 | break 156 | else: 157 | last = [current] 158 | 159 | right += 1 160 | 161 | return moves --------------------------------------------------------------------------------