Created
March 15, 2019 17:47
-
-
Save bendavis78/563af6186e79870debd6f68b4a6a780c to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import ml4k | |
import random | |
import tkinter as tk | |
from copy import copy | |
from tkinter import messagebox | |
API_KEY = "PASTE-API-KEY-HERE" | |
AI_MODE = "random" | |
TRAINING = False | |
# The board state can be represented as a list with 9 items, one for each space on the game board. | |
# We'll pick some arbitrary numbers to represent how each space is occupied: | |
# | |
# 0: Unoccupied space | |
# 1: Occupied by the player | |
# -1: Occupied by the opponent | |
# | |
# The board starts out as empty, so we set each item to 0. | |
board = [ | |
0, 0, 0, | |
0, 0, 0, | |
0, 0, 0 | |
] | |
labels = [ | |
'top_left', 'top_middle', 'top_right', | |
'middle_left', 'middle_middle', 'middle_right', | |
'bottom_left', 'bottom_middle', 'bottom_right' | |
] | |
player_moves = [] | |
# We'll also store the buttons in a similiar way. This list will be auto-populated by make_button() | |
buttons = [] | |
# | |
# Game Window Setup | |
# | |
# Now we create our window and give it a title. We'll store this window in a variable called | |
# "game", so we can access it later. | |
game = tk.Tk() | |
game.title("Tic Tac Toe") | |
# Add a message area (to display text to the user). Use `message.set(text)` to change the message. | |
message = tk.StringVar() | |
message_area = tk.Label(game, textvariable=message, font="Times 20 bold", pady=20) | |
message_area.grid(row=0) | |
# Add a "holder" for our buttons | |
button_holder = tk.Frame(game) | |
button_holder.grid(row=1) | |
# | |
# Function Definitions: | |
# | |
def check_victory(space_index): | |
""" | |
Returns `True` if there's a winner based on the last-played postion, otherwise returns `False` | |
""" | |
# First, we need to get the row and column from the space_index: | |
# To learn more about the // and % operators, see here: | |
# https://docs.python.org/3/reference/expressions.html#binary-arithmetic-operations | |
row = space_index // 3 | |
col = space_index % 3 | |
# check if previous move caused a win on vertical line | |
if board[col] == board[3 + col] == board[6 + col]: | |
return True | |
# check if previous move caused a win on horizontal line | |
if board[3 * row] == board[3 * row + 1] == board[3 * row + 2]: | |
return True | |
# check if previous move was on the main diagonal and caused a win | |
if row == col and board[0] == board[4] == board[8]: | |
return True | |
# check if previous move was on the secondary diagonal and caused a win | |
if row + col == 2 and board[2] == board[4] == board[6]: | |
return True | |
return False | |
def make_move(space_index): | |
""" | |
Makes a move for the current player (whether human or computer). | |
This function returns `True` if the move results in a win, otherwise it returns `False`. | |
""" | |
if game.current_player == "X": | |
player_moves.append([ | |
space_index, copy(board) | |
]) | |
# Set the space to the current player (1 for X, or -1 for O) | |
if game.current_player == "X": | |
board[space_index] = 1 | |
else: | |
board[space_index] = -1 | |
# Change the button text to the current player (X or O) | |
buttons[space_index].configure(text=game.current_player) | |
# If there's a winner, show a message and start a new game | |
if check_victory(space_index): | |
messagebox.showinfo("Game over!", "The winner is: " + game.current_player + "!") | |
if game.current_player == "X" and TRAINING: | |
train_winning_moves() | |
new_game() | |
return True | |
# Else if there are no spaces are left empty, it's a draw! | |
elif 0 not in board: | |
messagebox.showinfo("Game over!", "It's a draw!") | |
new_game() | |
return True | |
# Otherwise, switch players | |
else: | |
if game.current_player == "X": | |
game.current_player = "O" | |
else: | |
game.current_player = "X" | |
message.set(game.current_player + "'s move") | |
return False | |
def on_click(event): | |
""" | |
This function gets called any time a button is clicked. Each button has a `row` and `col` | |
attribute, which we can pass to the `make_move()` function. | |
""" | |
clicked_button = event.widget | |
# We can use the .index() method to find the position of an item within a list | |
space_index = buttons.index(clicked_button) | |
# If space is already occupied or it's not our turn, don't do anything | |
if board[space_index] != 0 or (AI_MODE and game.current_player != "X"): | |
return | |
# The `make_move()` function returns `True` if it results in a win. | |
# We store that result in a variable so we can use it in the next step. | |
is_winning_move = make_move(space_index) | |
if not is_winning_move: | |
# Wait 1 second before calling `ai_move()` to make it look like the computer is "thinking". | |
game.after(1000, ai_move) | |
def new_game(): | |
""" | |
Clears the game board and starts a new game | |
""" | |
# re-set the buttons list and player moves | |
buttons.clear() | |
player_moves.clear() | |
# Remove existing button widgets | |
for widget in button_holder.winfo_children(): | |
widget.destroy() | |
# Fill the window with new buttons | |
for space_index in range(0, 9): | |
# Make the button and add it to our buttons list | |
button = make_button(space_index) | |
buttons.append(button) | |
# Set the board space to 0 (empty) | |
board[space_index] = 0 | |
# Choose a random player | |
game.current_player = random.choice(["X", "O"]) | |
message.set(game.current_player + "'s move") | |
if game.current_player == "O": | |
# Wait 1 second before calling `ai_move()` to make it look like the computer is "thinking". | |
game.after(1000, ai_move) | |
def make_button(space_index): | |
""" | |
Creates a button in the given space | |
""" | |
button = tk.Button(button_holder, text=" ", font="Times 20 bold", | |
bg="gray", fg="white", height=4, width=8) | |
row = space_index // 3 # use the "floor division" operator to get the row | |
col = space_index % 3 # use the "modulo" operator to get the column | |
# Put the button in the window using TK's "grid" method | |
button.grid(row=row, column=col, stick=tk.S + tk.N + tk.E + tk.W) | |
# Call on_click() when the button is clicked | |
button.bind("<Button-1>", on_click) | |
# return the button object for use later on | |
return button | |
def inverse_board(): | |
return [space * -1 for space in board] | |
def ai_move(): | |
if AI_MODE == "random": | |
random_ai_move() | |
elif AI_MODE == "ml": | |
ml_ai_move() | |
def random_ai_move(): | |
valid_space = False | |
while not valid_space: | |
space_index = random.randint(0, 8) | |
valid_space = board[space_index] == 0 | |
make_move(space_index) | |
def ml_ai_move(): | |
model = ml4k.Model(API_KEY) | |
ai_board = inverse_board() | |
print("Finding best move for board:", ai_board) | |
result = model.classify(ai_board) | |
print(result) | |
label = result['class_name'] | |
space_index = labels.index(label) | |
# If the chosen space is occupied, choose a random one instead. | |
if board[space_index] == 0: | |
make_move(space_index) | |
else: | |
print("space is occupied, choosing random") | |
random_ai_move() | |
def train_winning_moves(): | |
API_KEY = "7b00fe60-46ad-11e9-aaea-ff8de7d1c9f769c3f786-6375-4ec1-9dad-f2d808999ba9" | |
model = ml4k.Model(API_KEY) | |
print("---") | |
for move in player_moves: | |
space = move[0] | |
board_state = move[1] | |
label = labels[space] | |
print("Adding training data for move: " + label) | |
model.add_training_data(label, board_state) | |
# The rest of the code is called immediately when the script starts: | |
new_game() | |
# Start the app | |
game.mainloop() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment