Tic-tac-toe

From Rosetta Code
Revision as of 16:30, 28 May 2011 by Peter (talk | contribs) (added Icon/Unicon example)
Task
Tic-tac-toe
You are encouraged to solve this task according to the task description, using any language you may know.

Play a game of tic-tac-toe. Ensure that legal moves are played and that a winning position is notified.

AutoHotkey

This program uses a Gui with 9 buttons. Clicking on one will place an X there, disable the button, and cause the program to go somewhere. It plays logically, trying to win, trying to block, or playing randomly in that order. <lang AutoHotkey>Gui, Add, Button, x12 y12 w30 h30 vB1 gButtonHandler, Gui, Add, Button, x52 y12 w30 h30 vB2 gButtonHandler, Gui, Add, Button, x92 y12 w30 h30 vB3 gButtonHandler, Gui, Add, Button, x12 y52 w30 h30 vB4 gButtonHandler, Gui, Add, Button, x52 y52 w30 h30 vB5 gButtonHandler, Gui, Add, Button, x92 y52 w30 h30 vB6 gButtonHandler, Gui, Add, Button, x12 y92 w30 h30 vB7 gButtonHandler, Gui, Add, Button, x52 y92 w30 h30 vB8 gButtonHandler, Gui, Add, Button, x92 y92 w30 h30 vB9 gButtonHandler,

Generated using SmartGUI Creator 4.0

Gui, Show, x127 y87 h150 w141, Tic-Tac-Toe Winning_Moves := "123,456,789,147,258,369,159,357" Return

ButtonHandler:

   ; Fired whenever the user clicks on an enabled button
   Go(A_GuiControl,"X")
   GoSub MyMove

Return

MyMove: ; Loops through winning moves. First attempts to win, then to block, then a random move

   Went=0
   Loop, parse, Winning_Moves,`,
   {
       Current_Set := A_LoopField
       X:=O:=0
       Loop, parse, Current_Set
       {
           GuiControlGet, Char,,Button%A_LoopField%
           If ( Char = "O" )
               O++
           If ( Char = "X" )
               X++
       }
       If ( O = 2 and X = 0 ) or ( X = 2 and O = 0 ){
           Finish_Line(Current_Set)
           Went = 1
           Break ; out of the Winning_Moves Loop to ensure the computer goes only once
       }
   }
   If (!Went)
       GoSub RandomMove

Return

Go(Control,chr){

   GuiControl,,%Control%, %chr%
   GuiControl,Disable,%Control%
   GoSub, CheckWin

}

CheckWin:

   Loop, parse, Winning_Moves,`,
   {
       Current_Set := A_LoopField
       X:=O:=0
       Loop, parse, Current_Set
       {
           GuiControlGet, Char,,Button%A_LoopField%
           If ( Char = "O" )
               O++
           If ( Char = "X" )
               X++
       }
       If ( O = 3 ){
           Msgbox O Wins!
           GoSub DisableAll
           Break
       }
       If ( X = 3 ){
           MsgBox X Wins!
           GoSub DisableAll
           Break
       }
   }

return

DisableAll:

   Loop, 9
       GuiControl, Disable, Button%A_Index%

return

Finish_Line(Set){ ; Finish_Line is called when a line exists with 2 of the same character. It goes in the remaining spot, thereby blocking or winning.

   Loop, parse, set
   {
       GuiControlGet, IsEnabled, Enabled, Button%A_LoopField%
       Control=Button%A_LoopField%
       If IsEnabled
           Go(Control,"O")
   }

}

RandomMove:

   Loop{
       Random, rnd, 1, 9
       GuiControlGet, IsEnabled, Enabled, Button%rnd%
       If IsEnabled
       {
           Control=Button%rnd%
           Go(Control,"O")
           Break
       }
   }

return

GuiClose: ExitApp </lang>

D

<lang d>import std.stdio, std.string, std.algorithm, std.conv, std.random;

struct Board {

   int[9] board;
   string positionString(int pos)
       in {
           assert(pos >= 0);
           assert(pos < 9);
       }
       out(ret) {
           assert(ret.length == 1);
       }
   body {
       if(board[pos])
           return board[pos] == 1 ? "X" : "O";
       return to!string(pos + 1);
   }

