
Task

Play a game of tic-tac-toe.

Ensure that legal moves are played and that a winning position is notified.


Tic-tac-toe   is also known as:

   naughts and crosses 
   tic tac toe 
   tick tack toe 
   three in a row 
   tres en rayo       and
   Xs  and  Os


See also
   MathWorld™, Tic-Tac-Toe game.
   Wikipedia tic-tac-toe.


property OMask : missing value
property XMask : missing value
property winningNumbers : {7, 56, 73, 84, 146, 273, 292, 448}
property difficulty : missing value
 
repeat
   set OMask to 0
   set XMask to 0
 
   if button returned of (display dialog "Who should start?" buttons {"I shoud", "CPU"}) = "CPU" then set OMask to npcGet()
   set difficulty to button returned of (display dialog "Please choose your difficulty" buttons {"Hard", "Normal"})
 
   repeat
       set XMask to XMask + 2 ^ (nGet() - 1)
       if winnerForMask(XMask) or OMask + XMask = 511 then exit repeat
       set OMask to npcGet()
       if winnerForMask(OMask) or OMask + XMask = 511 then exit repeat
   end repeat
 
   if winnerForMask(OMask) then
       set msg to "CPU Wins!"
   else if winnerForMask(XMask) then
       set msg to "You WON!!!"
   else
       set msg to "It's a draw"
   end if
 
   display dialog msg & return & return & drawGrid() & return & return & "Do you want to play again?"
end repeat
 
on nGet()
   set theMessage to "It's your turn Player 1, please fill in the number for X" & return & return & drawGrid()
   repeat
       set value to text returned of (display dialog theMessage default answer "")
       if (offset of value in "123456789") is not 0 then
           if not positionIsUsed(value as integer) then exit repeat
       end if
   end repeat
   return value as integer
end nGet
 
on npcGet()
   --first get the free positions
   set freeSpots to {}
   repeat with s from 1 to 9
       if not positionIsUsed(s) then set end of freeSpots to 2 ^ (s - 1)
   end repeat
   --second check if 1 move can make the CPU win
   repeat with spot in freeSpots
       if winnerForMask(OMask + spot) then return OMask + spot
   end repeat
 
   if difficulty is "Hard" and OMask is 0 then
       if XMask = 1 or XMask = 4 then return 2
       if XMask = 64 or XMask = 256 then return 128
   end if
   --third check if a user can make make it win (defensive) place it on position
   repeat with spot in freeSpots
       if winnerForMask(XMask + spot) then return OMask + spot
   end repeat
 
   --fourth check if CPU can win in two moves
   repeat with spot1 in freeSpots
       repeat with spot2 in freeSpots
           if winnerForMask(OMask + spot1 + spot2) then return OMask + spot2
       end repeat
   end repeat
   --fifth check if player can win in two moves
   repeat with spot1 in freeSpots
       repeat with spot2 in reverse of freeSpots
           if winnerForMask(XMask + spot1 + spot2) then return OMask + spot1
       end repeat
   end repeat
   --at last pick a random spot
   if XMask + OMask = 0 and difficulty = "Hard" then return 1
 
   return OMask + (some item of freeSpots)
end npcGet
 
on winnerForMask(mask)
   repeat with winLine in winningNumbers
       if BWAND(winLine, mask) = contents of winLine then return true
   end repeat
   return false
end winnerForMask
 
on drawGrid()
   set grid to ""
   repeat with o from 0 to 8
       if BWAND(OMask, 2 ^ o) = 2 ^ o then
           set grid to grid & "O"
       else if BWAND(XMask, 2 ^ o) = 2 ^ o then
           set grid to grid & "X"
       else
           set grid to grid & o + 1
       end if
       if o is in {2, 5} then set grid to grid & return
   end repeat
   return grid
end drawGrid
 
on positionIsUsed(pos)
   return BWAND(OMask + XMask, 2 ^ (pos - 1)) = 2 ^ (pos - 1)
end positionIsUsed
 
on BWAND(n1, n2)
   set theResult to 0
   repeat with o from 0 to 8
       if (n1 mod 2) = 1 and (n2 mod 2) = 1 then set theResult to theResult + 2 ^ o
       set {n1, n2} to {n1 div 2, n2 div 2}
   end repeat
   return theResult as integer
end BWAND