Simple Pycodes

Simple Pycodes

About the the game:

 

Tic – Tac -Toe is a simple board game which mostly played on paper. The object of the game is to get 3 pieces in row or mark all the squares to get even. The first player play with X mark and second with O mark.

 

There are a lot of possible combinations in this game, in fact mathematically 362880 combination!

But only 8 combinations are in win positions, and the game wont last longer than 9 moves.

 

Then its not that reasonable to calculate all the false and useless combinations.

Instead we are going to focus on decision making so computer choose correct moves.

 

This tutorial is not a bout machine learning or minimax algorithm.

 

8 win positions

So we have 8 win positions for each player but only one of them going to happen in the game. and each player will try to prevent other one to get close to that position and in the same time try to get closer to his own win position.

 

In implementation of the game we need to write a function or method to check the state of the game contently if any of the players are winning! There are many ways to do it, but the way we are going to try will works with list of winning position.

 

This would be representation of those win states in our code.

 

 

 

self.win_positions = [[0,1,2],[3,4,5],[6,7,8],

                                          [0,3,6],[1,4,7],[2,5,8],

                                          [0,4,8],[2,4,6]]

 

and now we can use it as reference in our method in game class:

 

def check_win(self):

     for f,s,t in self.win_positions:

         if self.board[f] == self.board[s] == self.board[t] and self.board[f] != " ":

              return True

as you see we can use for loop to check all the 8 possible combination which we define in our win_positions list, so if it happens to be on board the function will return True and game will be end.

 

 

Its possible for us to make the computer to be some how aware of win positions, then it would be possible for us to make it to be aware of other possible combinations and positions in each state of the game.

Now that we have all 8 possible win combinations lets try to make a list of possible combinations one move prior to each win combination! Sounds confusing? Not really. We just need to walk backward to starting positions.

 

 

Lets take a look at first horizontal win position and its possible one move prior combinations:

 

 

 

so it means we have to find a way to check all these 3 prior combinations.

And we are going with the same idea of lists, loops and a method in our game class.

First we need to find all prior combinations for all 8 win combinations.

Then we need a list contains of all possible positions:

 

self.semi_positions = [[0,1,2],[0,2,1],[1,2,0],

                                            [3,4,5],[3,5,4],[4,5,3],

                                            [6,7,8],[6,8,7],[7,8,6],

                                            [0,3,6],[0,6,3],[3,6,0],

                                            [1,4,7],[1,7,4],[4,7,1],

                                            [2,5,8],[2,8,5],[5,8,2],

                                            [0,4,8],[0,8,4],[4,8,0],

                                            [2,4,6],[2,6,4],[4,6,2]]

 

now before we continue forward we need to take a look at game from starting point after the first move.

 

For the first move the best move would be capturing the center square, so the player will have more advantage to win the game, specially depending on second players first move. Means we need to make the computer to play such a game that creates more chances for itself to win and less chances for the opponent to win.

 

Now lets take a look at the position after first move with a chance for computer to make a move,  then best place as I said would be center of the board:

 

 

 

 

 

now if second player don't go for the corners, will loose the game, lets take a look at an example:

 

 

 

if second players first move was move number 2 in the picture above how game could continue, keeping in mind our opponent is a computer and it wont make any mistakes, or at least we need to write our code in such a way. Here as you see the second player is not playing anymore, its just a forced win game for the first player and second player is just following forcefully to the end of the game. Now the first player just need to capture one of the close corners to create a fork position and we can call it C position. And other corners would be F for false position for first player.

 

let me show you in the next picture:

in this position winning the game is just mater of couple of moves for the first player and second player is helpless preventing it. Now if first player take one of the close corners marked with C it will create a fork position, it means it will have 2 possible semi win position which we define them before, then second player cant block both of the with in the next move so eventually in the next move first player will win the game.

 

check it out in the next picture:

 

 

 

 

 

alright now that we have some basic ideas about decision making lets try to implement it into our code.

Our goal will be create a tree of moves which starts from capturing the center of the board, if its possible (means if computer makes the first move) or go for the corners if computer plays as a second player, and from now we have to check constantly for semi win positions for both players. And in special states of the game when every possible move will be part of the false or useless combinations, we will leave it to random module to take care of it for us, then what we are trying to achieve is writing a basic conditional tree of choices.

 

 

Implementation:

We are going to write a simple tic tac toe with gui.

For Graphics im going to use Turtle module, surprised?!

Well you will find out soon enough that turtle module have a lot more potential in it than most people think.

 

We are going to write a Game class which will holds the game attributes and methods and at the end we will finish the program by writing a main function.

 

As I said we are going to focus on decision making depending on a given position in each state of the game, and try to write a simple AI or at least something that behaves like an AI!

 

 