   string toString() {
       string lineSeparator = "-+-+-\n";
       string row(int start) {
           return format("%s|%s|%s\n",
               positionString(start + 0),
               positionString(start + 1),
               positionString(start + 2));
       }

       string ret;

       ret ~= row(0);
       ret ~= lineSeparator;
       ret ~= row(3);
       ret ~= lineSeparator;
       ret ~= row(6);

       return ret;
   }

   int[] openPositions() {
       int[] ret;
       foreach(i, v; board) {
           if(!v)
               ret ~= i;
       }

       return ret;
   }

   bool isAvailable(int position) {
       if(position < 0 || position >= 9)
           return false;
       return board[position] == 0;
   }

   bool finished() {
       return winner() != -1;
   }

   int winner() {
       enum wins = [
           [0,1,2], [3,4,5], [6,7,8],
           [0,3,6], [1,4,7], [2,5,8],
           [0,4,8], [2,4,6]
       ];

       foreach(win; wins) {
           int desired = board[win[0]];
           if(desired == 0)
               continue; // nobody wins on this one

           // the same player needs to take all three positions
           if(board[win[1]] == desired && board[win[2]] == desired)
               return desired; // a winner!
       }

       if(openPositions().length == 0)
           return 0; // a draw

       return -1; // the game is still going
   }

   int suggestMove(int player)
       out(ret) {
           assert(ret >= 0);
           assert(ret < 9);
           assert(isAvailable(ret));
       }
   body {
       return randomCover(openPositions(),
           Random(unpredictableSeed)).front;
   }

}

void main() {

   writeln("\tTic-tac-toe game player.");


   Board board;
   int currentPlayer = 1;

   while(!board.finished()) {
       writeln(board);

       int move;

       if(currentPlayer == 1) {
           do {
               writef("Input the index of your move (from %s): ",
                   map!"a+1"(board.openPositions()));
               readf("%d\n", &move);

               move--; // zero based indexing
           } while(!board.isAvailable(move));
       } else {
           move = board.suggestMove(currentPlayer);
       }

       assert(board.isAvailable(move));

       writefln("%s chose %d", currentPlayer == 1 ? "You" : "I", move + 1);

       board.board[move] = currentPlayer;

       currentPlayer = currentPlayer == 2 ? 1 : 2;
   }

   int winner = board.winner();
   if(winner == 0)
       writeln("\nDraw");
   if(winner == 1)
       writeln("\nYou win!");
   if(winner == 2)
       writeln("\nComputer wins.");

}</lang> Output:

	Tic-tac-toe game player.
1|2|3
-+-+-
4|5|6
-+-+-
7|8|9

Input the index of your move (from [1, 2, 3, 4, 5, 6, 7, 8, 9]): 1
You chose 1
X|2|3
-+-+-
4|5|6
-+-+-
7|8|9

I chose 5
X|2|3
-+-+-
4|O|6
-+-+-
7|8|9

Input the index of your move (from [2, 3, 4, 6, 7, 8, 9]): 2
You chose 2
X|X|3
-+-+-
4|O|6
-+-+-
7|8|9

I chose 4
X|X|3
-+-+-
O|O|6
-+-+-
7|8|9

Input the index of your move (from [3, 6, 7, 8, 9]): 3
You chose 3

You win!

F#

A purely-functional solution with a naive (but perfect) computer player implementation. The first move is played randomly by the computer.

<lang fsharp>type Brick =

| Empty
| Computer
| User

let brickToString = function

| Empty -> ' '
| Computer -> 'X'
| User -> 'O'

// y -> x -> Brick type Board = Map<int, Map<int, Brick> >

let emptyBoard =

 let emptyRow = Map.ofList [0,Empty; 1,Empty; 2,Empty]
 Map.ofList [0,emptyRow; 1,emptyRow; 2,emptyRow]

let get (b:Board) (x,y) = b.[y].[x]

let set player (b:Board) (x,y) : Board =

 let row = b.[y].Add(x, player)
 b.Add(y, row)

