Python Foundations, Project Part 3: Building Agents (Random AI Agent)¶
Instructor: Wesley Beckner
Contact: wesleybeckner@gmail.com
In part III of our tic-tac-toe and AI journey, we're going to create NPCs for our game!
We will need to pull in our OOP work from the previous project notebook. Be sure to copy your code and run those cells below before we get started on section 3
3.0 Preparing Environment and Importing Data¶
3.0.1 Import Packages¶
class TicTacToe:
##########################
##### YOUR CODE HERE #####
##########################
pass
def play_game():
##########################
##### YOUR CODE HERE #####
##########################
pass
3.1 Creating an Agent¶
we'll start by creating an agent that just selects a random playing number
Q1 Use a Random Number Generator¶
import the random library. Use it to randomly generate numbers 1-9 (inclusive? yep, inclusive. We're going to use this to select the keys in our board dictionary! 📚)
# cell for Q4
# generates a random number 1-9
Q2 Play a Valid Move¶
Nice, now that we are generating random numbers 1-9. We need to check if a random number that's been drawn is a valid move. We're going to do this with... yeah that's right you guessed it, a while loop
while True:
# generate a random number 1-9 and set it equal to the variable name <move>
if # if the move is invalid then...
continue
else: # otherwise the move is valid and we can exit the loop!
break
Wow and that's it, we just need to wrap it in our play_game
function. Before we do that, we need to handle the ability of our engine to perform 1 and 2 player games. In order to do this, I'm going to introduce a new object to organize how the game is setup. I'm going to call it player_meta
player_meta = {'first': {'label': 'X',
'type': 'human'},
'second': {'label': 'O',
'type': 'human'}}
Note that in the above player_meta
I have 'type'
specificed to 'human'
, the other player 'type'
will be 'ai'
! Keep this in mind when answering Q3 below.
Q3 Game Setup and Random AI¶
I will skeleton out where the input
questions need to be sent to the user, it is your task to determine how the player_meta
dictionary should subsequently be updated!
def play_game():
tic_tac_toe = TicTacToe()
##############################################################################
################################# GAME SETUP #################################
##############################################################################
players = int(input("How many Players? (type 0, 1, or 2)"))
player_meta = {'first': {'label': 'X',
'type': 'human'},
'second': {'label': 'O',
'type': 'human'}}
if players == 1:
first = input("who will go first? (X, (AI), or O (Player))")
if first == 'O':
### UPDATE PLAYER_META HERE ###
else:
### UPDATE PLAYER_META HERE ###
elif players == 0: # insert wargames quote
first = random.choice(['X', 'O'])
if first == 'O':
### UPDATE PLAYER_META HERE ###
else:
### UPDATE PLAYER_META HERE ###
##############################################################################
################################# GAME PLAY ##################################
##############################################################################
while True:
# in the following line, instead of alternating between 'X' and 'O', we now
# alternate between the first and second player, which has the associated
# metadata of label (X or O) and type (ai or human).
for player in ['first', 'second']:
tic_tac_toe.visualize_board()
# we set the player_label and player_type according to which player is
# playing in this round
player_label = player_meta[player]['label']
player_type = player_meta[player]['type']
if player_type == 'human':
move = input("{}, what's your move?".format(player_label))
# we're going to allow the user to quit the game from the input line
if move in ['q', 'quit']:
tic_tac_toe.winner = 'F'
print('quiting the game')
break
move = int(move)
if tic_tac_toe.board[move] != ' ':
while True:
move = input("{}, that position is already taken! "\
"What's your move?".format(player))
move = int(move)
if tic_tac_toe.board[move] != ' ':
continue
else:
break
##########################################################################
################### YOUR RANDOM AI AGENT CODE GOES HERE ##################
##########################################################################
else:
pass # delete this line when finished
tic_tac_toe.board[move] = player_label
# the winner varaible will now be check within the board object
tic_tac_toe.check_winning()
tic_tac_toe.check_stalemate()
if tic_tac_toe.winner == '':
# clear_output()
continue
elif tic_tac_toe.winner == 'Stalemate':
print(tic_tac_toe.check_stalemate())
tic_tac_toe.visualize_board()
break
else:
print(tic_tac_toe.check_winning())
tic_tac_toe.visualize_board()
break
if tic_tac_toe.winner != '':
return tic_tac_toe
play_game()
3.2 OOP and Inheritance¶
We'll want to reconfigure our code a bit to allow for AI to play AI. We'll use this feature to record a bunch of games and generate some data for analysis and, eventually, machine learning.
We also want to run our game system without having any input from the user for our data generation. This is a good point to reconfigure our code. Notice how long and bulky play_game
is getting.
Q4 Inheriting from TicTacToe¶
To better organize our code, we're going to create a new class called GameEngine
and we're going to inherit all the properties of TicTacToe
. Do we remember how to do this? let's try it. The simplest way is the following:
class GameEngine(TicTacToe):
def __init__(self):
super().__init__()
Nice. Pretty clean, right? The only new thing here I want us to introduce is the parameter setup
setup will determine whether we have the user enter fields to setup the player_meta
dictionary, or whether we automatically set it up to allow ai vs ai and not require input from the user (which we will need if we are to run thousands of games automatically for data generation!!!)
class GameEngine(TicTacToe):
def __init__(self, setup='auto'):
super().__init__()
self.setup = setup
notice the new parameter flag! We're going to use it in this next step. Be sure to:
- access all methods/attributes via the
self
- use code you already wrote above for setting up and for the random AI agent
class GameEngine(TicTacToe):
def __init__(self, setup='auto'):
super().__init__()
self.setup = setup
def setup_game(self):
if self.setup == 'user':
##########################################################################
## YOUR GAME SETUP CODE FROM ABOVE GOES HERE, NOTE THE NEW IF STATEMENT ##
##########################################################################
elif self.setup == 'auto':
##########################################################################
## THE NEW AUTOSETUP FEATURE THAT WILL ALLOW THE GAME TO RANDOMLY SETUP ##
##########################################################################
first = random.choice(['X', 'O'])
if first == 'O':
self.start_player = 'O'
self.player_meta = {'second': {'label': 'X',
'type': 'ai'},
'first': {'label': 'O',
'type': 'ai'}}
else:
self.start_player = 'X'
self.player_meta = {'first': {'label': 'X',
'type': 'ai'},
'second': {'label': 'O',
'type': 'ai'}}
def play_game(self):
while True:
for player in ['first', 'second']:
self.visualize_board()
player_label = self.player_meta[player]['label']
player_type = self.player_meta[player]['type']
if player_type == 'human':
move = input("{}, what's your move?".format(player_label))
# we're going to allow the user to quit the game from the input line
if move in ['q', 'quit']:
self.winner = 'F'
print('quiting the game')
break
move = int(move)
if self.board[move] != ' ':
while True:
move = input("{}, that position is already taken! "\
"What's your move?".format(player))
move = int(move)
if self.board[move] != ' ':
continue
else:
break
########################################################################
################### YOUR RANDOM AI AGENT CODE GOES HERE ################
########################################################################
else:
pass # delete this line when finished
self.board[move] = player_label
# the winner varaible will now be check within the board object
self.check_winning()
self.check_stalemate()
if self.winner == '':
# clear_output()
continue
elif self.winner == 'Stalemate':
print(self.check_stalemate())
self.visualize_board()
break
else:
print(self.check_winning())
self.visualize_board()
break
if self.winner != '':
return self
And now we can practice using our Engine:
game = GameEngine()
game.setup_game()
board = game.play_game()
| | | |
| | | |
| | | |
| | | |
| |X| |
| | | |
| | | |
| |X| |
| |O| |
| |X| |
| |X| |
| |O| |
| |X|O|
| |X| |
| |O| |
| |X|O|
| |X| |
|X|O| |
| |X|O|
| |X| |
|X|O|O|
|X|X|O|
| |X| |
|X|O|O|
|X|X|O|
|O|X| |
|X|O|O|
It's a stalemate!
|X|X|O|
|O|X|X|
|X|O|O|
Q5 Test the Engine¶
Check that we can still use the GameEngine
to play a human v human or human v AI game of tic-tac-toe:
game = GameEngine(setup='user')
game.setup_game()
How many Players? (type 0, 1, or 2)1
who will go first? (X, (AI), or O (Player))O
game.play_game()
| | | |
| | | |
| | | |
O, what's your move?1
|O| | |
| | | |
| | | |
|O| |X|
| | | |
| | | |
O, what's your move?4
|O| |X|
|O| | |
| | | |
|O| |X|
|O| | |
| | |X|
O, what's your move?7
'O' Won!
|O| |X|
|O| | |
|O| |X|
<__main__.GameEngine at 0x7f71d3713050>
3.3 Simulating Data¶
We will now want to run our game thousands of times to collect data for building our AI agents. The following code should run without any input from the user:
game = GameEngine()
game.setup_game()
board = game.play_game()
game = GameEngine()
game.setup_game()
board = game.play_game()
| | | |
| | | |
| | | |
| | | |
| | | |
| | |O|
| | | |
| | |X|
| | |O|
| |O| |
| | |X|
| | |O|
| |O| |
|X| |X|
| | |O|
|O|O| |
|X| |X|
| | |O|
|O|O| |
|X| |X|
| |X|O|
'O' Won!
|O|O| |
|X|O|X|
| |X|O|
Q6 Record 1000 Games¶
Write a for loop that creates 1000 games and saves the game data in the following dictionary format, replacing <THE GAME NUMBER>
with whatever index you are using in the for loop:
data = {}
data['game {}'.format(<THE GAME NUMBER>)] = {'board': board.board,
'winner': board.winner,
'starting player': board.start_player}
# Cell for Q9
import json
with open('data.txt', 'w') as outfile:
json.dump(data, outfile)