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