let winningPositions =

 [for x in [0..2] -> x,x] // first diagonal
 ::[for x in [0..2] -> 2-x,x] // second diagonal
 ::[for y in [0..2] do
    yield! [[for x in [0..2]->(y,x)]; // columns
            [for x in [0..2] -> (x,y)]]] // rows
 

let hasWon player board =

 List.exists
   (fun ps -> List.forall (fun pos -> get board pos = player) ps)
   winningPositions

let freeSpace board =

 [for x in 0..2 do
    for y in 0..2 do
      if get board (x,y) = Empty then yield x,y]

type Evaluation =

| Win
| Draw
| Lose

let rec evaluate board move =

 let b2 = set Computer board move
 if hasWon Computer b2 then Win
 else
   match freeSpace b2 with
   | [] -> Draw
   | userChoices -> 
      let b3s = List.map (set User b2) userChoices
      if List.exists (hasWon User) b3s then Lose
      elif List.exists (fun b3 -> bestOutcome b3 = Lose) b3s
      then Lose
      elif List.exists (fun b3 -> bestOutcome b3 = Draw) b3s
      then Draw
      else Win
       

and findBestChoice b =

 match freeSpace b with
 | [] -> ((-1,-1), Draw)
 | choices -> 
   match List.tryFind (fun c -> evaluate b c = Win) choices with
   | Some c -> (c, Win)
   | None -> match List.tryFind (fun c -> evaluate b c = Draw) choices with
             | Some c -> (c, Draw)
             | None -> (List.head choices, Lose)

and bestOutcome b = snd (findBestChoice b)

let bestChoice b = fst (findBestChoice b)

let computerPlay b = set Computer b (bestChoice b)

let printBoard b =

 printfn "   | A | B | C |"
 printfn "---+---+---+---+"
 for y in 0..2 do
  printfn " %d | %c | %c | %c |"
   (3-y)
   (get b (0,y) |> brickToString)
   (get b (1,y) |> brickToString)
   (get b (2,y) |> brickToString)
  printfn "---+---+---+---+"

let rec userPlay b =

 printfn "Which field do you play? (format: a1)"
 let input = System.Console.ReadLine()
 if input.Length <> 2
    || input.[0] < 'a' || input.[0] > 'c'
    || input.[1] < '1' || input.[1] > '3' then
    printfn "illegal input"
    userPlay b
 else
    let x = int(input.[0]) - int('a')
    let y = 2 - int(input.[1]) + int('1')
    if get b (x,y) <> Empty then
      printfn "Field is not free."
      userPlay b
    else
      set User b (x,y)

let rec gameLoop b player =

 if freeSpace b = [] then
   printfn "Game over. Draw."
 elif player = Computer then
   printfn "Computer plays..."
   let b2 = computerPlay b
   printBoard b2
   if hasWon Computer b2 then
     printfn "Game over. I have won."
   else
     gameLoop b2 User
 elif player = User then
   let b2 = userPlay b
   printBoard b2
   if hasWon User b2 then
     printfn "Game over. You have won."
   else
     gameLoop b2 Computer

// randomly choose an element of a list let choose =

 let rnd = new System.Random()
 fun (xs:_ list) -> xs.[rnd.Next(xs.Length)]

// play first brick randomly printfn "Computer starts." let b = set Computer emptyBoard (choose (freeSpace emptyBoard)) printBoard b gameLoop b User</lang>

Example game:

Computer starts.
   | A | B | C |
---+---+---+---+
 3 |   |   | X |
---+---+---+---+
 2 |   |   |   |
---+---+---+---+
 1 |   |   |   |
---+---+---+---+
Which field do you play? (format: a1)
a1
   | A | B | C |
---+---+---+---+
 3 |   |   | X |
---+---+---+---+
 2 |   |   |   |
---+---+---+---+
 1 | O |   |   |
---+---+---+---+
Computer plays...
   | A | B | C |
---+---+---+---+
 3 | X |   | X |
---+---+---+---+
 2 |   |   |   |
---+---+---+---+
 1 | O |   |   |
---+---+---+---+
Which field do you play? (format: a1)
b3
   | A | B | C |
---+---+---+---+
 3 | X | O | X |
---+---+---+---+
 2 |   |   |   |
---+---+---+---+
 1 | O |   |   |
