[EN] Tic-Tac-Toe

This article is a collection of examples of Tic-Tac-Toe games that our team uses to teach game development in different languages ​​as appropriate for the group of learners. But most of them use Python for teaching because it is easier to explain and write than other languages. In addition to wanting to see how it can be implemented on other platforms, we used an example with the MicroPython of a board we named ml4m. Based on this board, TensorFlow Lite is installed on an ESP32 with a 4MB ROM. The board looks like Figure 1.

(Figure. 1 ml4m for testing Tic-Tac-Toe)

From the first Figure, it can be seen that the ml4m board consists of a JoyStick input, a touch switch, a push switch (because the joystick switch is broken ……. haha), there is a BME280 sensor (the humidity part also broken) and the MPU6050 is used to read the movement of the various axes, and has two outputs, the speaker and the OLED. The OLED connection uses the SCL of the OLED connected to pin GPIO22 and the SDA of the OLED to pin GPIO21 as Figure 2.

(Figure. 2 oled connected with GPIO22 and GPIO21)

The connections of the four switches are swA, swB, swC and swD, connected to GPIO32, GPIO33, GPIO34 and GPIO35 respectively as shown in Figure 3.

(Figure. 3 swD, swA, swB)

Tic-Tac-Toe

Tic-Tac-Toe is a board game for 2 players. The game features are as follows.
1. Table size : 3×3

   |   |
---+---+---
   |   |
---+---+---
   |   |
  1. 2 players : O and X
  2. Winning condition
    1. Arranged in horizontal rows
    1. Arranged in a vertical row.
    2. Arrange diagonally.

Data storage

The Tic-Tac-Toe game is a 3×3 grid, so the data must be a 3×3 grid by keeping the table. In Python, a list or tuple variable can be used to store, for example: which will find that the variable table is a list of 3 members, each member has 3 members inside represents each column of each row. There is also a function showTable() to display data in table variables.

# 1. ข้อมูล
table = [ # create list
    [1,2,3], # row 0
    [4,5,6], # row 1
    [7,8,9]  # row 2
# each row has 3 columns : 0, 1, 2
]
# show table
def showTable():
    print("{}|{}|{}".format(table[0][0],table[0][1],table[0][2]));
    print("{}|{}|{}".format(table[1][0],table[1][1],table[1][2]));
    print("{}|{}|{}".format(table[2][0],table[2][1],table[2][2]));
    
# main
showTable()

Getting turn data from players

To let the player select an O in the table, use the input() command, where the reader must test the operation by typing the data in the console. The program code is as follows.

# 1. data
table = [ # create list
    [1,2,3], # row 0
    [4,5,6], # row 1
    [7,8,9]  #  row 2
# each row has 3 columns : 0, 1, 2
]
# show table
def showTable():
    print("{}|{}|{}".format(table[0][0],table[0][1],table[0][2]))
    print("{}|{}|{}".format(table[1][0],table[1][1],table[1][2]))
    print("{}|{}|{}".format(table[2][0],table[2][1],table[2][2]))


# 
def getInput(who):
    while True: # by Mr. Kanokpon Sawasdee1
        showTable()
        try:
            position = int(input("choose(1...9)?"))
            if((position in table[0])or(position in table[1])or
               (position in table[2])):
                break
            else:
                print("1...9only")
                continue
        except:
            print("1...9only")
    r = 0
    if (position in table[0]):
        r = 0
    elif (position in table[1]):
        r = 1
    else:
        r = 2
    c = table[r].index(position)
    table[r][c] = who
    
# โปรแกรมหลัก
for i in range(4):
    getInput('o')
    print("------------------")
    getInput('x')
    print("------------------")
getInput('o')
showTable()

From the code, it is found that the type of input is checked for error using try and except. to get the numbers 1 to 9 representing the numbers 1 to 9 in the game grid, then double-check that the selected position has not been selected before. If it was previously selected it will report an error and re-select it. If it is never selected, it will change the value to “O”.

Check win loss

There are 8 cases of winning conditions:

  1. Row 1 is all O or X.
  2. Row 2 is all O or X.
  3. Row 3 is all O or X.
  4. Column 1 is all O or X.
  5. Column 2 is all O or X.
  6. Column 3 is all O or X.
  7. Positions 1,5 and 9 are all O or X.
  8. Positions 3,5 and 7 are all O or X.

Here’s an example validation code for a case where the first row is all O:

# Check victory of O
def checkWinO():
    # case 1 [0] --> 'o','o','o'
    if ((table[0][0] == 'o') and
        (table[0][1] == 'o') and
        (table[0][2] == 'o')):
        print("O Win!")
        return True
    return False        

Computer’s turn

To make solo playing more convenient, we write code for the computer to be the second player. The working principle of the computer side program is as follows.

  1. Random number from 1 to 9 or 0 to 8
  2. Check if the selection data exists before
    1. If yes, repeat again.
    2. If not, change the position value to “X”.

