In tic-tac-toe players take turns placing either an “X” (player 1) or an “O” (player 2) in any open cell to try to be the first person to get three spaces in a row either horizontally, vertically, or diagonally. There are some assumptions: the players are equally skilled and playing at random, if all cells are filled and there are no winners then the game is a stalemate, if a cell is filled then another player cannot take it, and we cannot determine how many turns will be taken in a game. In the gathering data we want to keep track of every play on every game and keep track of who wins each game, or if it is a stalemate.

We want to set up a function to play a turn, which will also let you know a cell is taken and to try again. There is an additional function for updating the existing game board as the players play. This is done based on the x and y coordinates and which player is next for a turn.

play <- function(board){
  repeat{
    x<-sample.int(3,1,replace=TRUE)
    y<-sample.int(3,1,replace=TRUE)
  
    if (board[x,y]==0) {
      break
    } 
  }
  return(c(x,y))
}

update_board <- function(player, play){
  if (player==1) {
    board[play[1],play[2]] <- p1
  } else {
    board[play[1],play[2]] <- p2
  }
  return(board)
}

Then we want to set up functions to check for a win by either Player 1 or Player 2 by taking the sum across columns, rows, and diagonals. It fallows that a sum=3 means that player 1 is the winner and a sum=30 means that player 2 is the winner.

winner_p1 <-function(board) {
  winner <- FALSE
  if (sum(board$X1) == p1*3 | sum(board$X2) == p1*3 | sum(board$X3) == p1*3) {
    winner <- TRUE
  } else if (sum(board[1,]) == p1*3 | sum(board[2,]) == p1*3 | sum(board[3,]) == p1*3) {
    winner <- TRUE
  } else if (board[1,1]+board[2,2]+board[3,3] == p1*3 | board[1,3]+board[2,2]+board[3,1] == p1*3) {
    winner <- TRUE
  }
  return(winner)
}


winner_p2 <-function(board) {
  winner <- FALSE
  if (sum(board$X1) == p2*3 | sum(board$X2) == p2*3 | sum(board$X3) == p2*3) {
    winner <- TRUE
  } else if (sum(board[1,]) == p2*3 | sum(board[2,]) == p2*3 | sum(board[3,]) == p2*3) {
    winner <- TRUE
  } else if (board[1,1]+board[2,2]+board[3,3] == p2*3 | board[1,3]+board[2,2]+board[3,1] == p2*3) {
    winner <- TRUE
  }
  return(winner)
}

With a new board for each game we play 10,000 of them in total tracking and saving each play to complile a master list which we can determine some probabilities from. For instance, can we see any advantage to playing first? Well it turns out that player 1 has the advantage winning 60%. Then by looking at all first moves made by player 1 we can determine which is the best to make, and it looks like 750 are won play cell (2,2). However, (1,1) and (3,1) aren’t far behind. Though the data does reaveal that if player 1 plays (2,2) the odds are very slim that player 2 can win.

set.seed(5)
repeat {

board  <- data.frame(matrix(nrow=3, ncol=3, dimnames = list(c("1","2","3"),c("1","2","3"))))
  board[is.na(board)] <- 0
  current_game        <-list()
  turn_count          <-1
  
    repeat{
    if (turn_count > 2) {
      if (winner_p1(board)==TRUE) {
        current_game[[turn_count]] <- p1win_code
        break
      }  
      if (winner_p2(board)==TRUE) {
        current_game[[turn_count]] <- p2win_code
        break
      }
      if (!(0 %in% board$X1 | 0 %in% board$X2 | 0 %in% board$X3)) {
        current_game[[turn_count]] <- stalemate_code
        break
      }
    }
      
    if (turn_count/2==floor(turn_count/2)) {
        played<-play(board)
        board<-update_board(2,played)
      } else {
        played<-play(board)
        board<-update_board(1,played)
      }
        current_game[[turn_count]]<-played
        turn_count <- turn_count + 1
    }
all_games[[game_count]]<-current_game
if (game_count==total_games){break}
game_count<-game_count+1
}  


unlisted_games<-unlist(all_games)
p1wins        <-length(unlisted_games[unlisted_games==p1win_code])
p2wins        <-length(unlisted_games[unlisted_games==p2win_code])
ties          <-length(unlisted_games[unlisted_games==stalemate_code])
p1wins
## [1] 5856
p2wins
## [1] 2868
ties
## [1] 1276
p1games   <-sapply(all_games, function(x){p1win_code %in% x})
p1wins    <-all_games[p1games]
firstplay <-sapply(p1wins, function(x){paste(unlist(x[1])[1],unlist(x[1])[2], sep="")})
table(firstplay)
## firstplay
##  11  12  13  21  22  23  31  32  33 
## 658 565 740 617 775 576 651 593 681
p2games   <-sapply(all_games, function(x){p2win_code %in% x})
p2wins    <-all_games[p2games]
secplay   <-sapply(p2wins, function(x){paste(unlist(x[2])[1],unlist(x[2])[2], sep="")})
table(secplay)
## secplay
##  11  12  13  21  22  23  31  32  33 
## 339 262 350 272 447 230 341 267 360
playfirst    <-sapply(p2wins, function(x){identical(x[[1]],as.integer(c(2,2)))})
p2wins       <-p2wins[playfirst]
secplay      <-sapply(p2wins, function(x){paste(unlist(x[2])[1],unlist(x[2])[2], sep="")})
table(secplay)
## secplay
## 11 12 13 21 23 31 32 33 
## 37 19 30 21 24 28 24 26