alperugurcan commited on
Commit
f5067fa
1 Parent(s): c78c7d0

Create game_logic.py

Browse files
Files changed (1) hide show
  1. game_logic.py +188 -0
game_logic.py ADDED
@@ -0,0 +1,188 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ class Configuration:
2
+ def __init__(self, config_dict):
3
+ self.rows = config_dict["rows"]
4
+ self.columns = config_dict["columns"]
5
+ self.inarow = config_dict["inarow"]
6
+
7
+ class Observation:
8
+ def __init__(self, obs_dict):
9
+ self.board = obs_dict["board"]
10
+ self.mark = obs_dict["mark"]
11
+
12
+ def my_agent(observation, configuration):
13
+ """
14
+ ConnectX agent using Minimax algorithm with alpha-beta pruning
15
+ Args:
16
+ observation: Current game state
17
+ configuration: Game configuration
18
+ Returns:
19
+ Column number (0-based) where to drop the piece
20
+ """
21
+ import numpy as np
22
+
23
+ # Constants
24
+ EMPTY = 0
25
+ MAX_DEPTH = 6 # Search depth limit
26
+ INFINITY = float('inf')
27
+
28
+ def make_board(obs):
29
+ """Convert observation to 2D numpy array"""
30
+ return np.asarray(obs.board).reshape(configuration.rows, configuration.columns)
31
+
32
+ def get_valid_moves(board):
33
+ """Get list of valid moves (columns that aren't full)"""
34
+ return [col for col in range(configuration.columns) if board[0][col] == EMPTY]
35
+
36
+ def drop_piece(board, col, piece):
37
+ """Drop piece in specified column and return row position"""
38
+ row = np.where(board[:, col] == EMPTY)[0][-1]
39
+ board[row, col] = piece
40
+ return row
41
+
42
+ def check_window(window, piece, inarow):
43
+ """
44
+ Score a window of positions
45
+ Higher scores for more pieces in a row and potential winning moves
46
+ Negative scores for opponent's threatening positions
47
+ """
48
+ score = 0
49
+ opp_piece = 1 if piece == 2 else 2
50
+
51
+ # Winning position
52
+ if np.count_nonzero(window == piece) == inarow:
53
+ score += 100
54
+ # One move away from winning
55
+ elif np.count_nonzero(window == piece) == (inarow - 1) and np.count_nonzero(window == EMPTY) == 1:
56
+ score += 10
57
+ # Two moves away from winning
58
+ elif np.count_nonzero(window == piece) == (inarow - 2) and np.count_nonzero(window == EMPTY) == 2:
59
+ score += 5
60
+
61
+ # Opponent one move away from winning - defensive move needed
62
+ if np.count_nonzero(window == opp_piece) == (inarow - 1) and np.count_nonzero(window == EMPTY) == 1:
63
+ score -= 80
64
+
65
+ return score
66
+
67
+ def score_position(board, piece):
68
+ """
69
+ Score entire board position
70
+ Considers horizontal, vertical, and diagonal possibilities
71
+ Extra weight for center column control
72
+ """
73
+ score = 0
74
+
75
+ # Horizontal windows
76
+ for row in range(configuration.rows):
77
+ for col in range(configuration.columns - (configuration.inarow - 1)):
78
+ window = board[row, col:col + configuration.inarow]
79
+ score += check_window(window, piece, configuration.inarow)
80
+
81
+ # Vertical windows
82
+ for row in range(configuration.rows - (configuration.inarow - 1)):
83
+ for col in range(configuration.columns):
84
+ window = board[row:row + configuration.inarow, col]
85
+ score += check_window(window, piece, configuration.inarow)
86
+
87
+ # Positive diagonal windows
88
+ for row in range(configuration.rows - (configuration.inarow - 1)):
89
+ for col in range(configuration.columns - (configuration.inarow - 1)):
90
+ window = [board[row + i][col + i] for i in range(configuration.inarow)]
91
+ score += check_window(window, piece, configuration.inarow)
92
+
93
+ # Negative diagonal windows
94
+ for row in range(configuration.inarow - 1, configuration.rows):
95
+ for col in range(configuration.columns - (configuration.inarow - 1)):
96
+ window = [board[row - i][col + i] for i in range(configuration.inarow)]
97
+ score += check_window(window, piece, configuration.inarow)
98
+
99
+ # Center column control bonus
100
+ center_array = board[:, configuration.columns//2]
101
+ center_count = np.count_nonzero(center_array == piece)
102
+ score += center_count * 6
103
+
104
+ return score
105
+
106
+ def is_terminal_node(board):
107
+ """Check if current position is terminal (game over)"""
108
+ # Check horizontal wins
109
+ for row in range(configuration.rows):
110
+ for col in range(configuration.columns - (configuration.inarow - 1)):
111
+ window = list(board[row, col:col + configuration.inarow])
112
+ if window.count(1) == configuration.inarow or window.count(2) == configuration.inarow:
113
+ return True
114
+
115
+ # Check vertical wins
116
+ for row in range(configuration.rows - (configuration.inarow - 1)):
117
+ for col in range(configuration.columns):
118
+ window = list(board[row:row + configuration.inarow, col])
119
+ if window.count(1) == configuration.inarow or window.count(2) == configuration.inarow:
120
+ return True
121
+
122
+ # Check if board is full
123
+ return len(get_valid_moves(board)) == 0
124
+
125
+ def minimax(board, depth, alpha, beta, maximizing_player):
126
+ """
127
+ Minimax algorithm with alpha-beta pruning
128
+ Returns best move and its score
129
+ """
130
+ valid_moves = get_valid_moves(board)
131
+ is_terminal = is_terminal_node(board)
132
+
133
+ # Base cases: max depth reached or terminal position
134
+ if depth == 0 or is_terminal:
135
+ if is_terminal:
136
+ return (None, -INFINITY if maximizing_player else INFINITY)
137
+ else:
138
+ return (None, score_position(board, observation.mark))
139
+
140
+ if maximizing_player:
141
+ value = -INFINITY
142
+ column = np.random.choice(valid_moves)
143
+ for col in valid_moves:
144
+ board_copy = board.copy()
145
+ drop_piece(board_copy, col, observation.mark)
146
+ new_score = minimax(board_copy, depth-1, alpha, beta, False)[1]
147
+ if new_score > value:
148
+ value = new_score
149
+ column = col
150
+ alpha = max(alpha, value)
151
+ if alpha >= beta:
152
+ break
153
+ return column, value
154
+
155
+ else:
156
+ value = INFINITY
157
+ column = np.random.choice(valid_moves)
158
+ opponent_piece = 1 if observation.mark == 2 else 2
159
+ for col in valid_moves:
160
+ board_copy = board.copy()
161
+ drop_piece(board_copy, col, opponent_piece)
162
+ new_score = minimax(board_copy, depth-1, alpha, beta, True)[1]
163
+ if new_score < value:
164
+ value = new_score
165
+ column = col
166
+ beta = min(beta, value)
167
+ if alpha >= beta:
168
+ break
169
+ return column, value
170
+
171
+ # Main game logic
172
+ board = make_board(observation)
173
+ valid_moves = get_valid_moves(board)
174
+
175
+ # First move: take center column
176
+ if len(np.where(board != 0)[0]) == 0:
177
+ return configuration.columns // 2
178
+
179
+ # Check for immediate winning moves
180
+ for col in valid_moves:
181
+ board_copy = board.copy()
182
+ drop_piece(board_copy, col, observation.mark)
183
+ if is_terminal_node(board_copy):
184
+ return col
185
+
186
+ # Use minimax to find best move
187
+ column, minimax_score = minimax(board, MAX_DEPTH, -INFINITY, INFINITY, True)
188
+ return column