Beside the game itself this program can be a good exercise to learn more about turtle module and writing a simple classes in general, so enough talking. Lets code it up!

 

 

 

 

Importing necessary modules and setup the screen.

 

import turtle, random, time

 

screen = turtle.Screen()

screen.setup(450, 450, 900)

screen.bgcolor("black")

screen.tracer(1)

 

By screen method we can create screen and setup its size and its position on the screen (monitor) and background color.

With screen.tracer() method we can control the drawing update, means if for example we use screen.tracer(0) or screen.tracer(False) the program wont draw on the screen and show it in the same time, it will draw and after end of the process will update the screen so by this way we can have faster animations. This method need to be combined with screen.update(), we will get there in no time.

 

Next step is to write our game structure class, but before we start with our game attributes, lets see how we should call turtle module and have access to its methods from our class.

 

class Game(turtle.Turtle):

    def __init__(self):

    turtle.Turtle.__init__(self)

 

after a simple classic __init__(self) method we should initialize the turtle module as part of the Game class so we can now have complete access to its methods.

 

 

class Game(turtle.Turtle):

    def __init__(self):

    turtle.Turtle.__init__(self)

    #-- Grafic attributes

    self.penup()

    self.hideturtle()

    self.speed(0)

 

penup()

part of the turtle module and it lift the a pen from the screen so by changing the position of the pen, it wont draw anymore. We can put it down by pendown()

 

hideturtle()

part of the turtle module and it makes the turtle icon invisible. We can get it back to visible mode by showturtle()

 

speed()

part of the turtle module, with this method we can define the speed of drawing, 0 will be the fastest and 1 would be slowest speed, between 10 and 0 in backward we can have faster drawing .

 

Our board we are going to use a list, and create a tile style graphical board to interact with

 

#-- Game attributes

self.clear()

self.y_text_pos = 150

self.x_text_pos = 60

self.distance = 80

self.space = 5

self.row = 3

self.col = 3

self.board = [" "]*9

self.spot = 0

self.x = 0

self.y = 0

self.count = 0

self.move = "p"

self.corners = [0,2,6,8]

self.win_positions = [[0,1,2],[3,4,5],[6,7,8],

                                          [0,3,6],[1,4,7],[2,5,8],

                                          [0,4,8],[2,4,6]]

 

self.semi_positions =  [[0,1,2],[0,2,1],[1,2,0],

                                              [3,4,5],[3,5,4],[4,5,3],

                                              [6,7,8],[6,8,7],[7,8,6],

                                              [0,3,6],[0,6,3],[3,6,0],

                                              [1,4,7],[1,7,4],[4,7,1],

                                              [2,5,8],[2,8,5],[5,8,2],

                                              [0,4,8],[0,8,4],[4,8,0],

                                              [2,4,6],[2,6,4],[4,6,2]]

we have some constants in our class. Distance, space, row, col, which we use them in drawing the board and converting the graphical coordinates to list index and wise versa.

Our board will be a simple list with 9 items. And 2 pack of lists one representing the win combinations and another semi win combinations. and self.corners are 4 corners of the board.

 

Self.count will help us to track the number of played moves, for example if self.count

is 0 means the next move will be the first move, if self.count is 1 then the next move will be second move and so on.

 

By using self.count and self.corners we can write a simple method to make the first move in a right way!

 

def get_opening(self):

if self.count == 0:

    c_spot = random.choice(self.corners)

    return c_spot

if self.count == 1:

    if self.board[4] == " ":

        c_spot = 4

            return c_spot

    if self.count != " ":

        c_spot = random.choice(self.corners)

        return c_spot

 

When we call this method from the computer method it will checks if self.count is 0 or not.

It means the board is empty or not if its empty then computer will right a way captures the center of the board, if count is 1 means one move has been made so it will check if by that move opponent captured the center or its still empty if its empty will capture it and if not will go for one of the corners even it can be other possible first moves for the opponent but for us only capturing the center is important.

 

 

The next step is to keep an eye on semi_positions and we can do by using almost the same method we used to check the win positions.

We will do it for each mark or in other hand for each player and we will use the result to go toward win position or prevent the opponent from it.

We use variable c_spot for{computer_spot}

 

 

for f,s,t in self.semi_positions:

    if self.board[f] == "o" and self.board[s] == "o":

        if self.board[t] == " ":

            c_spot = t

 

for f,s,t in self.semi_positions:

    if self.board[f] == "x" and self.board[s] == "x":

        if self.board[t] == " ":

            c_spot = t

 

so in this way easily we check the semi win positions.

From other side, user  function have more simple functionality.we  define a function to get an input from a user, check the availability of the chosen spot,

