"""
Python code to play tic-tac-toe game using the engine.py and interact with the Game Engine.
"""
# Import the function engine and move_at from engine.py
# Make sure that engine.py is located in the same folder on your computer as this agent.py
from engine_impress import engine, move_at
# Import the function randint from the module random (standard python library)
# See here for documentation of random and randint: https://docs.python.org/3/library/random.html
from random import randint
# Example agent
def first_available_agent(board):
"""
An agent that only plays in the first available position.
This strategy is a poor strategy overall because your agent is losing more than winning the game ¯\_(ツ)_/¯
Input: A tic tac toe board in its string representation nine characters (either "X"s, "O"s or "."s)
(either "X"s, "O"s or "."s)
E.g. 'X...O...X' represents the board
X . . .
. O . .
. . X .
. . . O
String index positions are:
0 1 2 3
4 5 6 7
8 9 10 11
12 13 14 15
Output: A string representation of the input board with our move made on it
- replaces one '.' with the variable our_pieces
A valid strategy (if not a very good one) is to place your piece in the first
open place on the tic tac toe board. An agent that does this isn't going to win
every game but it is able to play a move onto every possible board so its a good start
to see how the system operates with a valid agent
"""
# Work out which pieces we are playing
"""
Summary of thinking:
* Starting with the premise that "X" always play first we can infer two things
-> if the number of X's and O's are equal X should play next
-> if the number of empty squares ('.') is odd (empty board = 9, one move each = 7, ....)
then X should be playing
* Using the first inference we will make the comparison and return one of two things based on the result
-> Structure of the code: an if statement with different return calls within each condition
"""
# Compare the number of "X"s and "O"s
if board.count("X") == board.count("O"):
# If equal number of X's and O's then we are playing "X"s (if statement)
our_pieces = "X"
# If not then we must be playing "O"s (else statement)
else:
our_pieces = "O"
# Seize the top centre first (position number 1)
# check that its an available move (if statement)
# generate the board with our move on it
# return the board (on the spot return call)
""" This turned out to be a terrible strategy and wasn't implemented. The only way to win, was not to play position 1 first!
Also the same strategy applied for position 3 (left-middle), position 5 (right-middle) and position 7 (bottom centre). """
# Replace the first "." with our pieces
"""
To make the first available move by replacing the first "." with our pieces.
Three arguments of "Replace":
1. A string of the character(s) to be replaced,
2. What to replace those character(s) with and
3. How many times to do so (optionally).
Calling this bound method on `board` which returns us a new string with our requested changes that we will call 'first_available_agent'
"""
move = board.replace('.',our_pieces,1)
# Give out move back to the engine
return move
def random_agent(board):
""" A strategy of making a random move is a better strategy than the 'first_available_agent' strategy because this agent is winning more than losing.
Even though random movement is not strategic or pythonic enough but it could be useful if you have run out of 'strategic' options.
"""
# Work out which pieces we are playing
# Compare the number of "X"s and "O"s
if board.count("X") == board.count("O"):
# If equal number of X's and O's then we are playing "X"s
# (if statement)
our_pieces = "X"
else:
# If not then we must be playing "O"s (else)
our_pieces = "O"
# This strategy - select a random position which has a '.', is not the most efficient way.
# select a random integer (whole number) between 0 and 8
# and put it in variable 'r'
r = randint(0, 8) # thanks to SOC for pointing out the documentation
# NOTE: '.' is a free spot on the board
# We will loop until we know that board[r] == '.'
# in other words: the index position 'r' is a free space
# we are finished the loop when the value board[r] == '.'
while not board[r] == '.':
# thank you Erika for suggesting that we use
# "not board[r] == '.'" as the test for the loop
# NOTE: this is a great example of why you would use not
# instead of "(board[r] == 'X' or board[r] == 'O')"
# if we get here, then it is not a free spot on the board
# so - we get a new random location
r = randint(0, 8)
# Now we have finished with the loop, we know that
# board[r] == '.' - r indicates a free spot
# printing is not necessary - but it can help debug
# you do not need this in your final version
# print(f'Randomly moving to {r}')
# Thanks to Memunat for illustrating this way of
# "making a move" in the tutorial:
# Change the board string to a list
# NOTE: we can change the value of list at an index - but not a string
board_list = list(board)
# change the value of the board_list at position 'r' from '.' to our_pieces
board_list[r] = our_pieces
# join the list into a string (converts a list to a string)
board = ''.join(board_list)
# Give out board back to the engine
return board
def winning_agent(board: str):
if board.count("X") == board.count("O"):
# If equal number of X's and O's then we are playing "X"s (if statement)
our_pieces = "X"
# If not then we must be playing "O"s (else)
else:
our_pieces = "O"
for a, b, c, d in (
(0, 1, 2, 3), # 1st row (top-bottom) Here's the board:
(4, 5, 6, 7), # 2nd row (top-bottom) 0 1 2 3
(8, 9, 10, 11), # 3rd row (top-bottom) 4 5 6 7
(12, 13, 14, 15), # 4th row (top-bottom) 8 9 10 11
(0, 4, 8, 12), # 1st column (left-right) 12 13 14 15
(1, 5, 9, 13), # 2nd column (left-right)
(2, 6, 10, 14), # 3rd column (left-right)
(3, 7, 11, 15), # 4th column (left-right)
(0, 5, 10, 15), # top-left to bottom-right diagonal
(3, 6, 9, 12), # top-right to bottom-left diagonal
):
# If position (a) is empty, place your agent here.
if board[a] == '.' and ((our_pieces == board[b] == board[c] == board[d]) or (our_pieces != board[b] and board[b] == board[c] == board[d])):
return move_at(board, a)
# If position (b) is empty, place your agent here.
elif board[b] == '.' and ((our_pieces == board[a] == board[c] == board[d]) or (our_pieces != board[a] and board[a] == board[c] == board[d])):
return move_at(board, b)
# If position (c) is empty, place your agent here.
elif board[c] == '.' and ((our_pieces == board[a] == board[b] == board[d]) or (our_pieces != board[a] and board[a] == board[b] == board[d])):
return move_at(board, c)
# If position (d) is empty, place your agent here.
elif board[d] == '.' and ((our_pieces == board[a] == board[b] == board[c]) or (our_pieces != board[a] and board[a] == board[b] == board[c])):
return move_at(board, d)
# If position #0 (top-left corner) is empty, place your agent here.
if board[0] == '.' and ((board[1] == board[2] == board[3]) or (board[4] == board[8] == board[12]) or (board[5] == board[10] == board[15])):
return move_at(board,0)
# If position #3 (top-right corner) is empty, place your agent here.
elif board[3] == '.' and ((board[0] == board[1] == board[2]) or (board[7] == board[11] == board[15]) or (board[6] == board[9] == board[12])):
return move_at(board,3)
# If position #12 (bottom-left corner) is empty, place your agent here.
elif board[12] == '.' and ((board[0] == board[4] == board[8]) or (board[13] == board[14] == board[15]) or (board[3] == board[6] == board[9])):
return move_at(board,12)
# If position #15 (bottom-right corner) is empty, place your agent here.
elif board[15] == '.' and ((board[0] == board[5] == board[10]) or (board[3] == board[7] == board[11]) or (board[12] == board[13] == board[14])):
return move_at(board,15)
# If position #5 (the middle square) is empty, place your agent here.
if board[5] == '.' and ((board[0] == board[10] == board[15]) or (board[1] == board[9] == board[13]) or (board[4] == board[6] == board[7])):
return move_at(board,5)
# If position #6 (the middle square) is empty, place your agent here.
elif board[6] == '.' and ((board[3] == board[9] == board[12]) or (board[2] == board[10] == board[14]) or (board[4] == board[5] == board[7])):
return move_at(board,6)
# If position #9 (the middle square) is empty, place your agent here.
elif board[9] == '.' and ((board[1] == board[5] == board[13]) or (board[3] == board[6] == board[12]) or (board[8] == board[10] == board[11])):
return move_at(board,9)
# If position #10 (the middle square) is empty, place your agent here.
elif board[10] == '.' and ((board[0] == board[5] == board[15]) or (board[8] == board[9] == board[11]) or (board[2] == board[6] == board[14])):
return move_at(board,10)
# Otherwise, take any first empty position on the board at random.
else:
return random_agent(board)
#When you're ready swap over which line is being commented out
# engine(random_agent)
engine(winning_agent)