Tic Tac Toe game is amongst the very first paper-and-pencil game one learns. Its principle is rather very simple; two players put their marks (X or O) on a 3X3 board. Any player that manages to put three marks either vertically, horizontally or diagonally wins the game. If play optimally by two professional players, this game can go on forever. However, in this article, we will identify the outcome of games when those marks are put on the board randomly. We will set up two experiment using R. In the first variation of the experiment, we make the marks appear randomly on the board all at once, and on the second variation of our Tic Tac Toe game, we will make the marks appear one after another.

Tic Tac Toe Function Set up

In order for us to build the different models that will allow us to evaluate the variations in games outcome in Tic Tac Toe, we will first use a function that will allow us to evaluate the winner of a particular Tic Tac Toe game. We will assign to player number 1 (X) the value 1, and to player number 2 (O) the value -1.

For this experiment, we will consider the playground as a matrix, player number 1 will win if the sum of one or more columns, diagonals or rows is 3, and if there aren’t rows, columns, or diagonals that sum to -3. In other words, player 1 wins if the sum of one row, column or diagonal is equal to 3 and player two wins when that summation is equal to -3. If the result does not satisfy those conditions, we will record the game as a draw.

winner <- function(board) { square <- matrix(board, nrow = 3) horsum <- rowSums(square) versum <- colSums(square) diag1 <- sum(diag(square)) diag2 <- square[1,3] + square[2,2] + square[3,1] if (3 %in% c(horsum, versum, diag1, diag2) &! -3 %in% c(horsum, versum, diag1, diag2)) return (1) if (-3 %in% c(horsum, versum, diag1, diag2) &! 3 %in% c(horsum, versum, diag1, diag2)) return (-1) else return(0) }

First Variation

The first variation of our tic tac toe game is the one in which the board is automatically generated. In this Tic Tac Toe variation, we will create a function that will take in the number of games n that we would like to simulate. Set the seed, Reset the outcome of the games (player 1 win, player 2 win or draw), call a variable x that represents the moves. Create a loop that will represent each game up to n. Then we will sample x on a board of 9 tiles. That means that a matrix of 9 random values of -1 and 1 is created. From those values, we will call the win function to determine the winner of the game.

variation1 <- function(n=100){set.seed(23) #reset the values countPlayer1<- 0 countplayer0 <-0 Drawss<- 0 x<- c(-1,1) #variation 1 for (i in 1:n) { game <- sample(x,9, replace = TRUE) # fill board with random X and O win<-winner(game) if (1 %in% win){countPlayer1=countPlayer1+1} if (-1 %in% win){countplayer0=countplayer0+1} if (0 %in% win){Drawss=Drawss+1} } return(c(countPlayer1, countplayer0, Drawss)) }

Let’s run a simulation of 100 games

result <- variation1(100) print (paste("player X wins :", result[1], " ; ", (result[1]/100)*100 ," %" ))

## [1] "player X wins : 44 ; 44 %"

print (paste("player 0 wins :", result[2], " ; ", (result[2]/100)*100 ," %" ))

## [1] "player 0 wins : 32 ; 32 %"

print (paste("Draw :", result[3], " ; ", (result[3]/100)*100 ," %" ))

## [1] "Draw : 24 ; 24 %"

barplot(result,names.arg = c("player X wins", "player O wins", "Draw"), ylim = c(0,100))

With a seed set to 23, the probability of the player X to win are 44%. It seems that the player 1 or X has a higher probability of winning, but obviously, this is due to the number of simulation fixed to 100: if we want a more accurate probability, we have to do more simulations. Let’s apply the same function 10000 times.

result <- variation1(10000) print (paste("player X wins :", result[1], " ; ", (result[1]/10000)*100 ," %" ))

## [1] "player X wins : 3900 ; 39 %"

print (paste("player 0 wins :", result[2], " ; ", (result[2]/10000)*100 ," %" ))

## [1] "player 0 wins : 3851 ; 38.51 %"

print (paste("Draw :", result[3], " ; ", (result[3]/10000)*100 ," %" ))

## [1] "Draw : 2249 ; 22.49 %"

barplot(result,names.arg = c("player X wins", "player O wins", "Draw"), ylim = c(0,10000))

As we intuitively expected, if we fill each square with a random X or O with equal probability (0.5), the two gamers have approximately the same probability of winning (near 0.4), and a probability of drawing of 20%.

Variation 2

In this variation, we have the variable representing the counts for each winner. We have a for loop (representing the number of games to be played) that will contain the board game made of 9 tiles. Finally, we put the game process within a while loop that will run as long as the board is not full or until we have a winner. It first figures out which squares are empty, then when the program finds all empty squares it randomly places an X or O, and changes the board matrix. Once the board changes, the program evaluates if there is a winner and changes the player.

#variation 2 variation2 <- function(n=100){ countPlayer1 <- 0 countplayer0 <-0 Drawss<- 0 for (i in 1:n) { game <- rep(0, 9) win <- 0 player <- 1 while (0 %in% game & win == 0) { # Keep playing until win or full board empty <- which(game == 0) # find empty tiles move <- empty[sample(length(empty), 1)] # players move game[move] <- player # Change board win <- winner(game) player <- player * -1 } if (1 %in% win){countPlayer1=countPlayer1+1} if (-1 %in% win){countplayer0=countplayer0+1} if (0 %in% win){Drawss=Drawss+1} } return(c(countPlayer1, countplayer0, Drawss)) }

Now lets run a 100 games using this simulation to figure out the various outcome of the games.

result <- variation2(100) print (paste("player X wins :", result[1], " ; ", (result[1]/100)*100 ," %" ))

## [1] "player X wins : 61 ; 61 %"

print (paste("player 0 wins :", result[2], " ; ", (result[2]/100)*100 ," %" ))

## [1] "player 0 wins : 26 ; 26 %"

print (paste("Draw :", result[3], " ; ", (result[3]/100)*100 ," %" ))

## [1] "Draw : 13 ; 13 %"

barplot(result,names.arg = c("player X wins", "player 0 wins", "Draw"), ylim = c(0,100))

In the second variation, we realize that player X wins about 60 % of the time, player O 30% and draws occur around 10%. Lets run a simulation of 10 000 games to double check our results.

result <- variation2(10000) print (paste("player X wins :", result[1], " ; ", (result[1]/10000)*100 ," %" ))

## [1] "player X wins : 5758 ; 57.58 %"

print (paste("player 0 wins :", result[2], " ; ", (result[2]/10000)*100 ," %" ))

## [1] "player 0 wins : 2979 ; 29.79 %"

print (paste("Draw :", result[3], " ; ", (result[3]/10000)*100 ," %" ))

## [1] "Draw : 1263 ; 12.63 %"

barplot(result,names.arg = c("player X wins", "player 0 wins", "Draw"), ylim = c(0,10000))

This time the Player $X$ has a higher probability of a win. This makes sense because he plays first, thus he can put 5 X, instead of 4 O. In this case, the probability that X wins are approximately 60%, whereas player 2 or O has a 30% chance of winning. We record draws 10% of the time.

If there are any improvements, suggestions, questions, you might have, feel free to express them in the comment section down below.