if its available, make a move, check if game is in win state or not and finally end the game or call the computer function.

 

 

with this way we wont need to use nested while loops. And the only other thing we need to take care of is find a way to know when the game is tie.

Thanks to self.count we can just simply count the moves and each time before switching between user and computer methods we check  how many moves been played until now.

 

 

 

 

 

 

 

 

 

 

so in this way easily we check the semi win positions.

From other side, user  function have more simple functionality.we  define a function to get an input from a user, check the availability of the chosen spot,

if its available, make a move, check if game is in win state or not and finally end the game or call the computer function.

 

 

with this way we wont need to use nested while loops. And the only other thing we need to take care of is find a way to know when the game is tie.

Thanks to self.count we can just simply count the moves and each time before switching between user and computer methods we check  how many moves been played until now.

 

simple functionality:

 
 

Now that we got familiar with general idea of logic behind the game. lets briefly talk a bout gui.

 

 

 

                                                                            GUI

 

For developing a game in python the first choice of course would be pygame and ill will add some tutorials on how to develop simple arcade games with it here as well.

but in my personal experience with python, I find turtle very convincing and powerful module, a great tool not only to teach the programming to kids and draw with but to develop simple arcade games with sort of animation. 

 

specially for our purposes in this game, turtle is a great idea, because we need to have a graphical board and event control for using a mouse to interact with the boar.

and these are all what we need and turtle provides them for us and its really easy to combine turtle methods in our code.

Another option for us could be Tkinter module which is special library for gui programming. We will cover some of its abilities later.

 

First of all we need to have a graphical board, and as you remember our real board which actively changing during the game is just a simple pyhton list.

So the challenge or the problem for us to solve here is how we can write a representative for it, and how we can interactive with it?

 

One way would be we convert our python list to a tile base board and instead of using keyboard to interact with out list, we use mouse to mark the board then depends on its position and the mark we need to convert to X or O and append it to the list depending on its position on the board, and from other way around convert the values to marks in the list and display them on the board on the correct spot.

 

 

Before we try to convert coordinate to index and index to coordinate lets draw our board.

 

def draw_board(self):

    self.pencolor("green")

    self.shape("square")

    self.shapesize(self.distance/20)

    self.fillcolor("black")

    self.setpos(-150, 150)

    #--

    for r in range(self.row):

       for c in range(self.col):

           self.stamp()

           self.forward(self.distance)

       self.back(self.distance * self.row)

       self.right(90)

       self.forward(self.distance)

       self.left(90)

 

by using our constants and simple nested for loop we can easily draw a frame or board.

 

we just need to have a system to convert coordinates to index and index to coordinates, of course we can write much better code here but lets go with something simple like this:

 

def get_spot(self, x,y):

    self.spot = None

    if x == -160 and y == 160:

    self.spot = 0

    if x == -80 and y == 160:

    self.spot = 1

    if x == 0 and y == 160:

    self.spot = 2

    if x == -160 and y == 80:

    self.spot = 3

    if x == -80 and y == 80:

    self.spot = 4

    if x == 0 and y == 80:

    self.spot = 5

    if x == -160 and y == 0:

    self.spot = 6

    if x == -80 and y == 0:

    self.spot = 7

    if x == 0 and y == 0:

    self.spot = 8

    return self.spot

 

 

keep in mind that (x,y) are variables which we get from mouse click by using:

turtle.onscreenclick(function, or class method)

 

in our code we need to add this line of code in main function and connect it to user method in game class.

 

 

def main():

     turtle.onscreenclick(game.user)

 

and it will send the mouse click as event to:

 

def user(self, x,y):

 

one small adjustment should be done here and that would be rounding the coordinates accurately to fit them into our board, so 9 different range of them can represent only one index,

what I mean?

For example lets say we wanna click on the top left square which will be index[0] on our real board! Which is a simple python list. How we can manage it, because we cant click precisely on exact same spot on the screen right?

 

thats would be  done easily by these two lines of code!

 

x = int(self.distance * round(x/self.distance))

y = int(self.distance * round(y/self.distance))

 

now if we click on any spot on top left corner as long as its on a tile, we will get same coordinates, and then we can convert them to index using our simple convertor.

and from other side when we nna convert an index to coordinate, we just need to reverse the process.

now we almost done with the explenations, we have some general ideas  the game logic, decision making and rulles of the game, and so far we could manage to build up a gui for the game.

Reverse decision making with simple Tic-Tac-Toe

Part 1

Reverse decision making with simple Tic-Tac-Toe

Part 2

Reverse decision making with simple Tic-Tac-Toe

Part 3

Reverse decision making with simple Tic-Tac-Toe

Part 4

Reverse decision making with simple Tic-Tac-Toe

Last part