---+---+---+---+
Computer plays...
   | A | B | C |
---+---+---+---+
 3 | X | O | X |
---+---+---+---+
 2 |   |   |   |
---+---+---+---+
 1 | O |   | X |
---+---+---+---+
Which field do you play? (format: a1)
c2
   | A | B | C |
---+---+---+---+
 3 | X | O | X |
---+---+---+---+
 2 |   |   | O |
---+---+---+---+
 1 | O |   | X |
---+---+---+---+
Computer plays...
   | A | B | C |
---+---+---+---+
 3 | X | O | X |
---+---+---+---+
 2 |   | X | O |
---+---+---+---+
 1 | O |   | X |
---+---+---+---+
Game over. I have won.

Icon and Unicon

The following works in both Icon and Unicon. The computer plays randomly against a human player, with legal moves enforced and wins/draws notified.

<lang Icon>

  1. Play TicTacToe

$define E " " # empty square $define X "X" # X piece $define O "O" # O piece

  1. -- define a board

record Board(a, b, c, d, e, f, g, h, i)

procedure display_board (board, player)

 write ("\n===============")
 write (board.a || " | " || board.b || " | " || board.c)
 write ("---------")
 write (board.d || " | " || board.e || " | " || board.f)
 write ("---------")
 write (board.g || " | " || board.h || " | " || board.i)

end

  1. return a set of valid moves (empty positions) in given board

procedure empty_fields (board)

 fields := set()
 every i := !fieldnames(board) do 
   if (board[i] == E) then insert (fields, i)
 return fields

end

procedure game_start ()

 return Board (E, E, E, E, E, E, E, E, E)

end

procedure game_is_drawn (board)

 return *empty_fields(board) = 0

end

procedure game_won_by (board, player)

 return (board.a == board.b == board.c == player) |
        (board.d == board.e == board.f == player) |
        (board.g == board.h == board.i == player) |
        (board.a == board.d == board.g == player) | 
        (board.b == board.e == board.h == player) |
        (board.c == board.f == board.i == player) |
        (board.a == board.e == board.i == player) |
        (board.g == board.e == board.c == player)

end

procedure game_over (board)

 return game_is_drawn (board) | game_won_by (board, O) | game_won_by (board, X)

end

  1. -- players make their move on the board
  2. assume there is at least one empty square

procedure human_move (board, player)

 choice := "z"
 options := empty_fields (board)
 # keep prompting until player selects a valid square
 until member (options, choice) do {
   writes ("Choose one of: ")
   every writes (!options || " ")
   writes ("\n> ") 
   choice := read ()
 }
 board[choice] := player

end

  1. pick and make a move at random from empty positions

procedure random_move (board, player)

 board[?empty_fields(board)] := player

end

  1. -- manage the game play

procedure play_game ()

 # hold procedures for players' move in variables
 player_O := random_move
 player_X := human_move
 # randomly determine if human or computer moves first
 if (?2 = 0) 
   then {
     write ("Human plays first as O")
     player_O := human_move
     player_X := random_move
   }
   else write ("Computer plays first, human is X")
 # set up the game to start
 board := game_start ()
 player := O
 display_board (board, player)
 # loop until the game is over, getting each player to move in turn
 until game_over (board) do { 
   write (player || " to play next")
   # based on player, prompt for the next move
   if (player == O)
     then (player_O (board, player))
     else (player_X (board, player))
   # change player to move
   player := if (player == O) then X else O
   display_board (board, player)
 }
 # finish by writing out result
 if game_won_by (board, O) 
   then write ("O won") 
   else if game_won_by (board, X) 
     then write ("X won")
     else write ("draw") # neither player won, so must be a draw

end

  1. -- get things started

procedure main ()

 play_game ()

end </lang>

PicoLisp

This solution doesn't bother about the game logic, but simply uses the alpha-beta-pruning 'game' function in the "simul" library. <lang PicoLisp>(load "@lib/simul.l") # for 'game' function

(de display ()

  (for Y (3 2 1)
     (prinl "   +---+---+---+")
     (prin " " Y)
     (for X (1 2 3)
        (prin " | " (or (get *Board X Y) " ")) )
     (prinl " |") )
  (prinl "   +---+---+---+")
  (prinl "     a   b   c") )

