├── README.md ├── connect4.py └── connect4_with_ai.py /README.md: -------------------------------------------------------------------------------- 1 | # connect_python 2 | Connect Four Game in Python 3 | -------------------------------------------------------------------------------- /connect4.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pygame 3 | import sys 4 | import math 5 | 6 | BLUE = (0,0,255) 7 | BLACK = (0,0,0) 8 | RED = (255,0,0) 9 | YELLOW = (255,255,0) 10 | 11 | ROW_COUNT = 6 12 | COLUMN_COUNT = 7 13 | 14 | def create_board(): 15 | board = np.zeros((ROW_COUNT,COLUMN_COUNT)) 16 | return board 17 | 18 | def drop_piece(board, row, col, piece): 19 | board[row][col] = piece 20 | 21 | def is_valid_location(board, col): 22 | return board[ROW_COUNT-1][col] == 0 23 | 24 | def get_next_open_row(board, col): 25 | for r in range(ROW_COUNT): 26 | if board[r][col] == 0: 27 | return r 28 | 29 | def print_board(board): 30 | print(np.flip(board, 0)) 31 | 32 | def winning_move(board, piece): 33 | # Check horizontal locations for win 34 | for c in range(COLUMN_COUNT-3): 35 | for r in range(ROW_COUNT): 36 | if board[r][c] == piece and board[r][c+1] == piece and board[r][c+2] == piece and board[r][c+3] == piece: 37 | return True 38 | 39 | # Check vertical locations for win 40 | for c in range(COLUMN_COUNT): 41 | for r in range(ROW_COUNT-3): 42 | if board[r][c] == piece and board[r+1][c] == piece and board[r+2][c] == piece and board[r+3][c] == piece: 43 | return True 44 | 45 | # Check positively sloped diaganols 46 | for c in range(COLUMN_COUNT-3): 47 | for r in range(ROW_COUNT-3): 48 | if board[r][c] == piece and board[r+1][c+1] == piece and board[r+2][c+2] == piece and board[r+3][c+3] == piece: 49 | return True 50 | 51 | # Check negatively sloped diaganols 52 | for c in range(COLUMN_COUNT-3): 53 | for r in range(3, ROW_COUNT): 54 | if board[r][c] == piece and board[r-1][c+1] == piece and board[r-2][c+2] == piece and board[r-3][c+3] == piece: 55 | return True 56 | 57 | def draw_board(board): 58 | for c in range(COLUMN_COUNT): 59 | for r in range(ROW_COUNT): 60 | pygame.draw.rect(screen, BLUE, (c*SQUARESIZE, r*SQUARESIZE+SQUARESIZE, SQUARESIZE, SQUARESIZE)) 61 | pygame.draw.circle(screen, BLACK, (int(c*SQUARESIZE+SQUARESIZE/2), int(r*SQUARESIZE+SQUARESIZE+SQUARESIZE/2)), RADIUS) 62 | 63 | for c in range(COLUMN_COUNT): 64 | for r in range(ROW_COUNT): 65 | if board[r][c] == 1: 66 | pygame.draw.circle(screen, RED, (int(c*SQUARESIZE+SQUARESIZE/2), height-int(r*SQUARESIZE+SQUARESIZE/2)), RADIUS) 67 | elif board[r][c] == 2: 68 | pygame.draw.circle(screen, YELLOW, (int(c*SQUARESIZE+SQUARESIZE/2), height-int(r*SQUARESIZE+SQUARESIZE/2)), RADIUS) 69 | pygame.display.update() 70 | 71 | 72 | board = create_board() 73 | print_board(board) 74 | game_over = False 75 | turn = 0 76 | 77 | pygame.init() 78 | 79 | SQUARESIZE = 100 80 | 81 | width = COLUMN_COUNT * SQUARESIZE 82 | height = (ROW_COUNT+1) * SQUARESIZE 83 | 84 | size = (width, height) 85 | 86 | RADIUS = int(SQUARESIZE/2 - 5) 87 | 88 | screen = pygame.display.set_mode(size) 89 | draw_board(board) 90 | pygame.display.update() 91 | 92 | myfont = pygame.font.SysFont("monospace", 75) 93 | 94 | while not game_over: 95 | 96 | for event in pygame.event.get(): 97 | if event.type == pygame.QUIT: 98 | sys.exit() 99 | 100 | if event.type == pygame.MOUSEMOTION: 101 | pygame.draw.rect(screen, BLACK, (0,0, width, SQUARESIZE)) 102 | posx = event.pos[0] 103 | if turn == 0: 104 | pygame.draw.circle(screen, RED, (posx, int(SQUARESIZE/2)), RADIUS) 105 | else: 106 | pygame.draw.circle(screen, YELLOW, (posx, int(SQUARESIZE/2)), RADIUS) 107 | pygame.display.update() 108 | 109 | if event.type == pygame.MOUSEBUTTONDOWN: 110 | pygame.draw.rect(screen, BLACK, (0,0, width, SQUARESIZE)) 111 | #print(event.pos) 112 | # Ask for Player 1 Input 113 | if turn == 0: 114 | posx = event.pos[0] 115 | col = int(math.floor(posx/SQUARESIZE)) 116 | 117 | if is_valid_location(board, col): 118 | row = get_next_open_row(board, col) 119 | drop_piece(board, row, col, 1) 120 | 121 | if winning_move(board, 1): 122 | label = myfont.render("Player 1 wins!!", 1, RED) 123 | screen.blit(label, (40,10)) 124 | game_over = True 125 | 126 | 127 | # # Ask for Player 2 Input 128 | else: 129 | posx = event.pos[0] 130 | col = int(math.floor(posx/SQUARESIZE)) 131 | 132 | if is_valid_location(board, col): 133 | row = get_next_open_row(board, col) 134 | drop_piece(board, row, col, 2) 135 | 136 | if winning_move(board, 2): 137 | label = myfont.render("Player 2 wins!!", 1, YELLOW) 138 | screen.blit(label, (40,10)) 139 | game_over = True 140 | 141 | print_board(board) 142 | draw_board(board) 143 | 144 | turn += 1 145 | turn = turn % 2 146 | 147 | if game_over: 148 | pygame.time.wait(3000) -------------------------------------------------------------------------------- /connect4_with_ai.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import random 3 | import pygame 4 | import sys 5 | import math 6 | 7 | BLUE = (0,0,255) 8 | BLACK = (0,0,0) 9 | RED = (255,0,0) 10 | YELLOW = (255,255,0) 11 | 12 | ROW_COUNT = 6 13 | COLUMN_COUNT = 7 14 | 15 | PLAYER = 0 16 | AI = 1 17 | 18 | EMPTY = 0 19 | PLAYER_PIECE = 1 20 | AI_PIECE = 2 21 | 22 | WINDOW_LENGTH = 4 23 | 24 | def create_board(): 25 | board = np.zeros((ROW_COUNT,COLUMN_COUNT)) 26 | return board 27 | 28 | def drop_piece(board, row, col, piece): 29 | board[row][col] = piece 30 | 31 | def is_valid_location(board, col): 32 | return board[ROW_COUNT-1][col] == 0 33 | 34 | def get_next_open_row(board, col): 35 | for r in range(ROW_COUNT): 36 | if board[r][col] == 0: 37 | return r 38 | 39 | def print_board(board): 40 | print(np.flip(board, 0)) 41 | 42 | def winning_move(board, piece): 43 | # Check horizontal locations for win 44 | for c in range(COLUMN_COUNT-3): 45 | for r in range(ROW_COUNT): 46 | if board[r][c] == piece and board[r][c+1] == piece and board[r][c+2] == piece and board[r][c+3] == piece: 47 | return True 48 | 49 | # Check vertical locations for win 50 | for c in range(COLUMN_COUNT): 51 | for r in range(ROW_COUNT-3): 52 | if board[r][c] == piece and board[r+1][c] == piece and board[r+2][c] == piece and board[r+3][c] == piece: 53 | return True 54 | 55 | # Check positively sloped diaganols 56 | for c in range(COLUMN_COUNT-3): 57 | for r in range(ROW_COUNT-3): 58 | if board[r][c] == piece and board[r+1][c+1] == piece and board[r+2][c+2] == piece and board[r+3][c+3] == piece: 59 | return True 60 | 61 | # Check negatively sloped diaganols 62 | for c in range(COLUMN_COUNT-3): 63 | for r in range(3, ROW_COUNT): 64 | if board[r][c] == piece and board[r-1][c+1] == piece and board[r-2][c+2] == piece and board[r-3][c+3] == piece: 65 | return True 66 | 67 | def evaluate_window(window, piece): 68 | score = 0 69 | opp_piece = PLAYER_PIECE 70 | if piece == PLAYER_PIECE: 71 | opp_piece = AI_PIECE 72 | 73 | if window.count(piece) == 4: 74 | score += 100 75 | elif window.count(piece) == 3 and window.count(EMPTY) == 1: 76 | score += 5 77 | elif window.count(piece) == 2 and window.count(EMPTY) == 2: 78 | score += 2 79 | 80 | if window.count(opp_piece) == 3 and window.count(EMPTY) == 1: 81 | score -= 4 82 | 83 | return score 84 | 85 | def score_position(board, piece): 86 | score = 0 87 | 88 | ## Score center column 89 | center_array = [int(i) for i in list(board[:, COLUMN_COUNT//2])] 90 | center_count = center_array.count(piece) 91 | score += center_count * 3 92 | 93 | ## Score Horizontal 94 | for r in range(ROW_COUNT): 95 | row_array = [int(i) for i in list(board[r,:])] 96 | for c in range(COLUMN_COUNT-3): 97 | window = row_array[c:c+WINDOW_LENGTH] 98 | score += evaluate_window(window, piece) 99 | 100 | ## Score Vertical 101 | for c in range(COLUMN_COUNT): 102 | col_array = [int(i) for i in list(board[:,c])] 103 | for r in range(ROW_COUNT-3): 104 | window = col_array[r:r+WINDOW_LENGTH] 105 | score += evaluate_window(window, piece) 106 | 107 | ## Score posiive sloped diagonal 108 | for r in range(ROW_COUNT-3): 109 | for c in range(COLUMN_COUNT-3): 110 | window = [board[r+i][c+i] for i in range(WINDOW_LENGTH)] 111 | score += evaluate_window(window, piece) 112 | 113 | for r in range(ROW_COUNT-3): 114 | for c in range(COLUMN_COUNT-3): 115 | window = [board[r+3-i][c+i] for i in range(WINDOW_LENGTH)] 116 | score += evaluate_window(window, piece) 117 | 118 | return score 119 | 120 | def is_terminal_node(board): 121 | return winning_move(board, PLAYER_PIECE) or winning_move(board, AI_PIECE) or len(get_valid_locations(board)) == 0 122 | 123 | def minimax(board, depth, alpha, beta, maximizingPlayer): 124 | valid_locations = get_valid_locations(board) 125 | is_terminal = is_terminal_node(board) 126 | if depth == 0 or is_terminal: 127 | if is_terminal: 128 | if winning_move(board, AI_PIECE): 129 | return (None, 100000000000000) 130 | elif winning_move(board, PLAYER_PIECE): 131 | return (None, -10000000000000) 132 | else: # Game is over, no more valid moves 133 | return (None, 0) 134 | else: # Depth is zero 135 | return (None, score_position(board, AI_PIECE)) 136 | if maximizingPlayer: 137 | value = -math.inf 138 | column = random.choice(valid_locations) 139 | for col in valid_locations: 140 | row = get_next_open_row(board, col) 141 | b_copy = board.copy() 142 | drop_piece(b_copy, row, col, AI_PIECE) 143 | new_score = minimax(b_copy, depth-1, alpha, beta, False)[1] 144 | if new_score > value: 145 | value = new_score 146 | column = col 147 | alpha = max(alpha, value) 148 | if alpha >= beta: 149 | break 150 | return column, value 151 | 152 | else: # Minimizing player 153 | value = math.inf 154 | column = random.choice(valid_locations) 155 | for col in valid_locations: 156 | row = get_next_open_row(board, col) 157 | b_copy = board.copy() 158 | drop_piece(b_copy, row, col, PLAYER_PIECE) 159 | new_score = minimax(b_copy, depth-1, alpha, beta, True)[1] 160 | if new_score < value: 161 | value = new_score 162 | column = col 163 | beta = min(beta, value) 164 | if alpha >= beta: 165 | break 166 | return column, value 167 | 168 | def get_valid_locations(board): 169 | valid_locations = [] 170 | for col in range(COLUMN_COUNT): 171 | if is_valid_location(board, col): 172 | valid_locations.append(col) 173 | return valid_locations 174 | 175 | def pick_best_move(board, piece): 176 | 177 | valid_locations = get_valid_locations(board) 178 | best_score = -10000 179 | best_col = random.choice(valid_locations) 180 | for col in valid_locations: 181 | row = get_next_open_row(board, col) 182 | temp_board = board.copy() 183 | drop_piece(temp_board, row, col, piece) 184 | score = score_position(temp_board, piece) 185 | if score > best_score: 186 | best_score = score 187 | best_col = col 188 | 189 | return best_col 190 | 191 | def draw_board(board): 192 | for c in range(COLUMN_COUNT): 193 | for r in range(ROW_COUNT): 194 | pygame.draw.rect(screen, BLUE, (c*SQUARESIZE, r*SQUARESIZE+SQUARESIZE, SQUARESIZE, SQUARESIZE)) 195 | pygame.draw.circle(screen, BLACK, (int(c*SQUARESIZE+SQUARESIZE/2), int(r*SQUARESIZE+SQUARESIZE+SQUARESIZE/2)), RADIUS) 196 | 197 | for c in range(COLUMN_COUNT): 198 | for r in range(ROW_COUNT): 199 | if board[r][c] == PLAYER_PIECE: 200 | pygame.draw.circle(screen, RED, (int(c*SQUARESIZE+SQUARESIZE/2), height-int(r*SQUARESIZE+SQUARESIZE/2)), RADIUS) 201 | elif board[r][c] == AI_PIECE: 202 | pygame.draw.circle(screen, YELLOW, (int(c*SQUARESIZE+SQUARESIZE/2), height-int(r*SQUARESIZE+SQUARESIZE/2)), RADIUS) 203 | pygame.display.update() 204 | 205 | board = create_board() 206 | print_board(board) 207 | game_over = False 208 | 209 | pygame.init() 210 | 211 | SQUARESIZE = 100 212 | 213 | width = COLUMN_COUNT * SQUARESIZE 214 | height = (ROW_COUNT+1) * SQUARESIZE 215 | 216 | size = (width, height) 217 | 218 | RADIUS = int(SQUARESIZE/2 - 5) 219 | 220 | screen = pygame.display.set_mode(size) 221 | draw_board(board) 222 | pygame.display.update() 223 | 224 | myfont = pygame.font.SysFont("monospace", 75) 225 | 226 | turn = random.randint(PLAYER, AI) 227 | 228 | while not game_over: 229 | 230 | for event in pygame.event.get(): 231 | if event.type == pygame.QUIT: 232 | sys.exit() 233 | 234 | if event.type == pygame.MOUSEMOTION: 235 | pygame.draw.rect(screen, BLACK, (0,0, width, SQUARESIZE)) 236 | posx = event.pos[0] 237 | if turn == PLAYER: 238 | pygame.draw.circle(screen, RED, (posx, int(SQUARESIZE/2)), RADIUS) 239 | 240 | pygame.display.update() 241 | 242 | if event.type == pygame.MOUSEBUTTONDOWN: 243 | pygame.draw.rect(screen, BLACK, (0,0, width, SQUARESIZE)) 244 | #print(event.pos) 245 | # Ask for Player 1 Input 246 | if turn == PLAYER: 247 | posx = event.pos[0] 248 | col = int(math.floor(posx/SQUARESIZE)) 249 | 250 | if is_valid_location(board, col): 251 | row = get_next_open_row(board, col) 252 | drop_piece(board, row, col, PLAYER_PIECE) 253 | 254 | if winning_move(board, PLAYER_PIECE): 255 | label = myfont.render("Player 1 wins!!", 1, RED) 256 | screen.blit(label, (40,10)) 257 | game_over = True 258 | 259 | turn += 1 260 | turn = turn % 2 261 | 262 | print_board(board) 263 | draw_board(board) 264 | 265 | 266 | # # Ask for Player 2 Input 267 | if turn == AI and not game_over: 268 | 269 | #col = random.randint(0, COLUMN_COUNT-1) 270 | #col = pick_best_move(board, AI_PIECE) 271 | col, minimax_score = minimax(board, 5, -math.inf, math.inf, True) 272 | 273 | if is_valid_location(board, col): 274 | #pygame.time.wait(500) 275 | row = get_next_open_row(board, col) 276 | drop_piece(board, row, col, AI_PIECE) 277 | 278 | if winning_move(board, AI_PIECE): 279 | label = myfont.render("Player 2 wins!!", 1, YELLOW) 280 | screen.blit(label, (40,10)) 281 | game_over = True 282 | 283 | print_board(board) 284 | draw_board(board) 285 | 286 | turn += 1 287 | turn = turn % 2 288 | 289 | if game_over: 290 | pygame.time.wait(3000) --------------------------------------------------------------------------------