Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit cd8c7ab

Browse files
Merge pull request avinashkranjan#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)

AltStyle によって変換されたページ (->オリジナル) /