Example Code

The following example program uses the display to the OLED screen as shown in Figure 4, and when the position is selected alternately with the computer as shown in Figure 5, the result is as shown in Figure 6.

#######################################################
### Tic-Tac-Toe
#######################################################
from machine import Pin,I2C
import math
import machine
import gc
import ssd1306
import random
import time

#######################################################
## System setting
gc.enable()
gc.collect()
machine.freq(240000000)

#######################################################
## OLED
sclPin = Pin(22)
sdaPin = Pin(21)
i2c = I2C(0,scl=sclPin, sda=sdaPin, freq=400000)
oled = ssd1306.SSD1306_I2C(128,64,i2c)
oled.poweron()
oled.contrast(255)
oled.init_display()
oled.fill(0)
oled.show()

## constant
blockHeight = const(16)
blockWidth = const(16)
screenWidth = 128
screenHeight = 64

table = [1,2,3,4,5,6,7,8,9]

def drawTable():
    oled.fill(0)
    left = (screenWidth - (blockWidth*3))//2
    top = (screenHeight - (blockHeight*3))//2
    for i in range(4):
        oled.hline(left-(blockWidth//4),(top-(blockHeight//4))+blockHeight*i, blockWidth*3,1)
        oled.vline((left-(blockWidth//4))+(i*blockWidth),top-(blockHeight//4), blockWidth*3,1)
    i = 0
    for r in range(3):
        for c in range(3):
            oled.text(str(table[i]),left+blockWidth*c,top+blockHeight*r)
            i += 1
    oled.show()
    
def isWin(p):
    win = False
    # case 1 : first row is p
    if ((table[0]==p)and(table[1]==p)and(table[2]==p)):
        win = True
    # case 2 : second row is p
    if ((table[3]==p)and(table[4]==p)and(table[5]==p)):
        win = True
    # case 3 : third row is p
    if ((table[6]==p)and(table[7]==p)and(table[8]==p)):
        win = True
    # case 4 : column 0 is p
    if ((table[0]==p)and(table[3]==p)and(table[6]==p)):
        win = True
    # case 5 : column 1 is p
    if ((table[1]==p)and(table[4]==p)and(table[7]==p)):
        win = True
    # case 6 : column 2 is p
    if ((table[2]==p)and(table[5]==p)and(table[8]==p)):
        win = True
    # case 7 : diagonally left top to bottom right
    if ((table[0]==p)and(table[4]==p)and(table[8]==p)):
        win = True
    # case 8 : diagonally from bottom left to top right
    if ((table[2]==p)and(table[4]==p)and(table[6]==p)):
        win = True
    return win    
#######################################################
### Main program
counter = 0
while True:
    # draw table
    drawTable()
    # input
    print("Human")
    while True:
        try:
            pos = int(input("ตำแหน่งที่ต้องการเลือก(1-9)?"))
        except:
            print("กรุณากรอก 1..9")
        if ((pos < 1) or (pos > 9)):
             print("กรุ่ณากรอก 1..9")
        else:
             if (table[pos-1] != pos):
                 print("กรุณาเลือกช่องอื่น")
             else:
                 table[pos-1] = "O"
                 break
    drawTable()
    # check if human win ?
    if (isWin("O")):
        print("ขอแสดงความยินด้วยที่คุณชนะ")
        break
    # computer's turn
    print("Computer:")
    while True:
        pos = random.getrandbits(8)%9 # สุ่ม 0..8
        if (table[pos] == (pos+1)): # ถ้ายังว่าง
            table[pos] = "X"
            break
    # check if computer win?
    if (isWin("X")):
        print("ขอแสดงความเสียใจกับความพ่ายแพ้ของคุณ!")
        break
    # counter
    counter += 1
    if (counter == 4):
        break
drawTable()
time.sleep_ms(2000)
oled.poweroff()

(Figure. 4 Display on OLED)
(Figure. 5 Example of alternate position selection sequence with computer)
(Figure. 6 An example of the results of working according to the selection order is shown in Figure 5)

Conclusion

From this article, you will find that when we set the conditions for the thinking, conditions of work, after that, the code will be written to make the code work in a variety of systems. This is because the workflow is still similar. But when changing devices or platforms, what must be added is the part of writing the code to make it compatible with that platform includes different methods of data transmutation and part of the display like the tic-tac-toe game example quoted in this article. You will find that the principle of writing in python. But when you want to move a display to an OLED, you’ll need to code the connection to the OLED and figure out how to code the display to fit the device, for example. It’s also not very fun to play as the computer randomly thinks and do not choose the best point. Therefore, if you want the game to be more fun and more challenging, the reader must add more computer thinking than random …. Have fun with programming.

If you want to talk with us, feel free to leave comments below!!

(C) 2020-2021, By Jarut Busarathid and Danai Jedsadathitikul

Updated 2021-11-30