(de find3 (P)

  (find
     '((X Y DX DY)
        (do 3
           (NIL (= P (get *Board X Y)))
           (inc 'X DX)
           (inc 'Y DY)
           T ) )
     (1 1 1 1 2 3 1 1)
     (1 2 3 1 1 1 1 3)
     (1 1 1 0 0 0 1 1)
     (0 0 0 1 1 1 1 -1) ) )

(de myMove ()

  (when
     (game NIL 8
        '((Flg)     # Moves
           (unless (find3 (or (not Flg) 0))
              (make
                 (for (X . L) *Board
                    (for (Y . P) L
                       (unless P
                          (link
                             (cons
                                (cons X Y (or Flg 0))
                                (list X Y) ) ) ) ) ) ) ) )
        '((Mov) # Move
           (set (nth *Board (car Mov) (cadr Mov)) (cddr Mov)) )
        '((Flg)     # Cost
           (if (find3 (or Flg 0)) -100 0) ) )
     (let Mov (caadr @)
        (set (nth *Board (car Mov) (cadr Mov)) 0) )
     (display) ) )

(de yourMove (X Y)

  (and
     (sym? X)
     (>= 3 (setq X (- (char X) 96)) 1)
     (num? Y)
     (>= 3 Y 1)
     (not (get *Board X Y))
     (set (nth *Board X Y) T)
     (display) ) )

(de main ()

  (setq *Board (make (do 3 (link (need 3)))))
  (display) )

(de go Args

  (cond
     ((not (yourMove (car Args) (cadr Args)))
        "Illegal move!" )
     ((find3 T) "Congratulation, you won!")
     ((not (myMove)) "No moves")
     ((find3 0) "Sorry, you lost!") ) )</lang>

Output:

: (main)
   +---+---+---+
 3 |   |   |   |
   +---+---+---+
 2 |   |   |   |
   +---+---+---+
 1 |   |   |   |
   +---+---+---+
     a   b   c

: (go a 1)
   +---+---+---+
 3 |   |   |   |
   +---+---+---+
 2 |   |   |   |
   +---+---+---+
 1 | T |   |   |
   +---+---+---+
     a   b   c
   +---+---+---+
 3 |   |   |   |
   +---+---+---+
 2 |   | 0 |   |
   +---+---+---+
 1 | T |   |   |
   +---+---+---+
     a   b   c

Python

The computer enforces the rules but plays a random game. <lang python>

   Tic-tac-toe game player.
   Input the index of where you wish to place your mark at your turn.

import random

board = list('123456789') wins = ((0,1,2), (3,4,5), (6,7,8),

       (0,3,6), (1,4,7), (2,5,8),
       (0,4,8), (2,4,6))

def printboard():

   print('\n'.join(' '.join(board[x:x+3]) for x in(0,3,6)))

def score():

   for w in wins:
       b = board[w[0]]
       if b in 'XO' and all (board[i] == b for i in w):
           return b, [i+1 for i in w]
   return None, None

def finished():

   return all (b in 'XO' for b in board)

def space():

   return [ b for b in board if b not in 'XO']

def my_turn(xo):

   options = space()
   choice = random.choice(options)
   board[int(choice)-1] = xo
   return choice

def your_turn(xo):

   options = space()
   while True:
       choice = input(" Put your %s in any of these positions: %s "
                      % (xo, .join(options))).strip()
       if choice in options:
           break
       print( "Whoops I don't understand the input" )
   board[int(choice)-1] = xo
   return choice

def me(xo='X'):

   printboard()
   print('I go at', my_turn(xo))
   return score()
   assert not s[0], "\n%s wins across %s" % s

def you(xo='O'):

   printboard()
   # Call my_turn(xo) below for it to play itself
   print('You went at', your_turn(xo))
   return score()
   assert not s[0], "\n%s wins across %s" % s


print(__doc__) while not finished():

   s = me('X')
   if s[0]:
       printboard()
       print("\n%s wins across %s" % s)
       break
   if not finished():
       s = you('O')
       if s[0]:
           printboard()
           print("\n%s wins across %s" % s)
           break

else:

   print('\nA draw')

