Skip to content

Instantly share code, notes, and snippets.

@dltacube
Last active May 20, 2017 22:13
Show Gist options
  • Save dltacube/15e29f3d60d0fe66d1a1577a65e13c8b to your computer and use it in GitHub Desktop.
Save dltacube/15e29f3d60d0fe66d1a1577a65e13c8b to your computer and use it in GitHub Desktop.
from itertools import groupby
import os
from copy import deepcopy, copy
default_pos = [['-', '-', '-'], ['-', '-', '-'], ['-', '-', '-']]
class Board:
score = []
cpu = None
def __init__(self, positions=default_pos, row_move='', col_move=''):
self.pos = positions
self.row_move = row_move
self.col_move = col_move
self.getavailablemoves()
if row_move != '' and col_move != '':
self.update_pos(self.row_move, self.col_move)
o_plays, x_plays = 0, 0
# determine whose turn it is anytime a new board is created
for row in self.pos:
for col in row:
if col == 'O':
o_plays += 1
if col == 'X':
x_plays += 1
if x_plays > o_plays:
self.turn = 'O'
else:
self.turn = 'X'
self.winner = False
self.valid_moves = []
self.getavailablemoves()
self.check_for_winner()
def draw_board(self):
# clears the terminal
os.system('cls' if os.name == 'nt' else 'clear')
# line numbers
print('player ' + self.turn + "'s turn")
print(' ' * 3 + '0' + ' ' * 7 + '1' + ' ' * 7 + '2')
# board
print(' ' * 7 + '|' + ' ' * 7 + '|' + ' ' * 7)
print('0' + ' ' * 2 + self.pos[0][0] + ' ' * 3 + '|' + ' ' * 3 + self.pos[0][1] + ' ' * 3 + '|' + ' ' * 3 +
self.pos[0][2] +
' ' * 3)
print('_' * 7 + '|' + '_' * 7 + '|' + '_' * 7)
print(' ' * 7 + '|' + ' ' * 7 + '|' + ' ' * 7)
print('1' + ' ' * 2 + self.pos[1][0] + ' ' * 3 + '|' + ' ' * 3 + self.pos[1][1] + ' ' * 3 + '|' + ' ' * 3 +
self.pos[1][
2] + ' ' * 3)
print('_' * 7 + '|' + '_' * 7 + '|' + '_' * 7)
print(' ' * 7 + '|' + ' ' * 7 + '|' + ' ' * 7)
print('2' + ' ' * 2 + self.pos[2][0] + ' ' * 3 + '|' + ' ' * 3 + self.pos[2][1] + ' ' * 3 + '|' + ' ' * 3 +
self.pos[2][
2] + ' ' * 3)
print(' ' * 7 + '|' + ' ' * 7 + '|' + ' ' * 7)
print('')
# make sure that a move is legal
def update_pos(self, x, y):
if self.pos[x][y] == '-':
self.pos[x][y] = self.turn
self.turn = 'O' if self.turn == 'X' else 'X'
else:
print('invalid position. try again')
# look for a winner. assign winner to self.winner
def check_for_winner(self):
for y in range(0, 3):
tmp_hor = []
tmp_ver = []
# checks all horizontal and vertical sequences
for x in range(0, 3):
tmp_hor.append(self.pos[x][y])
tmp_ver.append(self.pos[y][x])
if len(tmp_ver) == 3:
self.check_groups(tmp_ver)
if len(tmp_hor) == 3:
self.check_groups(tmp_hor)
# hardcoding in diagonal sequences
self.check_groups([self.pos[0][0], self.pos[1][1], self.pos[2][2]])
self.check_groups([self.pos[2][0], self.pos[1][1], self.pos[0][2]])
def check_groups(self, seq):
'''we group together all recurring characters then check if there is more than one group.
One group in a sequence means that there are 3 of the same character in a row.
If there are 2 groups or more, there is no winner yet'''
groups = []
for k, g in groupby(seq):
groups.append(list(g))
if len(groups) == 1 and k != '-':
self.winner = k
def getavailablemoves(self):
self.valid_moves = []
for row in enumerate(self.pos):
for col in enumerate(row[1]):
if col[1] == '-':
self.valid_moves.append([row[0], col[0]])
def validate_input(self, move):
print("validating input...")
self.getavailablemoves()
xmove, ymove = map(int, move.split(','))
return xmove, ymove
# generate a list of all possible moves, and assign a score to each end
# state.
def find_next_move(self, allmoves, moves=[], levl=0):
for i in range(len(allmoves)):
# initialize a new Board() instance with potential move
newboard = Board(deepcopy(self.pos), self.turn)
newboard.update_pos(allmoves[i][0], allmoves[i][1])
newboard.check_for_winner()
newboard.getavailablemoves()
moves.append(allmoves[i])
if newboard.winner:
if self.turn == self.cpu:
self.score.append([10 - levl, levl, copy(moves)])
else:
self.score.append([levl - 10, levl, copy(moves)])
moves.pop()
elif len(newboard.valid_moves) < 1:
# this means no winner - store this list too for stalemates
self.score.append([0, levl, copy(moves)])
moves.pop()
else:
newboard.find_next_move(newboard.valid_moves, levl=levl + 1)
moves.pop()
# from the list of all moves, with their scores, find the path that leads
# to the highest score
def find_best_move(self):
levels = set([l[0] for l in groupby(self.score, key=lambda x: x[1])])
total = {}
for move in self.score:
firstmv = str(move[2][0])
if firstmv not in total.keys():
total.update({firstmv: {move[1]: move[0]}})
else:
if move[1] not in total[firstmv].keys():
total[firstmv].update({move[1]: move[0]})
else:
total[firstmv][move[1]] += move[0]
for k, v in total.items():
print(k + ' ' + str(v))
for level in levels:
prev_value = None
tmp = 0
move = 0
for k, v in total.items():
if not level in v.keys():
v[level] = 0
if prev_value is not None:
if v[level] > prev_value:
move = k
tmp = v[level]
prev_value = v[level]
else:
prev_value = v[level]
tmp = v[level]
move = k
if [v[level] for k, v in total.items()].count(tmp) == 1:
return move
return move
def start_game():
match = Board()
print("Would you like to play first? y/n")
firstplayer = input()
if firstplayer == 'y':
Board.cpu = 'O'
else:
Board.cpu = 'X'
match.draw_board()
print("make your move, i.e. '2,0' marks the third tile down in the first column.")
while True:
if match.turn == match.cpu:
Board.score = []
match.getavailablemoves()
match.find_next_move(match.valid_moves)
print('best move:')
bestmove = match.find_best_move()
move = str(bestmove[1]) + ', ' + str(bestmove[4])
else:
move = input()
xmove, ymove = match.validate_input(move)
result = match.update_pos(xmove, ymove)
match.draw_board()
match.check_for_winner()
if match.winner:
print("Player " + match.winner + " wins!")
playagain = input("play again? y/n: ")
break
return playagain
while True:
# Start a new game.
response = start_game()
# After the game has ended, check if the player wants to play another round
if response == 'y' or response == 'Y':
continue
else:
break
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment