-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathgameplay.py
198 lines (160 loc) · 8.69 KB
/
gameplay.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
import random
from database import create_connection, select_board_state, insert_fresh_board_state
from selections import select_move
from settings import settings
def iterable_from_config(config):
return list(map(int, config))
def iterable_from_weights(weights):
return list(map(int, weights.split(',')))
def choose_next_play(opponent_name, opponent_char, current_board_config):
# look up current board state in database
current_board_state = select_board_state(opponent_name, opponent_char, current_board_config)
# if it's not there, add it and initialize its weights
if not current_board_state:
insert_fresh_board_state(opponent_name, opponent_char, current_board_config)
current_board_state = select_board_state(opponent_name, opponent_char, current_board_config)
config, weights, nexts = current_board_state
# select a next play based on the weights
play_selection = select_move(iterable_from_weights(weights))
# return the next play (the index where the opponent will play)
return play_selection
def choose_next_human_play(valid_plays, human_name, human_char, current_board_config, display_game=True, human_plays_randomly=False):
input_is_valid = False
while not input_is_valid:
if human_plays_randomly:
play_for_win = winning_play(current_board_config, human_char)
if play_for_win is not None:
player_input = play_for_win
else:
player_input = valid_plays[random.randint(0, len(valid_plays) - 1)]
else:
if display_game:
print('Your turn. Enter a number from 1 to 9 to indicate your play position. Q to quit')
print(f'Valid plays: {valid_plays}')
player_input = input()
if player_input == 'Q' or player_input == 'q':
print('Thanks for playing!')
exit(0)
if player_input not in valid_plays:
print('Invalid input.')
continue
# player input is 1-indexed, but the board config is 0-indexed
next_play = int(player_input) - 1
input_is_valid = True
return next_play
def play(placement, opponent_name, opponent_char, current_board_state):
"""Add a new play to the game
:param int placement: index of the play
:param str opponent_name: db identifier for opponent
:param str opponent_char: char used by opponent
:param current_board_state: iterable board position statuses"""
# print(f'@@@ playing at {placement + 1} character {opponent_char} @@@')
new_board_state = list(current_board_state)
# plays are 1-indexed, but board config is 0-indexed
new_board_state[placement] = opponent_char
return new_board_state
def game_is_over(current_board_state):
"""Check if the game is over
:param current_board_state: iterable board position statuses"""
# game is over if:
# - X wins
# - O wins
# - board is full
# There are 8 possible winning configurations for each character
# (3 rows, 3 columns, 2 diagonals)
if player_wins(current_board_state, 'X'):
return True
if player_wins(current_board_state, 'O'):
return True
if board_is_full(current_board_state):
return True
return False
def player_wins(current_board_state, player_char):
"""Check if the player wins
:param current_board_state: iterable board position statuses
:param str player_char: char used by player
:returns: bool"""
# There are 8 possible winning configurations for each character
# (3 rows, 3 columns, 2 diagonals)
if current_board_state[0] == current_board_state[1] == current_board_state[2] == player_char:
return True
if current_board_state[3] == current_board_state[4] == current_board_state[5] == player_char:
return True
if current_board_state[6] == current_board_state[7] == current_board_state[8] == player_char:
return True
if current_board_state[0] == current_board_state[3] == current_board_state[6] == player_char:
return True
if current_board_state[1] == current_board_state[4] == current_board_state[7] == player_char:
return True
if current_board_state[2] == current_board_state[5] == current_board_state[8] == player_char:
return True
if current_board_state[0] == current_board_state[4] == current_board_state[8] == player_char:
return True
if current_board_state[2] == current_board_state[4] == current_board_state[6] == player_char:
return True
return False
def board_is_full(current_board_state):
"""Check if the board is full
:param current_board_state: iterable board position statuses
:returns: bool"""
played_positions = [position for position in current_board_state if position != settings['blank_char']]
return len(played_positions) == 9
def game_is_drawn(current_board_state):
"""Check if the game is drawn
:param current_board_state: iterable board position statuses
:returns: bool"""
return board_is_full(current_board_state) and not player_wins(current_board_state, 'X') and not player_wins(
current_board_state, 'O')
def current_valid_plays(current_board_config):
"""Return a list of valid plays
:param current_board_config: iterable board position statuses
:returns: list"""
return [str(index + 1) for index, position in enumerate(current_board_config) if position == settings['blank_char']]
def winning_play(current_board_config, player_char):
"""If there's a play for player_char that will win the game, return it"""
# winning play is a row, column, or diagonal with 2 of player_char and 1 blank
# check first row
player_chars_in_row = len([position for position in current_board_config[0:3] if position == player_char])
if player_chars_in_row == 2 and settings['blank_char'] in current_board_config[0:3]:
blank_char_index = current_board_config[0:3].index(settings['blank_char'])
return str(blank_char_index + 0 + 1)
# check second row
player_chars_in_row = len([position for position in current_board_config[3:6] if position == player_char])
if player_chars_in_row == 2 and settings['blank_char'] in current_board_config[3:6]:
blank_char_index = current_board_config[3:6].index(settings['blank_char'])
return str(blank_char_index + 3 + 1)
# check third row
player_chars_in_row = len([position for position in current_board_config[6:9] if position == player_char])
if player_chars_in_row == 2 and settings['blank_char'] in current_board_config[6:9]:
blank_char_index = current_board_config[6:9].index(settings['blank_char'])
return str(blank_char_index + 6 + 1)
# check first column
player_chars_in_col = len([position for position in current_board_config[0:9:3] if position == player_char])
if player_chars_in_col == 2 and settings['blank_char'] in current_board_config[0:9:3]:
blank_char_index = current_board_config[0:9:3].index(settings['blank_char'])
return str(0 + blank_char_index * 3 + 1)
# check second column
player_chars_in_col = len([position for position in current_board_config[1:9:3] if position == player_char])
if player_chars_in_col == 2 and settings['blank_char'] in current_board_config[1:9:3]:
blank_char_index = current_board_config[1:9:3].index(settings['blank_char'])
return str(1 + blank_char_index * 3 + 1)
# check third column
player_chars_in_col = len([position for position in current_board_config[2:9:3] if position == player_char])
if player_chars_in_col == 2 and settings['blank_char'] in current_board_config[2:9:3]:
blank_char_index = current_board_config[2:9:3].index(settings['blank_char'])
return str(2 + blank_char_index * 3 + 1)
# check first diagonal
player_chars_in_diag = len([position for position in current_board_config[0:9:4] if position == player_char])
if player_chars_in_diag == 2 and settings['blank_char'] in current_board_config[0:9:4]:
blank_char_index = current_board_config[0:9:4].index(settings['blank_char'])
return str(0 + blank_char_index * 4 + 1)
# check second diagonal
player_chars_in_diag = len([position for position in current_board_config[2:7:2] if position == player_char])
if player_chars_in_diag == 2 and settings['blank_char'] in current_board_config[2:7:2]:
blank_char_index = current_board_config[2:7:2].index(settings['blank_char'])
return str(2 + blank_char_index * 2 + 1)
return None
# TODO: consider making a game state or gameplay state function?
# This would return "X wins", "Y wins", "draw", or "in progress"
# (matching a game state enum) and could be checked in the main loop.
# It's a pure function since it only checks state.