</lang>

Sample Game

    Tic-tac-toe game player.
    Input the index of where you wish to place your mark at your turn.

1 2 3
4 5 6
7 8 9
I go at 9
1 2 3
4 5 6
7 8 X
 Put your O in any of these positions: 12345678 1
You went at 1
O 2 3
4 5 6
7 8 X
I go at 3
O 2 X
4 5 6
7 8 X
 Put your O in any of these positions: 245678 4
You went at 4
O 2 X
O 5 6
7 8 X
I go at 2
O X X
O 5 6
7 8 X
 Put your O in any of these positions: 5678 7
You went at 7
O X X
O 5 6
O 8 X

O wins across [1, 4, 7]

Better skilled player

In this version, The computer player will first complete a winning line of its own if it can, otherwise block a winning line of its opponent if they have two in a row, or then choose a random move.

<lang python>

   Tic-tac-toe game player.
   Input the index of where you wish to place your mark at your turn.

import random

board = list('123456789') wins = ((0,1,2), (3,4,5), (6,7,8),

       (0,3,6), (1,4,7), (2,5,8),
       (0,4,8), (2,4,6))

def printboard():

   print('\n-+-+-\n'.join('|'.join(board[x:x+3]) for x in(0,3,6)))

def score(board=board):

   for w in wins:
       b = board[w[0]]
       if b in 'XO' and all (board[i] == b for i in w):
           return b, [i+1 for i in w]
   return None

def finished():

   return all (b in 'XO' for b in board)

def space(board=board):

   return [ b for b in board if b not in 'XO']

def my_turn(xo, board):

   options = space()
   choice = random.choice(options)
   board[int(choice)-1] = xo
   return choice

def my_better_turn(xo, board):

   'Will return a next winning move or block your winning move if possible'
   ox = 'O' if xo =='X' else 'X'
   oneblock = None
   options  = [int(s)-1 for s in space(board)]
   for choice in options:
       brd = board[:]
       brd[choice] = xo
       if score(brd):
           break
       if oneblock is None:
           brd[choice] = ox
           if score(brd):
               oneblock = choice
   else:
       choice = oneblock if oneblock is not None else random.choice(options)
   board[choice] = xo
   return choice+1

def your_turn(xo, board):

   options = space()
   while True:
       choice = input("\nPut your %s in any of these positions: %s "
                      % (xo, .join(options))).strip()
       if choice in options:
           break
       print( "Whoops I don't understand the input" )
   board[int(choice)-1] = xo
   return choice

def me(xo='X'):

   printboard()
   print('\nI go at', my_better_turn(xo, board))
   return score()

def you(xo='O'):

   printboard()
   # Call my_turn(xo, board) below for it to play itself
   print('\nYou went at', your_turn(xo, board))
   return score()


print(__doc__) while not finished():

   s = me('X')
   if s:
       printboard()
       print("\n%s wins along %s" % s)
       break
   if not finished():
       s = you('O')
       if s:
           printboard()
           print("\n%s wins along %s" % s)
           break

else:

   print('\nA draw')</lang>

Sample output

    Tic-tac-toe game player.
    Input the index of where you wish to place your mark at your turn.

1|2|3
-+-+-
4|5|6
-+-+-
7|8|9

I go at 2
1|X|3
-+-+-
4|5|6
-+-+-
7|8|9

Put your O in any of these positions: 13456789 5

You went at 5
1|X|3
-+-+-
4|O|6
-+-+-
7|8|9

I go at 1
X|X|3
-+-+-
4|O|6
-+-+-
7|8|9

Put your O in any of these positions: 346789 3

You went at 3
X|X|O
-+-+-
4|O|6
-+-+-
7|8|9

I go at 7
X|X|O
-+-+-
4|O|6
-+-+-
X|8|9

Put your O in any of these positions: 4689 4

You went at 4
X|X|O
-+-+-
O|O|6
-+-+-
X|8|9

I go at 6
X|X|O
-+-+-
O|O|X
-+-+-
X|8|9

Put your O in any of these positions: 89 9

You went at 9
X|X|O
-+-+-
O|O|X
-+-+-
X|8|O

I go at 8

A draw

Tcl

