Skip to content

Commit cd8c7ab

Browse files
Merge pull request #1249 from Arnav-arw/master
Added Connect 4 with Minimax algorithm
2 parents 3d0b995 + dab0d14 commit cd8c7ab

File tree

3 files changed

+299
-0
lines changed

3 files changed

+299
-0
lines changed
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
This is a single player connect 4 game which used [Minimax algorithm](https://en.wikipedia.org/wiki/Minimax).
2+
3+
Dependancies:
4+
1. Pygame 2.1.2
5+
2. Numpy 1.23.1
6+
7+
Game Rules:
8+
9+
Four consecutive balls should match of the same colour horizontally, vertically or diagonally (either of both) just like tic-tac-toe.
10+
That's it now u r ready to play the game :)
Lines changed: 287 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,287 @@
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)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
pygame == 2.1.2
2+
numpy = 1.23.1

0 commit comments

Comments
 (0)