Translation of: Python

<lang tcl>package require Tcl 8.6

  1. This code splits the players from the core game engine

oo::class create TicTacToe {

   variable board player letter who
   constructor {player1class player2class} {

set board {1 2 3 4 5 6 7 8 9} set player(0) [$player1class new [self] [set letter(0) "X"]] set player(1) [$player2class new [self] [set letter(1) "O"]] set who 0

   }
   method PrintBoard {} {

lassign $board a1 b1 c1 a2 b2 c2 a3 b3 c3 puts [format " %s | %s | %s" $a1 $b1 $c1] puts "---+---+---" puts [format " %s | %s | %s" $a2 $b2 $c2] puts "---+---+---" puts [format " %s | %s | %s" $a3 $b3 $c3]

   }
   method WinForSomeone {} {

foreach w { {0 1 2} {3 4 5} {6 7 8} {0 3 6} {1 4 7} {2 5 8} {0 4 8} {2 4 6} } { set b [lindex $board [lindex $w 0]] if {$b ni "X O"} continue foreach i $w {if {[lindex $board $i] ne $b} break} if {[lindex $board $i] eq $b} { foreach p $w {lappend w1 [expr {$p+1}]} return [list $b $w1] } } return ""

   }
   method status {} {

return $board

   }
   method IsDraw {} {

foreach b $board {if {[string is digit $b]} {return false}} return true

   }
   method legalMoves {} {

foreach b $board {if {[string is digit $b]} {lappend legal $b}} return $legal

   }
   method DoATurn {} {

set legal [my legalMoves] my PrintBoard while 1 { set move [$player($who) turn] if {$move in $legal} break puts "Illegal move!" } lset board [expr {$move - 1}] $letter($who) $player($who) describeMove $move set who [expr {1 - $who}] return [my WinForSomeone]

   }
   method game {} {
       puts "    Tic-tac-toe game player.
   Input the index of where you wish to place your mark at your turn.\n"

while {![my IsDraw]} { set winner [my DoATurn] if {$winner eq ""} continue lassign $winner winLetter winSites my PrintBoard puts "\n$winLetter wins across \[[join $winSites {, }]\]" return $winLetter } puts "\nA draw"

   }

}

  1. Stupid robotic player

oo::class create RandomRoboPlayer {

   variable g
   constructor {game letter} {

set g $game

   }
   method turn {} {

set legal [$g legalMoves] return [lindex $legal [expr {int(rand()*[llength $legal])}]]

   }
   method describeMove {move} {

puts "I go at $move"

   }

}

  1. Interactive human player delegate

oo::class create HumanPlayer {

   variable g char
   constructor {game letter} {

set g $game set char $letter

   }
   method turn {} {

set legal [$g legalMoves] puts ">>> Put your $char in any of these positions: [join $legal {}]" while 1 { puts -nonewline ">>> " flush stdout gets stdin number if {$number in $legal} break puts ">>> Whoops I don't understand the input!" } return $number

   }
   method describeMove {move} {

puts "You went at $move"

   }

}

  1. Assemble the pieces

set ttt [TicTacToe new HumanPlayer RandomRoboPlayer] $ttt game</lang> Sample game:

    Tic-tac-toe game player.
    Input the index of where you wish to place your mark at your turn.

 1 | 2 | 3
---+---+---
 4 | 5 | 6
---+---+---
 7 | 8 | 9
>>> Put your X in any of these positions: 123456789
>>> 1
You went at 1
 X | 2 | 3
---+---+---
 4 | 5 | 6
---+---+---
 7 | 8 | 9
I go at 5
 X | 2 | 3
---+---+---
 4 | O | 6
---+---+---
 7 | 8 | 9
>>> Put your X in any of these positions: 2346789
>>> 7
You went at 7
 X | 2 | 3
---+---+---
 4 | O | 6
---+---+---
 X | 8 | 9
I go at 9
 X | 2 | 3
---+---+---
 4 | O | 6
---+---+---
 X | 8 | O
>>> Put your X in any of these positions: 23468
>>> 4
You went at 4
 X | 2 | 3
---+---+---
 X | O | 6
---+---+---
 X | 8 | O

X wins across [1, 4, 7]