⚠️ Warning: This is a draft ⚠️
This means it might contain formatting issues, incorrect code, conceptual problems, or other severe issues.
If you want to help to improve and eventually enable this page, please fork RosettaGit's repository and open a merge request on GitHub.
{{task|Games}} [[Category:Games]] [[File:Tic_tac_toe.jpg|500px||right]]
;Task: Play a game of [[wp:Tic-tac-toe|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''.
;See also
- [http://mathworld.wolfram.com/Tic-Tac-Toe.html MathWorld™, Tic-Tac-Toe game].
- [https://en.wikipedia.org/wiki/Tic-tac-toe Wikipedia tic-tac-toe].
Ada
with Ada.Text_IO, Ada.Numerics.Discrete_Random;
-- can play human-human, human-computer, computer-human or computer-computer
-- the computer isn't very clever: it just chooses a legal random move
procedure Tic_Tac_Toe is
type The_Range is range 1 .. 3;
type Board_Type is array (The_Range, The_Range) of Character;
package Rand is new Ada.Numerics.Discrete_Random(The_Range);
Gen: Rand.Generator; -- required for the random moves
procedure Show_Board(Board: Board_Type) is
use Ada.Text_IO;
begin
for Row in The_Range loop
for Column in The_Range loop
Put(Board(Row, Column));
end loop;
Put_Line("");
end loop;
Put_Line("");
end Show_Board;
function Find_Winner(Board: Board_Type) return Character is
-- if 'x' or 'o' wins, it returns that, else it returns ' '
function Three_Equal(A,B,C: Character) return Boolean is
begin
return (A=B) and (A=C);
end Three_Equal;
begin -- Find_Winner
for I in The_Range loop
if Three_Equal(Board(I,1), Board(I,2), Board(I,3)) then
return Board(I,1);
elsif Three_Equal(Board(1,I), Board(2,I), Board(3,I)) then
return Board(1,I);
end if;
end loop;
if Three_Equal(Board(1,1), Board(2,2), Board (3,3)) or
Three_Equal(Board(3,1), Board(2,2), Board (1,3)) then
return Board(2,2);
end if;
return ' ';
end Find_Winner;
procedure Do_Move(Board: in out Board_Type;
New_Char: Character; Computer_Move: Boolean) is
Done: Boolean := False;
C: Character;
use Ada.Text_IO;
procedure Do_C_Move(Board: in out Board_Type; New_Char: Character) is
Found: Boolean := False;
X,Y: The_Range;
begin
while not Found loop
X := Rand.Random(Gen);
Y := Rand.Random(Gen);
if (Board(X,Y) /= 'x') and (Board(X,Y) /= 'o') then
Found := True;
Board(X,Y) := New_Char;
end if;
end loop;
end Do_C_Move;
begin
if Computer_Move then
Do_C_Move(Board, New_Char);
else -- read move;
Put_Line("Choose your move, " & New_Char);
while not Done loop
Get(C);
for Row in The_Range loop
for Col in The_Range loop
if Board(Row, Col) = C then
Board(Row, Col) := New_Char;
Done := True;
end if;
end loop;
end loop;
end loop;
end if;
end Do_Move;
The_Board : Board_Type := (('1','2','3'), ('4','5','6'), ('7','8','9'));
Cnt_Moves: Natural := 0;
Players: array(0 .. 1) of Character := ('x', 'o'); -- 'x' begins
C_Player: array(0 .. 1) of Boolean := (False, False);
Reply: Character;
begin -- Tic_Tac_Toe
-- firstly, ask whether the computer shall take over either player
for I in Players'Range loop
Ada.Text_IO.Put_Line("Shall " & Players(I) &
" be run by the computer? (y=yes)");
Ada.Text_IO.Get(Reply);
if Reply='y' or Reply='Y' then
C_Player(I) := True;
Ada.Text_IO.Put_Line("Yes!");
else
Ada.Text_IO.Put_Line("No!");
end if;
end loop;
Rand.Reset(Gen); -- to initalize the random generator
-- now run the game
while (Find_Winner(The_Board) = ' ') and (Cnt_Moves < 9) loop
Show_Board(The_Board);
Do_Move(The_Board, Players(Cnt_Moves mod 2), C_Player(Cnt_Moves mod 2));
Cnt_Moves := Cnt_Moves + 1;
end loop;
Ada.Text_IO.Put_Line("This is the end!");
-- finally, output the outcome
Show_Board (The_Board);
if Find_Winner(The_Board) = ' ' then
Ada.Text_IO.Put_Line("Draw");
else
Ada.Text_IO.Put_Line("The winner is: " & Find_Winner(The_Board));
end if;
end Tic_Tac_Toe;
{{out}}
> ./tic_tac_toe
Shall x be run by the computer? (y=yes)
y
Yes!
Shall o be run by the computer? (y=yes)
n
No!
123
456
789
1x3
456
789
Choose your move, o
5
1x3
4o6
789
1x3
xo6
789
Choose your move, o
6
1x3
xoo
789
1xx
xoo
789
Choose your move, o
1
oxx
xoo
789
oxx
xoo
78x
Choose your move, o
7
oxx
xoo
o8x
This is the end!
oxx
xoo
oxx
Draw
> ./tic_tac_toe
Shall x be run by the computer? (y=yes)
n
No!
Shall o be run by the computer? (y=yes)
y
Yes!
123
456
789
Choose your move, x
6
123
45x
789
123
45x
o89
Choose your move, x
4
123
x5x
o89
123
x5x
o8o
Choose your move, x
8
123
x5x
oxo
o23
x5x
oxo
Choose your move, x
5
This is the end!
o23
xxx
oxo
The winner is: x
ALGOL W
The user can play O, X, both or neither. O goes first whether user or computer controlled.
begin
string(10) board;
% initialise the board %
procedure initBoard ; board := " 123456789";
% display the board %
procedure showBoard ;
begin
s_w := 0;
write( board(1//1), "|", board(2//1), "|", board(3//1) );
write( "-+-+-" );
write( board(4//1), "|", board(5//1), "|", board(6//1) );
write( "-+-+-" );
write( board(7//1), "|", board(8//1), "|", board(9//1) )
end showBoard ;
% returns true if board pos is free, false otherwise %
logical procedure freeSpace( integer value pos ) ;
( board(pos//1) >= "1" and board(pos//1) <= "9" );
% check for game over %
logical procedure gameOver ;
begin
logical noMoves;
noMoves := true;
for i := 1 until 9 do if noMoves then noMoves := not freeSpace( i );
noMoves
end gameOver ;
% makes the specified winning move or blocks it, if it will win %
logical procedure winOrBlock( integer value pos1, pos2, pos3
; string(1) value searchCharacter
; string(1) value playerCharacter
) ;
if board(pos1//1) = searchCharacter
and board(pos2//1) = searchCharacter
and freeSpace( pos3 )
then begin
board(pos3//1) := playerCharacter;
true
end
else if board(pos1//1) = searchCharacter
and freeSpace( pos2 )
and board(pos3//1) = searchCharacter
then begin
board(pos2//1) := playerCharacter;
true
end
else if freeSpace( pos1 )
and board(pos2//1) = searchCharacter
and board(pos3//1) = searchCharacter
then begin
board(pos1//1) := playerCharacter;
true
end
else begin
false
end winOrBlock ;
% makes a winning move or blocks a winning move, if there is one %
logical procedure makeOrBlockWinningMove( string(1) value searchCharacter
; string(1) value playerCharacter
) ;
( winOrBlock( 1, 2, 3, searchCharacter, playerCharacter )
or winOrBlock( 4, 5, 6, searchCharacter, playerCharacter )
or winOrBlock( 7, 8, 9, searchCharacter, playerCharacter )
or winOrBlock( 1, 4, 7, searchCharacter, playerCharacter )
or winOrBlock( 2, 5, 8, searchCharacter, playerCharacter )
or winOrBlock( 3, 6, 9, searchCharacter, playerCharacter )
or winOrBlock( 1, 5, 9, searchCharacter, playerCharacter )
or winOrBlock( 3, 5, 7, searchCharacter, playerCharacter )
) ;
% makes a move when there isn't an obvious winning/blocking move %
procedure move ( string(1) value playerCharacter ) ;
begin
logical moved;
moved := false;
% try for the centre, a corner or the midle of a line %
for pos := 5, 1, 3, 7, 9, 2, 4, 6, 8 do begin
if not moved and freeSpace( pos ) then begin
moved := true;
board(pos//1) := playerCharacter
end
end
end move ;
% gets a move from the user %
procedure userMove( string(1) value playerCharacter ) ;
begin
integer move;
while
begin
write( "Please enter the move for ", playerCharacter, " " );
read( move );
( move < 1 or move > 9 or not freeSpace( move ) )
end
do begin
write( "Invalid move" )
end;
board(move//1) := playerCharacter
end userMove ;
% returns true if the three board positions have the player character, %
% false otherwise %
logical procedure same( integer value pos1, pos2, pos3
; string(1) value playerCharacter
) ;
( board(pos1//1) = playerCharacter
and board(pos2//1) = playerCharacter
and board(pos3//1) = playerCharacter
);
% returns true if the player has made a winning move, false otherwise %
logical procedure playerHasWon( string(1) value playerCharacter ) ;
( same( 1, 2, 3, playerCharacter )
or same( 4, 5, 6, playerCharacter )
or same( 7, 8, 9, playerCharacter )
or same( 1, 4, 7, playerCharacter )
or same( 2, 5, 8, playerCharacter )
or same( 3, 6, 9, playerCharacter )
or same( 1, 5, 9, playerCharacter )
or same( 3, 5, 7, playerCharacter )
) ;
% takes a players turn - either automated or user input %
procedure turn ( string(1) value playerCharacter, otherCharacter
; logical value playerIsUser
) ;
begin
if playerIsUser then userMove( playerCharacter )
else begin
write( playerCharacter, " moves..." );
if not makeOrBlockWinningMove( playerCharacter, playerCharacter )
and not makeOrBlockWinningMove( otherCharacter, playerCharacter )
then move( playerCharacter )
end;
showBoard
end turn ;
% asks a question and returns true if the user inputs y/Y, %
% false otherwise %
logical procedure yes( string(32) value question ) ;
begin
string(1) answer;
write( question );
read( answer );
answer = "y" or answer = "Y"
end yes ;
% play the game %
while
begin
string(1) again;
string(32) gameResult;
logical oIsUser, xIsUser;
oIsUser := yes( "Do you want to play O? " );
xIsUser := yes( "Do you want to play X? " );
gameResult := "it's a draw";
initBoard;
showBoard;
while not gameOver and not playerHasWon( "O" ) and not playerHasWon( "X" ) do begin
turn( "O", "X", oIsUser );
if playerHasWon( "O" ) then gameResult := "O wins"
else if not gameOver then begin
turn( "X", "O", xIsUser );
if playerHasWon( "X" ) then gameResult := "X wins"
end
end ;
write( gameResult );
yes( "Play again? " )
end
do begin end
end.
{{out}}
Do you want to play O? y
Do you want to play X? n
1|2|3
-+-+-
4|5|6
-+-+-
7|8|9
Please enter the move for O 5
1|2|3
-+-+-
4|O|6
-+-+-
7|8|9
X moves...
X|2|3
-+-+-
4|O|6
-+-+-
7|8|9
...etc...
Please enter the move for O 8
X|2|O
-+-+-
O|O|X
-+-+-
X|O|9
X moves...
X|X|O
-+-+-
O|O|X
-+-+-
X|O|9
Please enter the move for O 9
X|X|O
-+-+-
O|O|X
-+-+-
X|O|O
it's a draw
Play again? n
AppleScript
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
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.
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
AWK
# syntax: GAWK -f TIC-TAC-TOE.AWK
BEGIN {
move[12] = "3 7 4 6 8"; move[13] = "2 8 6 4 7"; move[14] = "7 3 2 8 6"
move[16] = "8 2 3 7 4"; move[17] = "4 6 8 2 3"; move[18] = "6 4 7 3 2"
move[19] = "8 2 3 7 4"; move[23] = "1 9 6 4 8"; move[24] = "1 9 3 7 8"
move[25] = "8 3 7 4 0"; move[26] = "3 7 1 9 8"; move[27] = "6 4 1 9 8"
move[28] = "1 9 7 3 4"; move[29] = "4 6 3 7 8"; move[35] = "7 4 6 8 2"
move[45] = "6 7 3 2 0"; move[56] = "4 7 3 2 8"; move[57] = "3 2 8 4 6"
move[58] = "2 3 7 4 6"; move[59] = "3 2 8 4 6"
split("7 4 1 8 5 2 9 6 3",rotate)
n = split("253 280 457 254 257 350 452 453 570 590",special)
i = 0
while (i < 9) { s[++i] = " " }
print("")
print("You move first, use the keypad:")
board = "\n7 * 8 * 9\n*********\n4 * 5 * 6\n*********\n1 * 2 * 3\n\n? "
printf(board)
}
state < 7 {
x = $0
if (s[x] != " ") {
printf("? ")
next
}
s[x] = "X"
++state
print("")
if (state > 1) {
for (i=0; i<r; ++i) { x = rotate[x] }
}
}
state == 1 {
for (r=0; x>2 && x!=5; ++r) { x = rotate[x] }
k = x
if (x == 5) { d = 1 } else { d = 5 }
}
state == 2 {
c = 5.5 * (k + x) - 4.5 * abs(k - x)
split(move[c],t)
d = t[1]
e = t[2]
f = t[3]
g = t[4]
h = t[5]
}
state == 3 {
k = x / 2.
c = c * 10
d = f
if (abs(c-350) == 100) {
if (x != 9) { d = 10 - x }
if (int(k) == k) { g = f }
h = 10 - g
if (x+0 == e+0) {
h = g
g = 9
}
}
else if (x+0 != e+0) {
d = e
state = 6
}
}
state == 4 {
if (x+0 == g+0) {
d = h
}
else {
d = g
state = 6
}
x = 6
for (i=1; i<=n; ++i) {
b = special[i]
if (b == 254) { x = 4 }
if (k+0 == abs(b-c-k)) { state = x }
}
}
state < 7 {
if (state != 5) {
for (i=0; i<4-r; ++i) { d = rotate[d] }
s[d] = "O"
}
for (b=7; b>0; b-=5) {
printf("%s * %s * %s\n",s[b++],s[b++],s[b])
if (b > 3) { print("*********") }
}
print("")
}
state < 5 {
printf("? ")
}
state == 5 {
printf("tie game")
state = 7
}
state == 6 {
printf("you lost")
state = 7
}
state == 7 {
printf(", play again? ")
++state
next
}
state == 8 {
if ($1 !~ /^[yY]$/) { exit(0) }
i = 0
while (i < 9) { s[++i] = " " }
printf(board)
state = 0
}
function abs(x) { if (x >= 0) { return x } else { return -x } }
Bash
Computer is X. Computer randomly goes first. Computer plays a good game, but not a perfect game. It will win when it can and draw when it can not.
It performs a depth-first scan of all following moves. It ignores dumb actions like not winning when either player can.
For each possible move it records if it will win, lose, or something else (like win and draw depending on opponent's move).
If there is a choice of best moves, it picks one at random.
I have not used simple bash code to: #keep it under 100 lines; #to demonstrate usefulness of bash integers; #show-off ANSI ESC sequences; #implement recursion in bash; #demonstrate conditional and alternate execution using && and || with { ...; }; #show that you don't always need to use $ to refer to integer variables; #encourage use of [[ ]] instead of [ ] for boolean expressions; #provide examples of pattern matching; and #encourage use of bash for more interesting tasks.
#!/bin/bash
declare -a B=( e e e e e e e e e ) # Board
function show(){ # show B - underline first 2 rows; highlight position; number empty positoins
local -i p POS=${1:-9}; local UL BOLD="\e[1m" GREEN="\e[32m" DIM="\e[2m" OFF="\e[m" ULC="\e[4m"
for p in 0 1 2 3 4 5 6 7 8; do
[[ p%3 -eq 0 ]] && printf " " # indent boards
UL=""; [[ p/3 -lt 2 ]] && UL=$ULC # underline first 2 rows
[[ p -eq POS ]] && printf "$BOLD$GREEN" # bold and colour for this position
[[ ${B[p]} = e ]] && printf "$UL$DIM%d$OFF" $p || printf "$UL%s$OFF" ${B[p]} # num or UL
{ [[ p%3 -lt 2 ]] && printf "$UL | $OFF"; } || printf "\n" # underline vertical bars or NL
done
};
function win(){ # win 'X' 3 return true if X wins after move in position 3
local ME=$1; local -i p=$2
[[ ${B[p/3*3]} = $ME && ${B[p/3*3+1]} = $ME && ${B[p/3*3+2]} = $ME ]] && return 0 # row
[[ ${B[p]} = $ME && ${B[(p+3)%9]} = $ME && ${B[(p+6)%9]} = $ME ]] && return 0 # col
[[ ${B[4]} != $ME ]] && return 1 # don't test diags
[[ p%4 -eq 0 && ${B[0]} = $ME && ${B[8]} = $ME ]] && return 0 # TL - BR diag
[[ p%4 -eq 2 || p -eq 4 ]] && [[ ${B[2]} = $ME && ${B[6]} = $ME ]] # TR - BL diag
};
function bestMove(){ # return best move or 9 if none possible
local ME=$1 OP=$2; local -i o s p
local -ia S=( -9 -9 -9 -9 -9 -9 -9 -9 -9 ) # score board
local -a SB # save board
[[ ${B[*]//[!e]} = "" ]] && return 9 # game over
SB=( ${B[*]} ) # save Board
for p in 0 1 2 3 4 5 6 7 8; do # for each board position
[[ ${B[p]} != e ]] && continue # skip occupied positions
B[p]=$ME # occupy position
win $ME $p && { S[p]=2; B=( ${SB[*]} ); return $p; } # ME wins so this is best move
bestMove $OP $ME; o=$? # what will opponent do
[[ o -le 8 ]] && { B[o]=$OP; win $OP $o; s=$?; } # opponent can make a legal move
S[p]=${s:-1} # save result of opponent move
B=( ${SB[*]} ) # restore board after each trial run
done
local -i best=-1; local -ia MOV=()
for p in 0 1 2 3 4 5 6 7 8; do # find all best moves
[[ S[p] -lt 0 ]] && continue # dont bother with occupied positions
[[ S[p] -eq S[best] ]] && { MOV+=(p); best=p; } # add this move to current list
[[ S[p] -gt S[best] ]] && { MOV=(p); best=p; } # a better move so scrap list and start again
done
return ${MOV[ RANDOM%${#MOV[*]} ]} # pick one at random
};
function getMove(){ # getMove from opponent
[[ $ME = X ]] && { bestMove $ME $OP; return $?; } # pick X move automatically
read -p "O move: " -n 1; printf "\n"; return $REPLY # get opponents move
};
function turn(){ # turn starts or continues a game. It is ME's turn
local -i p; local ME=$1 OP=$2
getMove; p=$?; [[ p -gt 8 ]] && { printf "Draw!\n"; show; return 1; } # no move so a draw
B[p]=$ME; printf "%s moves %d\n" $ME $p # mark board
win $ME $p && { printf "%s wins!\n" $ME; show $p; [[ $ME = X ]] && return 2; return 0; }
[[ ${B[*]//[!e]} = "" ]] && { printf "Draw!\n"; show; return 1; } # no move so a draw
show $p; turn $OP $ME # opponent moves
};
printf "Bic Bash Bow\n"
show; [[ RANDOM%2 -eq 0 ]] && { turn O X; exit $?; } || turn X O
{{out}} (nice ANSI formatting is not shown)
Bic Bash Bow
0 | 1 | 2
3 | 4 | 5
6 | 7 | 8
X moves 1
0 | X | 2
3 | 4 | 5
6 | 7 | 8
O move: 5
O moves 5
0 | X | 2
3 | 4 | O
6 | 7 | 8
X moves 2
0 | X | X
3 | 4 | O
6 | 7 | 8
O move: 0
O moves 0
O | X | X
3 | 4 | O
6 | 7 | 8
X moves 4
O | X | X
3 | X | O
6 | 7 | 8
O move: 6
O moves 6
O | X | X
3 | X | O
O | 7 | 8
X moves 7
X wins!
O | X | X
3 | X | O
O | X | 8
BASIC
Microsoft Small Basic
This game has a simple AI.
place1 = 1
place2 = 2
place3 = 3
place4 = 4
place5 = 5
place6 = 6
place7 = 7
place8 = 8
place9 = 9
symbol1 = "X"
symbol2 = "O"
reset:
TextWindow.Clear()
TextWindow.Write(place1 + " ")
TextWindow.Write(place2 + " ")
TextWindow.WriteLine(place3 + " ")
TextWindow.Write(place4 + " ")
TextWindow.Write(place5 + " ")
TextWindow.WriteLine(place6 + " ")
TextWindow.Write(place7 + " ")
TextWindow.Write(place8 + " ")
TextWindow.WriteLine(place9 + " ")
TextWindow.WriteLine("Where would you like to go to (choose a number from 1 to 9 and press enter)?")
n = TextWindow.Read()
If n = 1 then
If place1 = symbol1 or place1 = symbol2 then
Goto ai
Else
place1 = symbol1
EndIf
ElseIf n = 2 then
If place2 = symbol1 or place2 = symbol2 then
Goto ai
Else
place2 = symbol1
EndIf
ElseIf n = 3 then
If place3 = symbol1 or place3 = symbol2 then
Goto ai
Else
place3 = symbol1
EndIf
ElseIf n = 4 then
If place4 = symbol1 or place4 = symbol2 then
Goto ai
Else
place4 = symbol1
EndIf
ElseIf n = 5 then
If place5 = symbol1 or place5 = symbol2 then
Goto ai
Else
place5 = symbol1
EndIf
ElseIf n = 6 then
If place6 = symbol1 or place6 = symbol2 then
Goto ai
Else
place6 = symbol1
EndIf
ElseIf n = 7 then
If place8 = symbol1 or place7 = symbol2 then
Goto ai
Else
place7 = symbol1
EndIf
ElseIf n = 8 then
If place8 = symbol1 or place8 = symbol2 then
Goto ai
Else
place8 = symbol1
EndIf
ElseIf n = 9 then
If place9 = symbol1 or place9 = symbol2 then
Goto ai
Else
place9 = symbol1
EndIf
EndIf
Goto ai
ai:
n = Math.GetRandomNumber(9)
If n = 1 then
If place1 = symbol1 or place1 = symbol2 then
Goto ai
Else
place1 = symbol2
EndIf
ElseIf n = 2 then
If place2 = symbol1 or place2 = symbol2 then
Goto ai
Else
place2 = symbol2
EndIf
ElseIf n = 3 then
If place3 = symbol1 or place3 = symbol2 then
Goto ai
Else
place3 = symbol2
EndIf
ElseIf n = 4 then
If place4 = symbol1 or place4 = symbol2 then
Goto ai
Else
place4 = symbol2
EndIf
ElseIf n = 5 then
If place5 = symbol1 or place5 = symbol2 then
Goto ai
Else
place5 = symbol2
EndIf
ElseIf n = 6 then
If place6 = symbol1 or place6 = symbol2 then
Goto ai
Else
place6 = symbol2
EndIf
ElseIf n = 7 then
If place7 = symbol1 or place7 = symbol2 then
Goto ai
Else
place7 = symbol2
EndIf
ElseIf n = 8 then
If place8 = symbol1 or place8 = symbol2 then
Goto ai
Else
place8 = symbol2
EndIf
ElseIf n = 9 then
If place9 = symbol1 or place9 = symbol2 then
Goto ai
Else
place9 = symbol2
EndIf
EndIf
If place1 = symbol1 and place2 = symbol1 and place3 = symbol1 or place4 = symbol1 and place5 = symbol1 and place6 = symbol1 or place7 = symbol1 and place8 = symbol1 and place9 = symbol1 or place1 = symbol1 and place4 = symbol1 and place7 = symbol1 or place2 = symbol1 and place5 = symbol1 and place8 = symbol1 or place3 = symbol1 and place6 = symbol1 and place9 = symbol1 or place1 = symbol1 and place5 = symbol1 and place9 = symbol1 or place3 = symbol1 and place5 = symbol1 and place7 = symbol1 then
TextWindow.WriteLine("Player 1 (" + symbol1 + ") wins!")
ElseIf place1 = symbol2 and place2 = symbol2 and place3 = symbol2 or place4 = symbol2 and place5 = symbol2 and place6 = symbol2 or place7 = symbol2 and place8 = symbol2 and place9 = symbol2 or place1 = symbol2 and place4 = symbol2 and place7 = symbol2 or place2 = symbol2 and place5 = symbol2 and place8 = symbol2 or place3 = symbol2 and place6 = symbol2 and place9 = symbol2 or place1 = symbol2 and place5 = symbol2 and place8 = symbol2 or place3 = symbol2 and place5 = symbol2 and place7 = symbol2 then
TextWindow.WriteLine("Player 2 (" + symbol2 + ") wins!")
Else
Goto reset
EndIf
ZX Spectrum Basic
Batch File
This is just a game between two human players.
@echo off
setlocal enabledelayedexpansion
:newgame
set a1=1
set a2=2
set a3=3
set a4=4
set a5=5
set a6=6
set a7=7
set a8=8
set a9=9
set ll=X
set /a zz=0
:display1
cls
echo Player: %ll%
echo %a7%_%a8%_%a9%
echo %a4%_%a5%_%a6%
echo %a1%_%a2%_%a3%
set /p myt=Where would you like to go (choose a number from 1-9 and press enter)?
if !a%myt%! equ %myt% (
set a%myt%=%ll%
goto check
)
goto display1
:check
set /a zz=%zz%+1
if %zz% geq 9 goto newgame
if %a7%+%a8%+%a9% equ %ll%+%ll%+%ll% goto win
if %a4%+%a5%+%a6% equ %ll%+%ll%+%ll% goto win
if %a1%+%a2%+%a3% equ %ll%+%ll%+%ll% goto win
if %a7%+%a5%+%a3% equ %ll%+%ll%+%ll% goto win
if %a1%+%a5%+%a9% equ %ll%+%ll%+%ll% goto win
if %a7%+%a4%+%a1% equ %ll%+%ll%+%ll% goto win
if %a8%+%a5%+%a2% equ %ll%+%ll%+%ll% goto win
if %a9%+%a6%+%a3% equ %ll%+%ll%+%ll% goto win
goto %ll%
:X
set ll=O
goto display1
:O
set ll=X
goto display1
:win
echo %ll% wins!
pause
goto newgame
Advanced
This code makes a version of Tic Tac Toe with more features:
@ECHO OFF
:BEGIN
REM Skill level
set sl=
cls
echo Tic Tac Toe (Q to quit)
echo.
echo.
echo Pick your skill level (press a number)
echo.
echo (1) Children under 6
echo (2) Average Mental Case
echo (3) Oversized Ego
CHOICE /c:123q /n > nul
if errorlevel 4 goto end
if errorlevel 3 set sl=3
if errorlevel 3 goto layout
if errorlevel 2 set sl=2
if errorlevel 2 goto layout
set sl=1
:LAYOUT
REM Player turn ("x" or "o")
set pt=
REM Game winner ("x" or "o")
set gw=
REM No moves
set nm=
REM Set to one blank space after equal sign (check with cursor end)
set t1=
set t2=
set t3=
set t4=
set t5=
set t6=
set t7=
set t8=
set t9=
:UPDATE
cls
echo (S to set skill level) Tic Tac Toe (Q to quit)
echo.
echo You are the X player.
echo Press the number where you want to put an X.
echo.
echo Skill level %sl% 7 8 9
echo 4 5 6
echo 1 2 3
echo.
echo : :
echo %t1% : %t2% : %t3%
echo ....:...:....
echo %t4% : %t5% : %t6%
echo ....:...:....
echo %t7% : %t8% : %t9%
echo : :
if "%gw%"=="x" goto winx2
if "%gw%"=="o" goto wino2
if "%nm%"=="0" goto nomoves
:PLAYER
set pt=x
REM Layout is for keypad. Change CHOICE to "/c:123456789sq /n > nul"
REM for numbers to start at top left (also change user layout above).
CHOICE /c:789456123sq /n > nul
if errorlevel 11 goto end
if errorlevel 10 goto begin
if errorlevel 9 goto 9
if errorlevel 8 goto 8
if errorlevel 7 goto 7
if errorlevel 6 goto 6
if errorlevel 5 goto 5
if errorlevel 4 goto 4
if errorlevel 3 goto 3
if errorlevel 2 goto 2
goto 1
:1
REM Check if "x" or "o" already in square.
if "%t1%"=="x" goto player
if "%t1%"=="o" goto player
set t1=x
goto check
:2
if "%t2%"=="x" goto player
if "%t2%"=="o" goto player
set t2=x
goto check
:3
if "%t3%"=="x" goto player
if "%t3%"=="o" goto player
set t3=x
goto check
:4
if "%t4%"=="x" goto player
if "%t4%"=="o" goto player
set t4=x
goto check
:5
if "%t5%"=="x" goto player
if "%t5%"=="o" goto player
set t5=x
goto check
:6
if "%t6%"=="x" goto player
if "%t6%"=="o" goto player
set t6=x
goto check
:7
if "%t7%"=="x" goto player
if "%t7%"=="o" goto player
set t7=x
goto check
:8
if "%t8%"=="x" goto player
if "%t8%"=="o" goto player
set t8=x
goto check
:9
if "%t9%"=="x" goto player
if "%t9%"=="o" goto player
set t9=x
goto check
:COMPUTER
set pt=o
if "%sl%"=="1" goto skill1
REM (win corner to corner)
if "%t1%"=="o" if "%t3%"=="o" if not "%t2%"=="x" if not "%t2%"=="o" goto c2
if "%t1%"=="o" if "%t9%"=="o" if not "%t5%"=="x" if not "%t5%"=="o" goto c5
if "%t1%"=="o" if "%t7%"=="o" if not "%t4%"=="x" if not "%t4%"=="o" goto c4
if "%t3%"=="o" if "%t7%"=="o" if not "%t5%"=="x" if not "%t5%"=="o" goto c5
if "%t3%"=="o" if "%t9%"=="o" if not "%t6%"=="x" if not "%t6%"=="o" goto c6
if "%t9%"=="o" if "%t7%"=="o" if not "%t8%"=="x" if not "%t8%"=="o" goto c8
REM (win outside middle to outside middle)
if "%t2%"=="o" if "%t8%"=="o" if not "%t5%"=="x" if not "%t5%"=="o" goto c5
if "%t4%"=="o" if "%t6%"=="o" if not "%t5%"=="x" if not "%t5%"=="o" goto c5
REM (win all others)
if "%t1%"=="o" if "%t2%"=="o" if not "%t3%"=="x" if not "%t3%"=="o" goto c3
if "%t1%"=="o" if "%t5%"=="o" if not "%t9%"=="x" if not "%t9%"=="o" goto c9
if "%t1%"=="o" if "%t4%"=="o" if not "%t7%"=="x" if not "%t7%"=="o" goto c7
if "%t2%"=="o" if "%t5%"=="o" if not "%t8%"=="x" if not "%t8%"=="o" goto c8
if "%t3%"=="o" if "%t2%"=="o" if not "%t1%"=="x" if not "%t1%"=="o" goto c1
if "%t3%"=="o" if "%t5%"=="o" if not "%t7%"=="x" if not "%t7%"=="o" goto c7
if "%t3%"=="o" if "%t6%"=="o" if not "%t9%"=="x" if not "%t9%"=="o" goto c9
if "%t4%"=="o" if "%t5%"=="o" if not "%t6%"=="x" if not "%t6%"=="o" goto c6
if "%t6%"=="o" if "%t5%"=="o" if not "%t4%"=="x" if not "%t4%"=="o" goto c4
if "%t7%"=="o" if "%t4%"=="o" if not "%t1%"=="x" if not "%t1%"=="o" goto c1
if "%t7%"=="o" if "%t5%"=="o" if not "%t3%"=="x" if not "%t3%"=="o" goto c3
if "%t7%"=="o" if "%t8%"=="o" if not "%t9%"=="x" if not "%t9%"=="o" goto c9
if "%t8%"=="o" if "%t5%"=="o" if not "%t2%"=="x" if not "%t2%"=="o" goto c2
if "%t9%"=="o" if "%t8%"=="o" if not "%t7%"=="x" if not "%t7%"=="o" goto c7
if "%t9%"=="o" if "%t5%"=="o" if not "%t1%"=="x" if not "%t1%"=="o" goto c1
if "%t9%"=="o" if "%t6%"=="o" if not "%t3%"=="x" if not "%t3%"=="o" goto c3
REM (block general attempts) -----------------------------------------------
if "%t1%"=="x" if "%t2%"=="x" if not "%t3%"=="x" if not "%t3%"=="o" goto c3
if "%t1%"=="x" if "%t5%"=="x" if not "%t9%"=="x" if not "%t9%"=="o" goto c9
if "%t1%"=="x" if "%t4%"=="x" if not "%t7%"=="x" if not "%t7%"=="o" goto c7
if "%t2%"=="x" if "%t5%"=="x" if not "%t8%"=="x" if not "%t8%"=="o" goto c8
if "%t3%"=="x" if "%t2%"=="x" if not "%t1%"=="x" if not "%t1%"=="o" goto c1
if "%t3%"=="x" if "%t5%"=="x" if not "%t7%"=="x" if not "%t7%"=="o" goto c7
if "%t3%"=="x" if "%t6%"=="x" if not "%t9%"=="x" if not "%t9%"=="o" goto c9
if "%t4%"=="x" if "%t5%"=="x" if not "%t6%"=="x" if not "%t6%"=="o" goto c6
if "%t6%"=="x" if "%t5%"=="x" if not "%t4%"=="x" if not "%t4%"=="o" goto c4
if "%t7%"=="x" if "%t4%"=="x" if not "%t1%"=="x" if not "%t1%"=="o" goto c1
if "%t7%"=="x" if "%t5%"=="x" if not "%t3%"=="x" if not "%t3%"=="o" goto c3
if "%t7%"=="x" if "%t8%"=="x" if not "%t9%"=="x" if not "%t9%"=="o" goto c9
if "%t8%"=="x" if "%t5%"=="x" if not "%t2%"=="x" if not "%t2%"=="o" goto c2
if "%t9%"=="x" if "%t8%"=="x" if not "%t7%"=="x" if not "%t7%"=="o" goto c7
if "%t9%"=="x" if "%t5%"=="x" if not "%t1%"=="x" if not "%t1%"=="o" goto c1
if "%t9%"=="x" if "%t6%"=="x" if not "%t3%"=="x" if not "%t3%"=="o" goto c3
REM (block obvious corner to corner)
if "%t1%"=="x" if "%t3%"=="x" if not "%t2%"=="x" if not "%t2%"=="o" goto c2
if "%t1%"=="x" if "%t9%"=="x" if not "%t5%"=="x" if not "%t5%"=="o" goto c5
if "%t1%"=="x" if "%t7%"=="x" if not "%t4%"=="x" if not "%t4%"=="o" goto c4
if "%t3%"=="x" if "%t7%"=="x" if not "%t5%"=="x" if not "%t5%"=="o" goto c5
if "%t3%"=="x" if "%t9%"=="x" if not "%t6%"=="x" if not "%t6%"=="o" goto c6
if "%t9%"=="x" if "%t7%"=="x" if not "%t8%"=="x" if not "%t8%"=="o" goto c8
if "%sl%"=="2" goto skill2
REM (block sneaky corner to corner 2-4, 2-6, etc.)
if "%t2%"=="x" if "%t4%"=="x" if not "%t1%"=="x" if not "%t1%"=="o" goto c1
if "%t2%"=="x" if "%t6%"=="x" if not "%t3%"=="x" if not "%t3%"=="o" goto c3
if "%t8%"=="x" if "%t4%"=="x" if not "%t7%"=="x" if not "%t7%"=="o" goto c7
if "%t8%"=="x" if "%t6%"=="x" if not "%t9%"=="x" if not "%t9%"=="o" goto c9
REM (block offset corner trap 1-8, 1-6, etc.)
if "%t1%"=="x" if "%t6%"=="x" if not "%t8%"=="x" if not "%t8%"=="o" goto c8
if "%t1%"=="x" if "%t8%"=="x" if not "%t6%"=="x" if not "%t6%"=="o" goto c6
if "%t3%"=="x" if "%t8%"=="x" if not "%t4%"=="x" if not "%t4%"=="o" goto c4
if "%t3%"=="x" if "%t4%"=="x" if not "%t8%"=="x" if not "%t8%"=="o" goto c8
if "%t9%"=="x" if "%t4%"=="x" if not "%t2%"=="x" if not "%t2%"=="o" goto c2
if "%t9%"=="x" if "%t2%"=="x" if not "%t4%"=="x" if not "%t4%"=="o" goto c4
if "%t7%"=="x" if "%t2%"=="x" if not "%t6%"=="x" if not "%t6%"=="o" goto c6
if "%t7%"=="x" if "%t6%"=="x" if not "%t2%"=="x" if not "%t2%"=="o" goto c2
:SKILL2
REM (block outside middle to outside middle)
if "%t2%"=="x" if "%t8%"=="x" if not "%t5%"=="x" if not "%t5%"=="o" goto c5
if "%t4%"=="x" if "%t6%"=="x" if not "%t5%"=="x" if not "%t5%"=="o" goto c5
REM (block 3 corner trap)
if "%t1%"=="x" if "%t9%"=="x" if not "%t2%"=="x" if not "%t2%"=="o" goto c2
if "%t3%"=="x" if "%t7%"=="x" if not "%t2%"=="x" if not "%t2%"=="o" goto c2
if "%t1%"=="x" if "%t9%"=="x" if not "%t4%"=="x" if not "%t4%"=="o" goto c4
if "%t3%"=="x" if "%t7%"=="x" if not "%t4%"=="x" if not "%t4%"=="o" goto c4
if "%t1%"=="x" if "%t9%"=="x" if not "%t6%"=="x" if not "%t6%"=="o" goto c6
if "%t3%"=="x" if "%t7%"=="x" if not "%t6%"=="x" if not "%t6%"=="o" goto c6
if "%t1%"=="x" if "%t9%"=="x" if not "%t8%"=="x" if not "%t8%"=="o" goto c8
if "%t3%"=="x" if "%t7%"=="x" if not "%t8%"=="x" if not "%t8%"=="o" goto c8
:SKILL1
REM (just take a turn)
if not "%t5%"=="x" if not "%t5%"=="o" goto c5
if not "%t1%"=="x" if not "%t1%"=="o" goto c1
if not "%t3%"=="x" if not "%t3%"=="o" goto c3
if not "%t7%"=="x" if not "%t7%"=="o" goto c7
if not "%t9%"=="x" if not "%t9%"=="o" goto c9
if not "%t2%"=="x" if not "%t2%"=="o" goto c2
if not "%t4%"=="x" if not "%t4%"=="o" goto c4
if not "%t6%"=="x" if not "%t6%"=="o" goto c6
if not "%t8%"=="x" if not "%t8%"=="o" goto c8
set nm=0
goto update
:C1
set t1=o
goto check
:C2
set t2=o
goto check
:C3
set t3=o
goto check
:C4
set t4=o
goto check
:C5
set t5=o
goto check
:C6
set t6=o
goto check
:C7
set t7=o
goto check
:C8
set t8=o
goto check
:C9
set t9=o
goto check
:CHECK
if "%t1%"=="x" if "%t2%"=="x" if "%t3%"=="x" goto winx
if "%t4%"=="x" if "%t5%"=="x" if "%t6%"=="x" goto winx
if "%t7%"=="x" if "%t8%"=="x" if "%t9%"=="x" goto winx
if "%t1%"=="x" if "%t4%"=="x" if "%t7%"=="x" goto winx
if "%t2%"=="x" if "%t5%"=="x" if "%t8%"=="x" goto winx
if "%t3%"=="x" if "%t6%"=="x" if "%t9%"=="x" goto winx
if "%t1%"=="x" if "%t5%"=="x" if "%t9%"=="x" goto winx
if "%t3%"=="x" if "%t5%"=="x" if "%t7%"=="x" goto winx
if "%t1%"=="o" if "%t2%"=="o" if "%t3%"=="o" goto wino
if "%t4%"=="o" if "%t5%"=="o" if "%t6%"=="o" goto wino
if "%t7%"=="o" if "%t8%"=="o" if "%t9%"=="o" goto wino
if "%t1%"=="o" if "%t4%"=="o" if "%t7%"=="o" goto wino
if "%t2%"=="o" if "%t5%"=="o" if "%t8%"=="o" goto wino
if "%t3%"=="o" if "%t6%"=="o" if "%t9%"=="o" goto wino
if "%t1%"=="o" if "%t5%"=="o" if "%t9%"=="o" goto wino
if "%t3%"=="o" if "%t5%"=="o" if "%t7%"=="o" goto wino
if "%pt%"=="x" goto computer
if "%pt%"=="o" goto update
:WINX
set gw=x
goto update
:WINX2
echo You win!
echo Play again (Y,N)?
CHOICE /c:ynsq /n > nul
if errorlevel 4 goto end
if errorlevel 3 goto begin
if errorlevel 2 goto end
goto layout
:WINO
set gw=o
goto update
:WINO2
echo Sorry, You lose.
echo Play again (Y,N)?
CHOICE /c:ynsq /n > nul
if errorlevel 4 goto end
if errorlevel 3 goto begin
if errorlevel 2 goto end
goto layout
:NOMOVES
echo There are no more moves left!
echo Play again (Y,N)?
CHOICE /c:ynsq /n > nul
if errorlevel 4 goto end
if errorlevel 3 goto begin
if errorlevel 2 goto end
goto layout
:END
cls
echo Tic Tac Toe
echo.
REM Clear all variables (no spaces after equal sign).
set gw=
set nm=
set sl=
set pt=
set t1=
set t2=
set t3=
set t4=
set t5=
set t6=
set t7=
set t8=
set t9=
Befunge
Requires an intepreter with working support for numeric input, which unfortunately excludes most online implementations.
Plays reasonably well, but not perfectly, so can be beaten.
v123456789 --- >9 >48*,:55+\-0g,1v
>9>066+0p076+0p^ ^,," |"_v#%3:- <
:,,0537051v>:#,_$#^5#,5#+<>:#v_55+
74 1098709<^+55"---+---+---"0<v520
69 04560123 >:!#v_0\1v>$2-:6%v>803
6 +0g\66++0p^ $_>#% v#9:-1_ 6/5
5 vv5!/*88\%*28 ::g0_^>9/#v_ "I",
,,5v>5++0p82*/3-:*+\:^v,_@ >"uoY",
0+5<v0+66_v#!%2:_55v >:^:" win!"\
1-^ g >$>0" :evom ruoY">:#,_$v>p
\*8+ 65_^#!/*88g0** `0\!`9:::<&<^0
v >:!67+0g:!56+0g *+*+0" :evom "
>"yM">:#,_$ :. 1234+++, 789*+ \0^<
"a s't"98:*+>:#,_$@>365*+"ward"48*
{{out}}
1 | 2 | 3
---+---+---
4 | 5 | 6
---+---+---
7 | 8 | 9
Your move: 1
X | 2 | 3
---+---+---
4 | 5 | 6
---+---+---
7 | 8 | 9
My move: 5
X | 2 | 3
---+---+---
4 | O | 6
---+---+---
7 | 8 | 9
Your move: 2
X | X | 3
---+---+---
4 | O | 6
---+---+---
7 | 8 | 9
My move: 3
X | X | O
---+---+---
4 | O | 6
---+---+---
7 | 8 | 9
Your move:
C
Opening alternates between human and computer. Computer never loses.
#include <stdio.h>
#include <stdlib.h>
int b[3][3]; /* board. 0: blank; -1: computer; 1: human */
int check_winner()
{
int i;
for (i = 0; i < 3; i++) {
if (b[i][0] && b[i][1] == b[i][0] && b[i][2] == b[i][0])
return b[i][0];
if (b[0][i] && b[1][i] == b[0][i] && b[2][i] == b[0][i])
return b[0][i];
}
if (!b[1][1]) return 0;
if (b[1][1] == b[0][0] && b[2][2] == b[0][0]) return b[0][0];
if (b[1][1] == b[2][0] && b[0][2] == b[1][1]) return b[1][1];
return 0;
}
void showboard()
{
const char *t = "X O";
int i, j;
for (i = 0; i < 3; i++, putchar('\n'))
for (j = 0; j < 3; j++)
printf("%c ", t[ b[i][j] + 1 ]);
printf("-----\n");
}
#define for_ij for (i = 0; i < 3; i++) for (j = 0; j < 3; j++)
int best_i, best_j;
int test_move(int val, int depth)
{
int i, j, score;
int best = -1, changed = 0;
if ((score = check_winner())) return (score == val) ? 1 : -1;
for_ij {
if (b[i][j]) continue;
changed = b[i][j] = val;
score = -test_move(-val, depth + 1);
b[i][j] = 0;
if (score <= best) continue;
if (!depth) {
best_i = i;
best_j = j;
}
best = score;
}
return changed ? best : 0;
}
const char* game(int user)
{
int i, j, k, move, win = 0;
for_ij b[i][j] = 0;
printf("Board postions are numbered so:\n1 2 3\n4 5 6\n7 8 9\n");
printf("You have O, I have X.\n\n");
for (k = 0; k < 9; k++, user = !user) {
while(user) {
printf("your move: ");
if (!scanf("%d", &move)) {
scanf("%*s");
continue;
}
if (--move < 0 || move >= 9) continue;
if (b[i = move / 3][j = move % 3]) continue;
b[i][j] = 1;
break;
}
if (!user) {
if (!k) { /* randomize if computer opens, less boring */
best_i = rand() % 3;
best_j = rand() % 3;
} else
test_move(-1, 0);
b[best_i][best_j] = -1;
printf("My move: %d\n", best_i * 3 + best_j + 1);
}
showboard();
if ((win = check_winner()))
return win == 1 ? "You win.\n\n": "I win.\n\n";
}
return "A draw.\n\n";
}
int main()
{
int first = 0;
while (1) printf("%s", game(first = !first));
return 0;
}
C++
#include <windows.h>
#include <iostream>
#include <string>
//--------------------------------------------------------------------------------------------------
using namespace std;
//--------------------------------------------------------------------------------------------------
enum players { Computer, Human, Draw, None };
const int iWin[8][3] = { { 0, 1, 2 }, { 3, 4, 5 }, { 6, 7, 8 }, { 0, 3, 6 }, { 1, 4, 7 }, { 2, 5, 8 }, { 0, 4, 8 }, { 2, 4, 6 } };
//--------------------------------------------------------------------------------------------------
class ttt
{
public:
ttt() { _p = rand() % 2; reset(); }
void play()
{
int res = Draw;
while( true )
{
drawGrid();
while( true )
{
if( _p ) getHumanMove();
else getComputerMove();
drawGrid();
res = checkVictory();
if( res != None ) break;
++_p %= 2;
}
if( res == Human ) cout << "CONGRATULATIONS HUMAN --- You won!";
else if( res == Computer ) cout << "NOT SO MUCH A SURPRISE --- I won!";
else cout << "It's a draw!";
cout << endl << endl;
string r;
cout << "Play again( Y / N )? "; cin >> r;
if( r != "Y" && r != "y" ) return;
++_p %= 2;
reset();
}
}
private:
void reset()
{
for( int x = 0; x < 9; x++ )
_field[x] = None;
}
void drawGrid()
{
system( "cls" );
COORD c = { 0, 2 };
SetConsoleCursorPosition( GetStdHandle( STD_OUTPUT_HANDLE ), c );
cout << " 1 | 2 | 3 " << endl;
cout << "---+---+---" << endl;
cout << " 4 | 5 | 6 " << endl;
cout << "---+---+---" << endl;
cout << " 7 | 8 | 9 " << endl << endl << endl;
int f = 0;
for( int y = 0; y < 5; y += 2 )
for( int x = 1; x < 11; x += 4 )
{
if( _field[f] != None )
{
COORD c = { x, 2 + y };
SetConsoleCursorPosition( GetStdHandle( STD_OUTPUT_HANDLE ), c );
string o = _field[f] == Computer ? "X" : "O";
cout << o;
}
f++;
}
c.Y = 9;
SetConsoleCursorPosition( GetStdHandle( STD_OUTPUT_HANDLE ), c );
}
int checkVictory()
{
for( int i = 0; i < 8; i++ )
{
if( _field[iWin[i][0]] != None &&
_field[iWin[i][0]] == _field[iWin[i][1]] && _field[iWin[i][1]] == _field[iWin[i][2]] )
{
return _field[iWin[i][0]];
}
}
int i = 0;
for( int f = 0; f < 9; f++ )
{
if( _field[f] != None )
i++;
}
if( i == 9 ) return Draw;
return None;
}
void getHumanMove()
{
int m;
cout << "Enter your move ( 1 - 9 ) ";
while( true )
{
m = 0;
do
{ cin >> m; }
while( m < 1 && m > 9 );
if( _field[m - 1] != None )
cout << "Invalid move. Try again!" << endl;
else break;
}
_field[m - 1] = Human;
}
void getComputerMove()
{
int move = 0;
do{ move = rand() % 9; }
while( _field[move] != None );
for( int i = 0; i < 8; i++ )
{
int try1 = iWin[i][0], try2 = iWin[i][1], try3 = iWin[i][2];
if( _field[try1] != None && _field[try1] == _field[try2] && _field[try3] == None )
{
move = try3;
if( _field[try1] == Computer ) break;
}
if( _field[try1] != None && _field[try1] == _field[try3] && _field[try2] == None )
{
move = try2;
if( _field[try1] == Computer ) break;
}
if( _field[try2] != None && _field[try2] == _field[try3] && _field[try1] == None )
{
move = try1;
if( _field[try2] == Computer ) break;
}
}
_field[move] = Computer;
}
int _p;
int _field[9];
};
//--------------------------------------------------------------------------------------------------
int main( int argc, char* argv[] )
{
srand( GetTickCount() );
ttt tic;
tic.play();
return 0;
}
//--------------------------------------------------------------------------------------------------
{{out}} Computer plays 'X' and human plays 'O'
1 | 2 | X
---+---+---
X | 5 | 6
---+---+---
7 | O | 9
Enter your move ( 1 - 9 )
C#
This implementation is purposely wordy because Tic-Tac-Toe is often a starting level program.
It tries to show a number of C# code features while still keeping each function small and understandable.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace RosettaTicTacToe
{
class Program
{
/*
### ==========================================================
*Pieces (players and board)
*
### ==========================================================
*/
static string[][] Players = new string[][] {
new string[] { "COMPUTER", "X" }, // computer player
new string[] { "HUMAN", "O" } // human player
};
const int Unplayed = -1;
const int Computer = 0;
const int Human = 1;
// GameBoard holds index into Players[] (0 or 1) or Unplayed (-1) if location not yet taken
static int[] GameBoard = new int[9];
static int[] corners = new int[] { 0, 2, 6, 8 };
static int[][] wins = new int[][] {
new int[] { 0, 1, 2 }, new int[] { 3, 4, 5 }, new int[] { 6, 7, 8 },
new int[] { 0, 3, 6 }, new int[] { 1, 4, 7 }, new int[] { 2, 5, 8 },
new int[] { 0, 4, 8 }, new int[] { 2, 4, 6 } };
/*
### ==========================================================
*Main Game Loop (this is what runs/controls the game)
*
### ==========================================================
*/
static void Main(string[] args)
{
while (true)
{
Console.Clear();
Console.WriteLine("Welcome to Rosetta Code Tic-Tac-Toe for C#.");
initializeGameBoard();
displayGameBoard();
int currentPlayer = rnd.Next(0, 2); // current player represented by Players[] index of 0 or 1
Console.WriteLine("The first move goes to {0} who is playing {1}s.\n", playerName(currentPlayer), playerToken(currentPlayer));
while (true)
{
int thisMove = getMoveFor(currentPlayer);
if (thisMove == Unplayed)
{
Console.WriteLine("{0}, you've quit the game ... am I that good?", playerName(currentPlayer));
break;
}
playMove(thisMove, currentPlayer);
displayGameBoard();
if (isGameWon())
{
Console.WriteLine("{0} has won the game!", playerName(currentPlayer));
break;
}
else if (isGameTied())
{
Console.WriteLine("Cat game ... we have a tie.");
break;
}
currentPlayer = getNextPlayer(currentPlayer);
}
if (!playAgain())
return;
}
}
/*
### ==========================================================
*Move Logic
*
### ==========================================================
*/
static int getMoveFor(int player)
{
if (player == Human)
return getManualMove(player);
else
{
//int selectedMove = getManualMove(player);
//int selectedMove = getRandomMove(player);
int selectedMove = getSemiRandomMove(player);
//int selectedMove = getBestMove(player);
Console.WriteLine("{0} selects position {1}.", playerName(player), selectedMove + 1);
return selectedMove;
}
}
static int getManualMove(int player)
{
while (true)
{
Console.Write("{0}, enter you move (number): ", playerName(player));
ConsoleKeyInfo keyInfo = Console.ReadKey();
Console.WriteLine(); // keep the display pretty
if (keyInfo.Key == ConsoleKey.Escape)
return Unplayed;
if (keyInfo.Key >= ConsoleKey.D1 && keyInfo.Key <= ConsoleKey.D9)
{
int move = keyInfo.KeyChar - '1'; // convert to between 0..8, a GameBoard index position.
if (GameBoard[move] == Unplayed)
return move;
else
Console.WriteLine("Spot {0} is already taken, please select again.", move + 1);
}
else
Console.WriteLine("Illegal move, please select again.\n");
}
}
static int getRandomMove(int player)
{
int movesLeft = GameBoard.Count(position => position == Unplayed);
int x = rnd.Next(0, movesLeft);
for (int i = 0; i < GameBoard.Length; i++) // walk board ...
{
if (GameBoard[i] == Unplayed && x < 0) // until we reach the unplayed move.
return i;
x--;
}
return Unplayed;
}
// plays random if no winning move or needed block.
static int getSemiRandomMove(int player)
{
int posToPlay;
if (checkForWinningMove(player, out posToPlay))
return posToPlay;
if (checkForBlockingMove(player, out posToPlay))
return posToPlay;
return getRandomMove(player);
}
// purposely not implemented (this is the thinking part).
static int getBestMove(int player)
{
return -1;
}
static bool checkForWinningMove(int player, out int posToPlay)
{
posToPlay = Unplayed;
foreach (var line in wins)
if (twoOfThreeMatchPlayer(player, line, out posToPlay))
return true;
return false;
}
static bool checkForBlockingMove(int player, out int posToPlay)
{
posToPlay = Unplayed;
foreach (var line in wins)
if (twoOfThreeMatchPlayer(getNextPlayer(player), line, out posToPlay))
return true;
return false;
}
static bool twoOfThreeMatchPlayer(int player, int[] line, out int posToPlay)
{
int cnt = 0;
posToPlay = int.MinValue;
foreach (int pos in line)
{
if (GameBoard[pos] == player)
cnt++;
else if (GameBoard[pos] == Unplayed)
posToPlay = pos;
}
return cnt == 2 && posToPlay >= 0;
}
static void playMove(int boardPosition, int player)
{
GameBoard[boardPosition] = player;
}
static bool isGameWon()
{
return wins.Any(line => takenBySamePlayer(line[0], line[1], line[2]));
}
static bool takenBySamePlayer(int a, int b, int c)
{
return GameBoard[a] != Unplayed && GameBoard[a] == GameBoard[b] && GameBoard[a] == GameBoard[c];
}
static bool isGameTied()
{
return !GameBoard.Any(spot => spot == Unplayed);
}
/*
### ==========================================================
*Misc Methods
*
### ==========================================================
*/
static Random rnd = new Random();
static void initializeGameBoard()
{
for (int i = 0; i < GameBoard.Length; i++)
GameBoard[i] = Unplayed;
}
static string playerName(int player)
{
return Players[player][0];
}
static string playerToken(int player)
{
return Players[player][1];
}
static int getNextPlayer(int player)
{
return (player + 1) % 2;
}
static void displayGameBoard()
{
Console.WriteLine(" {0} | {1} | {2}", pieceAt(0), pieceAt(1), pieceAt(2));
Console.WriteLine("---|---|---");
Console.WriteLine(" {0} | {1} | {2}", pieceAt(3), pieceAt(4), pieceAt(5));
Console.WriteLine("---|---|---");
Console.WriteLine(" {0} | {1} | {2}", pieceAt(6), pieceAt(7), pieceAt(8));
Console.WriteLine();
}
static string pieceAt(int boardPosition)
{
if (GameBoard[boardPosition] == Unplayed)
return (boardPosition + 1).ToString(); // display 1..9 on board rather than 0..8
return playerToken(GameBoard[boardPosition]);
}
private static bool playAgain()
{
Console.WriteLine("\nDo you want to play again?");
return Console.ReadKey(false).Key == ConsoleKey.Y;
}
}
}
{{out}}
Welcome to Rosetta Code Tic-Tac-Toe for C#.
1 | 2 | 3
---|---|---
4 | 5 | 6
---|---|---
7 | 8 | 9
The first move goes to HUMAN who is playing Os.
HUMAN, enter you move (number): 5
1 | 2 | 3
---|---|---
4 | O | 6
---|---|---
7 | 8 | 9
COMPUTER selects position 7.
1 | 2 | 3
---|---|---
4 | O | 6
---|---|---
X | 8 | 9
HUMAN, enter you move (number): 0
Illegal move, please select again.
HUMAN, enter you move (number): 1
O | 2 | 3
---|---|---
4 | O | 6
---|---|---
X | 8 | 9
COMPUTER selects position 9.
O | 2 | 3
---|---|---
4 | O | 6
---|---|---
X | 8 | X
HUMAN, enter you move (number):
Common Lisp
(defun generate-board ()
(loop repeat 9 collect nil))
(defparameter *straights* '((1 2 3) (4 5 6) (7 8 9) (1 4 7) (2 5 8) (3 6 9) (1 5 9) (3 5 7)))
(defparameter *current-player* 'x)
(defun get-board-elt (n board)
(nth (1- n) board))
(defun legal-p (n board)
(null (get-board-elt n board)))
(defun set-board-elt (n board symbol)
(if (legal-p n board)
(setf (nth (1- n) board) symbol)
(progn (format t "Illegal move. Try again.~&")
(set-board-elt (read) board symbol))))
(defun list-legal-moves (board)
(loop for i from 1 to (length board)
when (legal-p i board)
collect i))
(defun get-random-element (lst)
(nth (random (length lst)) lst))
(defun multi-non-nil-eq (lst)
(and (notany #'null lst)
(notany #'null (mapcar #'(lambda (x) (eq (car lst) x)) lst))
(car lst)))
(defun elements-of-straights (board)
(loop for i in *straights*
collect (loop for j from 0 to 2
collect (get-board-elt (nth j i) board))))
(defun find-winner (board)
(car (remove-if #'null (mapcar #'multi-non-nil-eq (elements-of-straights board)))))
(defun set-player (mark)
(format t "Shall a computer play as ~a? (y/n)~&" mark)
(let ((response (read)))
(cond ((equalp response 'y) t)
((equalp response 'n) nil)
(t (format t "Come again?~&")
(set-player mark)))))
(defun player-move (board symbol)
(format t "~%Player ~a, please input your move.~&" symbol)
(set-board-elt (read) board symbol)
(format t "~%"))
(defun computer-move (board symbol)
(let ((move (get-random-element (list-legal-moves board))))
(set-board-elt move board symbol)
(format t "~%computer selects ~a~%~%" move)))
(defun computer-move-p (current-player autoplay-x-p autoplay-o-p)
(if (eq current-player 'x)
autoplay-x-p
autoplay-o-p))
(defun perform-turn (current-player board autoplay-x-p autoplay-o-p)
(if (computer-move-p current-player autoplay-x-p autoplay-o-p)
(computer-move board current-player)
(player-move board current-player)))
(defun switch-player ()
(if (eq *current-player* 'x)
(setf *current-player* 'o)
(setf *current-player* 'x)))
(defun display-board (board)
(loop for i downfrom 2 to 0
do (loop for j from 1 to 3
initially (format t "|")
do (format t "~a|" (or (get-board-elt (+ (* 3 i) j) board) (+ (* 3 i) j)))
finally (format t "~&"))))
(defun tic-tac-toe ()
(setf *current-player* 'x)
(let ((board (generate-board))
(autoplay-x-p (set-player 'x))
(autoplay-o-p (set-player 'o)))
(format t "~%")
(loop until (or (find-winner board) (null (list-legal-moves board)))
do (display-board board)
do (perform-turn *current-player* board autoplay-x-p autoplay-o-p)
do (switch-player)
finally (if (find-winner board)
(format t "The winner is ~a!" (find-winner board))
(format t "It's a tie.")))))
{{out}}
CL-USER> (tic-tac-toe)
Shall a computer play as X? (y/n)
n
Shall a computer play as O? (y/n)
y
|7|8|9|
|4|5|6|
|1|2|3|
Player X, please input your move.
5
|7|8|9|
|4|X|6|
|1|2|3|
computer selects 8
|7|O|9|
|4|X|6|
|1|2|3|
Player X, please input your move.
D
import std.stdio, std.string, std.algorithm, std.conv, std.random,
std.ascii, std.array, std.range, std.math;
struct GameBoard {
dchar[9] board = "123456789";
enum : dchar { human = 'X', computer = 'O' }
enum Game { going, humanWins, computerWins, draw }
const pure nothrow @safe @nogc invariant() {
int nHuman = 0, nComputer = 0;
foreach (immutable i, immutable c; board)
if (c.isDigit)
assert(i == c - '1'); // In correct position?
else {
assert(c == human || c == computer);
(c == human ? nHuman : nComputer)++;
}
assert(abs(nHuman - nComputer) <= 1);
}
string toString() const pure {
return format("%(%-(%s|%)\n-+-+-\n%)", board[].chunks(3));
}
bool isAvailable(in int i) const pure nothrow @safe @nogc {
return i >= 0 && i < 9 && board[i].isDigit;
}
auto availablePositions() const pure nothrow @safe /*@nogc*/ {
return 9.iota.filter!(i => isAvailable(i));
}
Game winner() const pure nothrow @safe /*@nogc*/ {
static immutable 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 (immutable win; wins) {
immutable bw0 = board[win[0]];
if (bw0.isDigit)
continue; // Nobody wins on this one.
if (bw0 == board[win[1]] && bw0 == board[win[2]])
return bw0 == GameBoard.human ?
Game.humanWins :
Game.computerWins;
}
return availablePositions.empty ? Game.draw: Game.going;
}
bool isFinished() const pure nothrow @safe /*@nogc*/ {
return winner != Game.going;
}
int computerMove() const // Random move.
out(res) {
assert(res >= 0 && res < 9 && isAvailable(res));
} body {
// return availablePositions.array.choice;
return availablePositions.array[uniform(0, $)];
}
}
GameBoard playGame() {
GameBoard board;
bool playsHuman = true;
while (!board.isFinished) {
board.writeln;
int move;
if (playsHuman) {
do {
writef("Your move (available moves: %s)? ",
board.availablePositions.map!q{ a + 1 });
readf("%d\n", &move);
move--; // Zero based indexing.
if (move < 0)
return board;
} while (!board.isAvailable(move));
} else
move = board.computerMove;
assert(board.isAvailable(move));
writefln("\n%s chose %d", playsHuman ? "You" : "I", move + 1);
board.board[move] = playsHuman ? GameBoard.human :
GameBoard.computer;
playsHuman = !playsHuman; // Switch player.
}
return board;
}
void main() {
"Tic-tac-toe game player.\n".writeln;
immutable outcome = playGame.winner;
final switch (outcome) {
case GameBoard.Game.going:
"Game stopped.".writeln;
break;
case GameBoard.Game.humanWins:
"\nYou win!".writeln;
break;
case GameBoard.Game.computerWins:
"\nI win.".writeln;
break;
case GameBoard.Game.draw:
"\nDraw".writeln;
break;
}
}
{{out}}
Tic-tac-toe game player.
1|2|3
-+-+-
4|5|6
-+-+-
7|8|9
Your move (available moves: [1, 2, 3, 4, 5, 6, 7, 8, 9])? 1
You chose 1
X|2|3
-+-+-
4|5|6
-+-+-
7|8|9
I chose 2
X|O|3
-+-+-
4|5|6
-+-+-
7|8|9
Your move (available moves: [3, 4, 5, 6, 7, 8, 9])? 5
You chose 5
X|O|3
-+-+-
4|X|6
-+-+-
7|8|9
I chose 3
X|O|O
-+-+-
4|X|6
-+-+-
7|8|9
Your move (available moves: [4, 6, 7, 8, 9])? 9
You chose 9
You win!
EasyLang
It uses minimax with alpha-beta pruning. Therefore, the computer never loses.
[https://easylang.online/apps/tictactoe.html Run it]
func init . . color 000 move 0 0 rect 100 100 color 666 move 24 4 line 24 68 move 48 4 line 48 68 move 4 24 line 68 24 move 4 48 line 68 48 for i range 9 f[i] = 0 . if state = 1 timer 0.2 . . func draw ind . . c = ind mod 3 r = ind / 3 x = c * 24 + 12 y = r * 24 + 12 if f[ind] = 4 color 900 move x - 7 y - 7 line x + 7 y + 7 move x + 7 y - 7 line x - 7 y + 7 elif f[ind] = 1 color 009 move x y circle 9 color 000 circle 7 . . func sum3 a d . st . for i range 3 s += f[a] a += d . if s = 3 st = -1 elif s = 12 st = 1 . . func rate . res . res = 0 for i range 3 call sum3 i * 3 1 res . for i range 3 call sum3 i 3 res . call sum3 0 4 res call sum3 2 2 res if res = 0 for i range 9 if f[i] = 0 res = 9 . . . . func minmax player alpha beta . rval rmov . call rate rval if rval <> 9 if player = 1 rval = -rval . else rval = alpha start = random 9 mov = start repeat if f[mov] = 0 f[mov] = player call minmax (5 - player) (-beta) (-rval) val h val = -val f[mov] = 0 if val > rval rval = val rmov = mov if rval >= beta mov = start - 1 . . . mov = (mov + 1) mod 9 until mov = start . . . func show_result val . . color 555 move 5 76 if val = -1 # this never happens text "You won" elif val = 1 text "You lost" else text "Tie" . state += 2 . func computer . . call minmax 4 -2 2 val mov f[mov] = 4 call draw mov call rate val state = 0 if val <> 9 call show_result val . . func human . . mov = floor (mouse_x / 24) + 3 * floor (mouse_y / 24) if f[mov] = 0 f[mov] = 1 call draw mov call rate val state = 1 if val <> 9 call show_result val else timer 0.5 . . . on timer call computer . on mouse_down if state = 0 if mouse_x < 72 and mouse_y < 72 call human . elif state >= 2 state -= 2 call init . . call init
## Erlang
The program will randomly chose if the computer ("X") or the user ("O") starts. The computer look ahead is only one level. Perhaps the computer might lose?
```Erlang
-module(tic_tac_toe).
-export( [task/0] ).
task() -> io:fwrite( "Result: ~p.~n", [turn(player(random:uniform()), board())] ).
board() -> [{X, erlang:integer_to_list(X)} || X <- lists:seq(1, 9)].
board_tuples( Selections, Board ) -> [lists:keyfind(X, 1, Board) || X <- Selections].
computer_move( Player, Board ) ->
[N | _T] = lists:flatten( [X(Player, Board) || X <- [fun computer_move_win/2, fun computer_move_block/2, fun computer_move_middle/2, fun computer_move_random/2]] ),
N.
computer_move_block( Player, Board ) -> computer_move_two_same_player( player(false, Player), Board ).
computer_move_middle( _Player, Board ) ->
{5, Y} = lists:keyfind( 5, 1, Board ),
computer_move_middle( is_empty(Y) ).
computer_move_middle( true ) -> [5];
computer_move_middle( false ) -> [].
computer_move_random( _Player, Board ) ->
Ns = [X || {X, Y} <- Board, is_empty(Y)],
[lists:nth( random:uniform(erlang:length(Ns)), Ns )].
computer_move_two_same_player( Player, Board ) ->
Selections = [X || X <- three_in_row_all(), is_two_same_player(Player, X, Board)],
computer_move_two_same_player( Player, Board, Selections ).
computer_move_two_same_player( _Player, _Board, [] ) -> [];
computer_move_two_same_player( _Player, Board, [Selection | _T] ) -> [X || {X, Y} <- board_tuples(Selection, Board), is_empty(Y)].
computer_move_win( Player, Board ) -> computer_move_two_same_player( Player, Board ).
is_empty( Square ) -> Square =< "9". % Do not use < "10".
is_finished( Board ) -> is_full( Board ) orelse is_three_in_row( Board ).
is_full( Board ) -> [] =:= [X || {X, Y} <- Board, is_empty(Y)].
is_three_in_row( Board ) ->
Fun = fun(Selections) -> is_three_in_row_same_player( board_tuples(Selections, Board) ) end,
lists:any( Fun, three_in_row_all() ).
is_three_in_row_same_player( Selected ) -> three_in_row_player( Selected ) =/= no_player.
is_two_same_player( Player, Selections, Board ) -> is_two_same_player( Player, [{X, Y} || {X, Y} <- board_tuples(Selections, Board), not is_empty(Y)] ).
is_two_same_player( Player, [{_X, Player}, {_Y, Player}] ) -> true;
is_two_same_player( _Player, _Selected ) -> false.
player( Random ) when Random > 0.5 -> "O";
player( _Random ) -> "X".
player( true, _Player ) -> finished;
player( false, "X" ) -> "O";
player( false, "O" ) -> "X".
result( Board ) -> result( is_full(Board), Board ).
result( true, _Board ) -> draw;
result( false, Board ) ->
[Winners] = [Selections || Selections <- three_in_row_all(), three_in_row_player(board_tuples(Selections, Board)) =/= no_player],
"Winner is " ++ three_in_row_player( board_tuples(Winners, Board) ).
three_in_row_all() -> three_in_row_horisontal() ++ three_in_row_vertical() ++ three_in_row_diagonal().
three_in_row_diagonal() -> [[1,5,9], [3,5,7]].
three_in_row_horisontal() -> [[1,2,3], [4,5,6], [7,8,9]].
three_in_row_vertical() -> [[1,4,7], [2,5,8], [3,6,9]].
three_in_row_player( [{_X, Player}, {_Y, Player}, {_Z, Player}] ) -> three_in_row_player( not is_empty(Player), Player );
three_in_row_player( _Selected ) -> no_player.
three_in_row_player( true, Player ) -> Player;
three_in_row_player( false, _Player ) -> no_player.
turn( finished, Board ) -> result( Board );
turn( "X"=Player, Board ) ->
N = computer_move( Player, Board ),
io:fwrite( "Computer, ~p, selected ~p~n", [Player, N] ),
New_board = [{N, Player} | lists:keydelete(N, 1, Board)],
turn( player(is_finished(New_board), Player), New_board );
turn( "O"=Player, Board ) ->
[turn_board_write_horisontal(X, Board) || X <- three_in_row_horisontal()],
Ns = [X || {X, Y} <- Board, is_empty(Y)],
Prompt = lists:flatten( io_lib:format("Player, ~p, select one of ~p: ", [Player, Ns]) ),
N = turn_next_move( Prompt, Ns ),
New_board = [{N, Player} | lists:keydelete(N, 1, Board)],
turn( player(is_finished(New_board), Player), New_board ).
turn_board_write_horisontal( Selections, Board ) ->
Tuples = [lists:keyfind(X, 1, Board) || X <- Selections],
[io:fwrite( "~p ", [Y]) || {_X, Y} <- Tuples],
io:fwrite( "~n" ).
turn_next_move( Prompt, Ns ) ->
{ok,[N]} = io:fread( Prompt, "~d" ),
turn_next_move_ok( lists:member(N, Ns), Prompt, Ns, N ).
turn_next_move_ok( true, _Prompt, _Ns, N ) -> N;
turn_next_move_ok( false, Prompt, Ns, _N ) -> turn_next_move( Prompt, Ns ).
{{out}}
96> tic_tac_toe:task().
"1" "2" "3"
"4" "5" "6"
"7" "8" "9"
Player, "O", select one of [1,2,3,4,5,6,7,8,9]: 5
Computer, "X", selected 2
"1" "X" "3"
"4" "O" "6"
"7" "8" "9"
Player, "O", select one of [1,3,4,6,7,8,9]: 1
Computer, "X", selected 9
"O" "X" "3"
"4" "O" "6"
"7" "8" "X"
Player, "O", select one of [3,4,6,7,8]: 3
Computer, "X", selected 7
"O" "X" "O"
"4" "O" "6"
"X" "8" "X"
Player, "O", select one of [4,6,8]: 8
Computer, "X", selected 6
"O" "X" "O"
"4" "O" "X"
"X" "O" "X"
Player, "O", select one of [4]: 4
Result: draw.
ERRE
Taken from ERRE distribution disk: comments and messages are in Italian.
!--------------------------------------------
! TRIS.R : gioca a tris contro l'operatore
!--------------------------------------------
PROGRAM TRIS
DIM TRIS%[9],T1%[9],PIECES$[3]
!$SEGMENT=$B800
!$INCLUDE="PC.LIB"
PROCEDURE DELAY(COUNT%)
FOR Z%=1 TO COUNT DO
END FOR
END PROCEDURE
PROCEDURE SET_BOARD
!
! Disegna lo schema del gioco
!
CLS
BLOAD("TRIS.BLD",0)
!$KEY
END PROCEDURE
PROCEDURE PUT_PIECES
!
! Pone i pezzi sulla scacchiera
!
Z%=0
FOR ROW%=6 TO 12 STEP 3 DO ! posizioni assolute sullo schermo
FOR COL%=32 TO 48 STEP 8 DO
LOCATE(ROW%+1,COL%+1)
Z%=Z%+1
PRINT(PIECES$[TRIS%[Z%]])
END FOR
END FOR
END PROCEDURE
PROCEDURE COMPUTE_MOVE(A%)
CASE A% OF
2-> C1%=C1%+1 END ->
4-> C2%=C2%+1 END ->
8-> S1%=TRUE S2%=TRUE END ->
3-> N1%=N1%+1 END ->
9-> N2%=N2%+1 END ->
27-> S1%=FALSE S2%=FALSE END ->
END CASE
END PROCEDURE
PROCEDURE PREPAREMOVE(T1%[],I%->M%)
!
! Prepara la mossa del calcolatore
!
T1%[I%]=2
C1%=0
C2%=0
N1%=0
N2%=0
FOR K%=0 TO 2 DO
COMPUTE_MOVE(T1%[3*K%+1]*T1%[3*K%+2]*T1%[3*K%+3])
COMPUTE_MOVE(T1%[K%+1]*T1%[K%+4]*T1%[K%+7])
END FOR
COMPUTE_MOVE(T1%[1]*T1%[5]*T1%[9])
COMPUTE_MOVE(T1%[3]*T1%[5]*T1%[7])
M%=-63*N2%+31*C2%-15*N1%+7*C1%
END PROCEDURE
PROCEDURE COMPUTER_MOVE
!
! Coordina le mosse del calcolatore
!
MAXSCORE%=-1000
FOR I%=1 TO 9 DO
IF TRIS%[I%]=1
THEN
PREPAREMOVE(TRIS%[],I%->MV%)
EXIT IF S2% AND NOT S1%
IF S1% AND S2%
THEN
TRIS%[I%]=2
DIARY$=DIARY$+"c"+MID$(STR$(I%),2)+"*"
PUT_PIECES
EXIT
END IF
IF MV%=0
THEN
MOVE%=I%
EXIT
END IF
IF MV%>MAXSCORE%
THEN
MOVE%=I%
MAXSCORE%=MV%
END IF
END IF
END FOR
IF NOT S2%
THEN
TRIS%[MOVE%]=2
DIARY$=DIARY$+"c"+MID$(STR$(MOVE%),2)+";"
PUT_PIECES
NMOVE%=NMOVE%-1
S1%=(NMOVE%=0)
END IF
END PROCEDURE
PROCEDURE PLAYER_MOVE
!
! Gioca l'avversario umano usando i tasti cursore per lo spostamento
!
LOCATE(19,13)
PRINT("Tocca a te .... ")
REPEAT
ROW%=7
COL%=32
LOCATE(ROW%+1,COL%+1)
PRINT("Ü")
REPEAT
GET(B$)
IF LEN(B$)=2 THEN
CASE ASC(RIGHT$(B$,1)+CHR$(0)) OF
77-> ! codice tastiera per CRSR =>
LOCATE(ROW%+1,COL%+1)
PRINT(" ")
COL%=-(COL%+8)*(COL%<=40)-32*(COL%>40)
LOCATE(ROW%+1,COL%+1)
PRINT("Ü")
END ->
75-> ! codice tastiera per CRSR <=
LOCATE(ROW%+1,COL%+1)
PRINT(" ")
COL%=-(COL%-8)*(COL%>=40)-48*(COL%<40)
LOCATE(ROW%+1,COL%+1)
PRINT("Ü")
END ->
80-> ! codice tastiera per CRSR DOWN
LOCATE(ROW%+1,COL%+1)
PRINT(" ")
ROW%=-(ROW%+3)*(ROW%<=10)-7*(ROW%>10)
LOCATE(ROW%+1,COL%+1)
PRINT("Ü")
END ->
72-> ! codice tastiera per CRSR UP
LOCATE(ROW%+1,COL%+1)
PRINT(" ")
ROW%=-(ROW%-3)*(ROW%>=10)-13*(ROW%<10)
LOCATE(ROW%+1,COL%+1)
PRINT("Ü")
END ->
END CASE
END IF
UNTIL B$=CHR$(13)
MM%=ROW%+COL%/8-10 ! da coordinate schermo a coordinate scacchiera
UNTIL TRIS%[MM%]=1
TRIS%[MM%]=3
LOCATE(ROW%+1,COL%+1)
PRINT(" ")
DIARY$=DIARY$+"p"+MID$(STR$(MM%),2)+";"
PUT_PIECES
NMOVE%=NMOVE%-1
S1%=(NMOVE%=0)
LOCATE(19,13)
PRINT(STRING$(45," "))
END PROCEDURE
BEGIN
DATA(" ","+","o")
SET_BOARD
REPEAT
S1%=FALSE S2%=FALSE ! determinano lo stato della partita
NMOVE%=9
FOR Z%=1 TO 3 DO
READ(PIECES$[Z%])
END FOR
FOR Z%=1 TO 9 DO
TRIS%[Z%]=1
END FOR
LOCATE(19,13)
PRINT("Giochi per primo ?")
REPEAT
GET(A$)
UNTIL A$="S" OR A$="s" OR A$="N" OR A$="n"
PUT_PIECES
FOR INDICE%=1 TO 9 DO
IF A$="s" OR A$="S"
THEN
PLAYER_MOVE
EXIT IF S1% OR S2%
COMPUTER_MOVE
EXIT IF S1% OR S2%
ELSE
COMPUTER_MOVE
EXIT IF S1% OR S2%
PLAYER_MOVE
EXIT IF S1% OR S2%
END IF
END FOR
LOCATE(19,13)
CASE TRUE OF
(S1% AND NOT S2%)->
PRINT("E' finita pari !!! ")
END ->
(S2% AND NOT S1%)->
PRINT("HAI VINTO !!! ")
END ->
(S1% AND S2%)->
PRINT("HO VINTO IO !!! ")
END ->
END CASE
DELAY(500)
LOCATE(19,13)
PRINT(DIARY$)
DELAY(500)
LOCATE(19,13)
PRINT("Vuoi giocare ancora ? ")
REPEAT
GET(A$)
UNTIL A$="S" OR A$="s" OR A$="N" OR A$="n"
UNTIL A$="N" OR A$="n"
END PROGRAM
{{out}} A game example
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░
░░▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒░░
░░▒▒▓▓ ▓▓▒▒░░
░░▒▒▓▓ ┌───────────────┐ ╔═══════╤═══════╤═══════╗ ┌────────────┐ ▓▓▒▒░░
░░▒▒▓▓ │───── TRIS ────│ ║ + │ o │ + ║ │Il calcola- │ ▓▓▒▒░░
░░▒▒▓▓ │ │ ║ │ │ ║ │tore non può│ ▓▓▒▒░░
░░▒▒▓▓ │Si gioca contro│ ╟───────┼───────┼───────╢ │perdere e │ ▓▓▒▒░░
░░▒▒▓▓ │il calcolatore.│ ║ + │ o │ o ║ │perciò il │ ▓▓▒▒░░
░░▒▒▓▓ │Per muoversi │ ║ │ │ ║ │giocatore │ ▓▓▒▒░░
░░▒▒▓▓ │usare i tasti │ ╟───────┼───────┼───────╢ │può al mas- │ ▓▓▒▒░░
░░▒▒▓▓ │cursore e ◄──┘ │ ║ o │ + │ o ║ │simo pareg- │ ▓▓▒▒░░
░░▒▒▓▓ │per confermare.│ ║ │ │ ║ │giare. │ ▓▓▒▒░░
░░▒▒▓▓ └───────────────┘ ╚═══════╧═══════╧═══════╝ └────────────┘ ▓▓▒▒░░
░░▒▒▓▓ ▓▓▒▒░░
░░▒▒▓▓ ▓▓▒▒░░
░░▒▒▓▓ ┌────────────────────────────────────────────────────────────┐ ▓▓▒▒░░
░░▒▒▓▓ │ Vuoi giocare ancora ? │ ▓▓▒▒░░
░░▒▒▓▓ └────────────────────────────────────────────────────────────┘ ▓▓▒▒░░
░░▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒░░
░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
Euphoria
{{works with|OpenEuphoria}} No computer AI
include std/console.e
include std/text.e
include std/search.e
include std/sequence.e
sequence board
sequence players = {"X","O"}
function DisplayBoard()
for i = 1 to 3 do
for j = 1 to 3 do
printf(1,"%s",board[i][j])
if j < 3 then
printf(1,"%s","|")
end if
end for
if i < 3 then
puts(1,"\n-----\n")
else
puts(1,"\n\n")
end if
end for
return 1
end function
function CheckWinner()
sequence temp = board
for a = 1 to 2 do
for i = 1 to 3 do
if equal({"X","X","X"},temp[i]) then
puts(1,"X wins\n\n")
return 1
elsif equal({"O","O","O"},temp[i]) then
puts(1,"O wins\n\n")
return 1
end if
end for
temp = columnize(board)
end for
if equal({"X","X","X"},{board[1][1],board[2][2],board[3][3]}) or
equal({"X","X","X"},{board[1][3],board[2][2],board[3][1]}) then
puts(1,"X wins\n")
return 1
elsif equal({"O","O","O"},{board[1][1],board[2][2],board[3][3]}) or
equal({"O","O","O"},{board[1][3],board[2][2],board[3][1]}) then
puts(1,"O wins\n")
return 1
end if
if moves = 9 then
puts(1,"Draw\n\n")
return 1
end if
return 0
end function
integer turn, row, column, moves
sequence replay
while 1 do
board = repeat(repeat(" ",3),3)
DisplayBoard()
turn = rand(2)
moves = 0
while 1 do
while 1 do
printf(1,"%s's turn\n",players[turn])
row = prompt_number("Enter row: ",{})
column = prompt_number("Enter column: ",{})
if match(board[row][column]," ") then
board[row][column] = players[turn]
moves += 1
exit
else
puts(1,"Space already taken - pick again\n")
end if
end while
DisplayBoard()
if CheckWinner() then
exit
end if
if turn = 1 then
turn = 2
else
turn = 1
end if
end while
replay = lower(prompt_string("Play again (y/n)?\n\n"))
if match(replay,"n") then
exit
end if
end while
{{out}}
| |
-----
| |
-----
| |
O's turn
Enter row: 1
Enter column: 1
O| |
-----
| |
-----
| |
X's turn
Enter row: 3
Enter column: 3
O| |
-----
| |
-----
| |X
O's turn
Enter row: 3
Enter column: 1
O| |
-----
| |
-----
O| |X
X's turn
Enter row: 2
Enter column: 1
O| |
-----
X| |
-----
O| |X
O's turn
Enter row: 2
Enter column: 2
O| |
-----
X|O|
-----
O| |X
X's turn
Enter row: 1
Enter column: 3
O| |X
-----
X|O|
-----
O| |X
O's turn
Enter row: 1
Enter column: 2
O|O|X
-----
X|O|
-----
O| |X
X's turn
Enter row: 3
Enter column: 2
O|O|X
-----
X|O|
-----
O|X|X
O's turn
Enter row: 2
Enter column: 3
O|O|X
-----
X|O|O
-----
O|X|X
Draw
Play again (y/n)?
=={{header|F_Sharp|F#}}== A purely-functional solution with a naive (but perfect) computer player implementation. The first move is played randomly by the computer.
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
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.
Fortran
Objective: write program in less than 100 lines, not using semicolons. Computer never loses, but plays as random as possible. Player gets first move of first game. Afterwards, first move alternates between computer and player.
! This is a fortran95 implementation of the game of tic-tac-toe.
! - Objective was to use less than 100 lines.
! - No attention has been devoted to efficiency.
! - Indentation by findent: https://sourceforge.net/projects/findent/
! - This is free software, use as you like at own risk.
! - Compile: gfortran -o tictactoe tictactoe.f90
! - Run: ./tictactoe
! Comments to: wvermin at gmail dot com
module tic
implicit none
integer :: b(9)
contains
logical function iswin(p)
integer,intent(in) :: p
iswin = &
all(b([1,2,3])==p).or.all(b([4,5,6])==p).or.all(b([7,8,9])==p).or.&
all(b([1,4,7])==p).or.all(b([2,5,8])==p).or.all(b([3,6,9])==p).or.&
all(b([1,5,9])==p).or.all(b([3,5,7])==p)
end function iswin
subroutine printb(mes)
character(len=*) :: mes
integer :: i,j
character :: s(0:2) = ['.','X','O']
print "(3a3,' ',3i3)",(s(b(3*i+1:3*i+3)),(j,j=3*i+1,3*i+3),i=0,2)
if(mes /= ' ') print "(/,a)",mes
end subroutine printb
integer recursive function minmax(player,bestm) result(bestv)
integer :: player,bestm,move,v,bm,win=1000,inf=100000
real :: x
if (all(b .ne. 0)) then
bestv = 0
else
bestv = -inf
do move=1,9
if (b(move) == 0) then
b(move) = player
if (iswin(player)) then
v = win
else
call random_number(x)
v = -minmax(3-player,bm) - int(10*x)
endif
if (v > bestv) then
bestv = v
bestm = move
endif
b(move) = 0
if (v == win) exit
endif
enddo
endif
end function minmax
end module tic
program tictactoe
! computer: player=1, user: player=2
use tic
implicit none
integer :: move,ios,v,bestmove,ply,player=2,k,values(8)
integer,allocatable :: seed(:)
call date_and_time(values=values)
call random_seed(size=k)
allocate(seed(k))
seed = values(8)+1000*values(7)+60*1000*values(6)+60*60*1000*values(5)
call random_seed(put=seed)
mainloop: do
b = 0
call printb('You have O, I have X. You enter 0: game ends.')
plyloop: do ply=0,4
if (player == 2 .or. ply >0 ) then ! user move
write(*,"(/,a)",advance='no'),'Your move? (0..9) '
getloop: do
readloop: do
read (*,*,iostat=ios),move
if (ios == 0 .and. move >= 0 .and. move <= 9) exit readloop
write(*,"(a)",advance='no'),'huh? Try again (0..9): '
enddo readloop
if ( move == 0) exit mainloop
if (b(move) == 0) exit getloop
write(*,"(a)",advance='no'),'Already occupied, again (0..9): '
enddo getloop
b(move) = 2
if(iswin(2)) then ! this should not happen
call printb('***** You win *****')
exit plyloop
endif
endif
v = minmax(1,bestmove) ! computer move
b(bestmove) = 1
if(iswin(1)) then
call printb('***** I win *****')
exit plyloop
endif
write(*,"(/,a,i3)"), 'My move: ',bestmove
call printb(' ')
enddo plyloop
if(ply == 5) write(*,"('***** Draw *****',/)")
player = 3-player
enddo mainloop
end program
Go
Intermediate, like Python's "Better skilled player." Computer wins and blocks where it can, but otherwise plays randomly. Plays multiple games and keeps score. Player gets first move of first game. Afterwards, loser gets to go first, or after cat games, first move alternates.
package main
import (
"bufio"
"fmt"
"math/rand"
"os"
"strings"
)
var b []byte
func printBoard() {
fmt.Printf("%s\n%s\n%s\n", b[0:3], b[3:6], b[6:9])
}
var pScore, cScore int
var pMark, cMark byte = 'X', 'O'
var in = bufio.NewReader(os.Stdin)
func main() {
b = make([]byte, 9)
fmt.Println("Play by entering a digit.")
for {
// start of game
for i := range b {
b[i] = '1' + byte(i)
}
computerStart := cMark == 'X'
if computerStart {
fmt.Println("I go first, playing X's")
} else {
fmt.Println("You go first, playing X's")
}
TakeTurns:
for {
if !computerStart {
if !playerTurn() {
return
}
if gameOver() {
break TakeTurns
}
}
computerStart = false
computerTurn()
if gameOver() {
break TakeTurns
}
}
fmt.Println("Score: you", pScore, "me", cScore)
fmt.Println("\nLet's play again.")
}
}
func playerTurn() bool {
var pm string
var err error
for i := 0; i < 3; i++ { // retry loop
printBoard()
fmt.Printf("%c's move? ", pMark)
if pm, err = in.ReadString('\n'); err != nil {
fmt.Println(err)
return false
}
pm = strings.TrimSpace(pm)
if pm >= "1" && pm <= "9" && b[pm[0]-'1'] == pm[0] {
x := pm[0] - '1'
b[x] = pMark
return true
}
}
fmt.Println("You're not playing right.")
return false
}
var choices = make([]int, 9)
func computerTurn() {
printBoard()
var x int
defer func() {
fmt.Println("My move:", x+1)
b[x] = cMark
}()
// look for two in a row
block := -1
for _, l := range lines {
var mine, yours int
x = -1
for _, sq := range l {
switch b[sq] {
case cMark:
mine++
case pMark:
yours++
default:
x = sq
}
}
if mine == 2 && x >= 0 {
return // strategy 1: make winning move
}
if yours == 2 && x >= 0 {
block = x
}
}
if block >= 0 {
x = block // strategy 2: make blocking move
return
}
// default strategy: random move
choices = choices[:0]
for i, sq := range b {
if sq == '1'+byte(i) {
choices = append(choices, i)
}
}
x = choices[rand.Intn(len(choices))]
}
func gameOver() bool {
// check for win
for _, l := range lines {
if b[l[0]] == b[l[1]] && b[l[1]] == b[l[2]] {
printBoard()
if b[l[0]] == cMark {
fmt.Println("I win!")
cScore++
pMark, cMark = 'X', 'O'
} else {
fmt.Println("You win!")
pScore++
pMark, cMark = 'O', 'X'
}
return true
}
}
// check for empty squares
for i, sq := range b {
if sq == '1'+byte(i) {
return false
}
}
fmt.Println("Cat game.")
pMark, cMark = cMark, pMark
return true
}
var lines = [][]int{
{0, 1, 2}, // rows
{3, 4, 5},
{6, 7, 8},
{0, 3, 6}, // columns
{1, 4, 7},
{2, 5, 8},
{0, 4, 8}, // diagonals
{2, 4, 6},
}
Groovy
Simplified version of Tic Tac Toe for player vs. player (no AI computer-controlled option).
class Main {
def input = new Scanner(System.in)
static main(args) {
Main main = new Main();
main.play();
}
public void play() {
TicTackToe game = new TicTackToe();
game.init()
def gameOver = false
def player = game.player1
println "Players take turns marking a square. Only squares \n"+
"not already marked can be picked. Once a player has \n"+
"marked three squares in a row, column or diagonal, they win! If all squares \n"+
"are marked and no three squares are the same, a tied game is declared.\n"+
"Player 1 is O and Player 2 is X \n"+
"Have Fun! \n\n"
println "${game.drawBoard()}"
while (!gameOver && game.plays < 9) {
player = game.currentPlayer == 1 ? game.player1 :game.player2
def validPick = false;
while (!validPick) {
def square
println "Next $player, enter move by selecting square's number :"
try {
square = input.nextLine();
} catch (Exception ex) { }
if (square.length() == 1 && Character.isDigit(square.toCharArray()[0])) { validPick = game.placeMarker(square) }
if (!validPick) { println "Select another Square" }
}
(game.checkWinner(player))? gameOver = true : game.switchPlayers()
println(game.drawBoard());
}
println "Game Over, " + ((gameOver == true)? "$player Wins" : "Draw")
}
}
public class TicTackToe {
def board = new Object[3][3]
def final player1 = "player 1"
def final player2 = "player 2"
def final marker1 = 'X'
def final marker2 = 'O'
int currentPlayer
int plays
public TicTacToe(){
}
def init() {
int counter = 0;
(0..2).each { row ->
(0..2).each { col ->
board[row][col] = (++counter).toString();
}
}
plays = 0
currentPlayer =1
}
def switchPlayers() {
currentPlayer = (currentPlayer == 1) ? 2:1
plays++
}
def placeMarker(play) {
def result = false
(0..2).each { row ->
(0..2).each { col ->
if (board[row][col].toString()==play.toString()){
board[row][col] = (currentPlayer == 1) ? marker2 : marker1;
result = true;
}
}
}
return result;
}
def checkWinner(player) {
char current = (player == player1)? marker2: marker1
//Checking
return checkRows(current) || checkColumns(current) ||checkDiagonals(current);
}
def checkRows(char current){
(0..2).any{ line ->
board[line].every { it == current}
}
}
def checkColumns(char current){
(0..2).any{i ->
(0..2).every{j ->
board[j][i]==current }
}
}
def checkDiagonals(char current){
def rightDiag = [board[0][0],board[1][1],board[2][2]]
def leftDiag = [board[0][2],board[1][1],board[2][0]]
return rightDiag.every{it == current} || leftDiag.every{it == current}
}
def drawBoard() {
StringBuilder builder = new StringBuilder("Game board: \n");
(0..2).each { row->
(0..2).each {col ->
builder.append("[" + board[row][col] + "]");
}
builder.append("\n");
}
builder.append("\n");
return builder.toString();
}
}
Haskell
Computer player has three strategies: 1. Try to block the opponent first, 2. Try to guess a good position for the next move, 3. Place a piece randomly. There are lots of comments throughout the code.
module Main where
import System.Random
import Data.List (intercalate, find, minimumBy)
import System.Environment (getArgs)
import Data.Char (digitToInt)
import Data.Maybe (listToMaybe, mapMaybe)
import Control.Monad (guard)
import Data.Ord (comparing)
-- check if there is a horizontal, vertical or diagonal line of
-- X or O
tictactoe :: String -> Bool
tictactoe a = tictactoeFor 'X' a /= tictactoeFor 'O' a
-- check if there is a horizontal, vertical or diagonal line
-- for the given player "n"
tictactoeFor :: Char -> String -> Bool
tictactoeFor n [a,b,c,d,e,f,g,h,i] =
[n,n,n] `elem` [[a,b,c],[d,e,f],[g,h,i],[a,d,g],
[b,e,h],[c,f,i],[a,e,i],[c,e,g]]
-- empty game board
start :: String
start = " "
-- check if there is an X or an O at the given position
isPossible :: Int -> String -> Bool
isPossible n game = (game !! n) `notElem` "XO"
-- try to place an X or an O at a given position.
-- "Right" + modified board means success, "Left" + unmodified board
-- means failure
place :: Int -> Char -> String -> Either String String
place i c game =
if isPossible i game
then Right $ take i game ++ [c] ++ drop (i + 1) game
else Left game
-- COMPUTER AI
-- get the number of movements, starting from a given non-empty board
-- and a position for the next movement, until the specified player
-- wins or no movement is possible
-- the positions are chosen sequentially, so there's not much
-- intelligence here anyway
developGame :: Bool -> Int -> Int -> Char -> String -> (Int, Char, String)
developGame iterateMore moves i player game
| i > 8 =
-- if i arrives to the last position, iterate again from 0
-- but do it only once
if iterateMore
then developGame False moves 0 player game
-- draw game (after one iteration, still no winning moves)
else (moves, player, game)
-- draw game (game board full) or a win for the player
| moves == 9 || tictactoeFor player game = (moves, player, game)
-- make a move, if possible, and continue playing
| otherwise = case place i otherPlayer game of
-- position i is not empty. try with the next position
Left _ -> developGame iterateMore moves (i + 1)
otherPlayer game
-- position i was empty, so it was a valid move.
-- change the player and make a new move, starting at pos 0
Right newGame -> developGame iterateMore (moves + 1) 0
otherPlayer newGame
where
otherPlayer = changePlayer player
-- COMPUTER AI
-- starting from a given non-empty board, try to guess which position
-- could lead the player to the fastest victory.
bestMoveFor :: Char -> String -> Int
bestMoveFor player game = bestMove
where
-- drive the game to its end for each starting position
continuations = [ (x, developGame True 0 x player game) |
x <- [0..8] ]
-- compare the number of moves of the game and take the
-- shortest one
move (_, (m, _, _)) = m
(bestMove, _) = minimumBy (comparing move) continuations
-- canBlock checks if the opponent has two pieces in a row and the
-- other cell in the row is empty, and places the player's piece there,
-- blocking the opponent
canBlock :: Char -> String -> Maybe Int
canBlock p [a,b,c,d,e,f,g,h,i] =
listToMaybe $ mapMaybe blockable [[a,b,c],[d,e,f],[g,h,i],[a,d,g],
[b,e,h],[c,f,i],[a,e,i],[c,e,g]]
where
blockable xs = do
guard $ length (filter (== otherPlayer) xs) == 2
x <- find (`elem` "123456789") xs
return $ digitToInt x
otherPlayer = changePlayer p
-- format a game board for on-screen printing
showGame :: String -> String
showGame [a,b,c,d,e,f,g,h,i] =
topBottom ++
"| | 1 | 2 | 3 |\n" ++
topBottom ++
row "0" [[a],[b],[c]] ++
row "3" [[d],[e],[f]] ++
row "6" [[g],[h],[i]]
where
topBottom = "+----+---+---+---+\n"
row n x = "| " ++ n ++ "+ | " ++
intercalate " | " x ++ " |\n" ++ topBottom
-- ask the user to press a numeric key and convert it to an int
enterNumber :: IO Int
enterNumber = do
c <- getChar
if c `elem` "123456789"
then do
putStrLn ""
return $ digitToInt c
else do
putStrLn "\nPlease enter a digit!"
enterNumber
-- a human player's turn: get the number of pieces put on the board,
-- the next piece to be put (X or O) and a game board, and return
-- a new game state, checking if the piece can be placed on the board.
-- if it can't, make the user try again.
turn :: (Int, Char, String) -> IO (Int, Char, String)
turn (count, player, game) = do
putStr $ "Please tell me where you want to put an " ++
[player] ++ ": "
pos <- enterNumber
case place (pos - 1) player game of
Left oldGame -> do
putStrLn "That place is already taken!\n"
turn (count, player, oldGame)
Right newGame ->
return (count + 1, changePlayer player, newGame)
-- alternate between X and O players
changePlayer :: Char -> Char
changePlayer 'O' = 'X'
changePlayer 'X' = 'O'
-- COMPUTER AI
-- make an automatic turn, placing an X or an O game board.
-- the first movement is always random.
-- first, the computer looks for two pieces of his opponent in a row
-- and tries to block.
-- otherwise, it tries to guess the best position for the next movement.
-- as a last resort, it places a piece randomly.
autoTurn :: Bool -> (Int, Char, String) -> IO (Int, Char, String)
autoTurn forceRandom (count, player, game) = do
-- try a random position 'cause everything else failed
-- count == 0 overrides the value of forceRandom
i <- if count == 0 || forceRandom
then randomRIO (0,8)
else return $
case canBlock player game of
-- opponent can't be blocked. try to guess
-- the best movement
Nothing -> bestMoveFor player game
-- opponent can be blocked, so just do it!
Just blockPos -> blockPos
-- if trying to place a piece at a calculated position doesn't work,
-- just try again with a random value
case place i player game of
Left oldGame -> autoTurn True (count, player, oldGame)
Right newGame -> do
putStrLn $ "It's player " ++ [player] ++ "'s turn."
return (count + 1, changePlayer player, newGame)
-- play a game until someone wins or the board becomes full.
-- depending on the value of the variable "auto", ask the user(s) to
-- put some pieces on the board or do it automatically
play :: Int -> (Int, Char, String) -> IO ()
play auto cpg@(_, player, game) = do
newcpg@(newCount, newPlayer, newGame) <- case auto of
-- if both players are human, always ask them
0 -> turn cpg
-- if both players are computer, always play auto
1 -> autoTurn False cpg
-- X is computer, O is human
2 -> if player == 'X' then autoTurn False cpg else turn cpg
-- X is human, O is computer
3 -> if player == 'O' then autoTurn False cpg else turn cpg
putStrLn $ "\n" ++ showGame newGame
if tictactoe newGame
then putStrLn $ "Player " ++ [changePlayer newPlayer] ++ " wins!\n"
else
if newCount == 9
then putStrLn "Draw!\n"
else play auto newcpg
-- main program: greet the user, ask for a game type, ask for the
-- player that'll start the game, and play the game beginning with an
-- empty board
main :: IO ()
main = do
a <- getArgs
if null a
then usage
else do
let option = head a
if option `elem` ["0","1","2","3"]
then do
putStrLn $ "\n" ++ showGame start
let m = read option :: Int
play m (0, 'X', start)
else usage
usage :: IO ()
usage = do
putStrLn "TIC-TAC-TOE GAME\n
### ==========
\n"
putStrLn "How do you want to play?"
putStrLn "Run the program with one of the following options."
putStrLn "0 : both players are human"
putStrLn "1 : both players are computer"
putStrLn "2 : player X is computer and player O is human"
putStrLn "3 : player X is human and player O is computer"
putStrLn "Player X always begins."
{{out}} Player X is computer, O is human.
+----+---+---+---+
| | 1 | 2 | 3 |
+----+---+---+---+
| 0+ | | | |
+----+---+---+---+
| 3+ | | | |
+----+---+---+---+
| 6+ | | | |
+----+---+---+---+
It's player X's turn.
+----+---+---+---+
| | 1 | 2 | 3 |
+----+---+---+---+
| 0+ | | | |
+----+---+---+---+
| 3+ | | X | |
+----+---+---+---+
| 6+ | | | |
+----+---+---+---+
Please tell me where you want to put an O: 1
+----+---+---+---+
| | 1 | 2 | 3 |
+----+---+---+---+
| 0+ | O | | |
+----+---+---+---+
| 3+ | | X | |
+----+---+---+---+
| 6+ | | | |
+----+---+---+---+
It's player X's turn.
+----+---+---+---+
| | 1 | 2 | 3 |
+----+---+---+---+
| 0+ | O | | X |
+----+---+---+---+
| 3+ | | X | |
+----+---+---+---+
| 6+ | | | |
+----+---+---+---+
Please tell me where you want to put an O: 7
+----+---+---+---+
| | 1 | 2 | 3 |
+----+---+---+---+
| 0+ | O | | X |
+----+---+---+---+
| 3+ | | X | |
+----+---+---+---+
| 6+ | O | | |
+----+---+---+---+
It's player X's turn.
+----+---+---+---+
| | 1 | 2 | 3 |
+----+---+---+---+
| 0+ | O | | X |
+----+---+---+---+
| 3+ | X | X | |
+----+---+---+---+
| 6+ | O | | |
+----+---+---+---+
Please tell me where you want to put an O: 6
+----+---+---+---+
| | 1 | 2 | 3 |
+----+---+---+---+
| 0+ | O | | X |
+----+---+---+---+
| 3+ | X | X | O |
+----+---+---+---+
| 6+ | O | | |
+----+---+---+---+
It's player X's turn.
+----+---+---+---+
| | 1 | 2 | 3 |
+----+---+---+---+
| 0+ | O | | X |
+----+---+---+---+
| 3+ | X | X | O |
+----+---+---+---+
| 6+ | O | X | |
+----+---+---+---+
Please tell me where you want to put an O: 2
+----+---+---+---+
| | 1 | 2 | 3 |
+----+---+---+---+
| 0+ | O | O | X |
+----+---+---+---+
| 3+ | X | X | O |
+----+---+---+---+
| 6+ | O | X | |
+----+---+---+---+
It's player X's turn.
+----+---+---+---+
| | 1 | 2 | 3 |
+----+---+---+---+
| 0+ | O | O | X |
+----+---+---+---+
| 3+ | X | X | O |
+----+---+---+---+
| 6+ | O | X | X |
+----+---+---+---+
Draw!
=={{header|Icon}} and {{header|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.
# Play TicTacToe
$define E " " # empty square
$define X "X" # X piece
$define O "O" # O piece
# -- 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
# 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
# -- players make their move on the board
# 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
# pick and make a move at random from empty positions
procedure random_move (board, player)
board[?empty_fields(board)] := player
end
# -- 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
# -- get things started
procedure main ()
play_game ()
end
J
To subsequent j poster: replacing this entry is fine by me.
Note 'ttt adjudicates or plays'
use: markers ttt characters
characters represent the cell names.
markers is a length 3 character vector of the
characters to use for first and second player
followed by the opponent's mark.
'XOX' means X plays 1st, O is the other mark,
and the first strategy plays 1st.
'XOO' means X plays 1st, O is the other mark,
and the second strategy moves first.
The game is set up for the computer as the
first strategy (random), and human as second.
A standard use:
'XOX'ttt'abcdefghijkl'
Example game reformatted w/ emacs artist-mode
to fit your display:
'#-#'ttt'wersdfxcv'
w│e│r w│e│r .... -│e│r . -│e│#
─┼─┼─ . ─┼─┼─ .. ─┼─┼─ .. ─┼─┼─
s│d│f . s│#│f .. s│#│f .. -│#│f
─┼─┼─ .. ─┼─┼─ . ─┼─┼─ ... ─┼─┼─
x│c│v .. -│c│v . -│c│# .. -│c│#
d .. v .. r . VICTORY
w│e│r .. w│e│r .. -│e│# .
─┼─┼─ ... ─┼─┼─ .. ─┼─┼─ .
s│#│f ... s│#│f .. s│#│f .
─┼─┼─ .. ─┼─┼─ ... ─┼─┼─ ...
x│c│v -│c│# -│c│#
-->cell for -? -->cell for -? -->cell for -?
x w s
)
while=: conjunction def 'u ^: v ^:_' NB. j assumes while is a verb and needs to know while is a conjunction.
ttt=: outcome@:((display@:move) while undecided)@:display@:prepare
blindfolded_variant=: outcome@:display@:(move while undecided)@:display@:prepare
outcome=: {&(>;:'kitty VICTORY')@:won NB. (outcome does not pass along the state)
move=: post locate
undecided=: won nor full
prepare=: , board@:] NB. form the state vector
Note 'locate'
is a monadic verb. y is the state vector.
returns the character of the chosen cell.
Generally:
locate=: second_strategy`first_strategy@.(first = mark)
Simplified:
locate=: human_locate NB. human versus human
)
locate=: human_locate`computer_locate@.(first = mark)
display=: show [: (1 1,:5 5)&(];.0)@:": [: <"0 fold
computer_locate=: [: show@{. board -. marks NB. strategy: first available
computer_locate=: [: show@({~ ?@:#) board -. marks NB. strategy: random
human_locate=: monad define
state=. y
m=. mark state
b=. board state
cells=. b -. marks state
show '-->cell for ' , m , '?'
whilst. cell -.@:e. cells do. cell =. {. (1!:1]1) , m end.
)
post=: 2&A.@:(3&{.)@:[ prepare mark@:[`((i.~ board)~)`(board@:[)}
mark=: {. NB. mark of the current player from state
marks=: 2&{. NB. extract both markers from state
board=: _9&{. NB. extract board from state
first=: 2&{ NB. extract first player from state
show=: [ smoutput
full=: 2 = #@:~.
won=: test@:fold
fold=: 3 3 $ board
test=: [: any [: all [: infix_pairs_agree |:@:lines
lines=: , diagonal , diagonal@:|. , |:
diagonal=: (<0 1)&|:
all=: *./
any=: +./
nor=: 8 b.
infix_pairs_agree=: 2&(=/\)
Java
This version works in the terminal itself, and uses the numpad for data entry. The computer is unbeatable, but some lines can be removed to avoid that. There's also an override that thrown in, just for fun.
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.Hashtable;
public class TicTacToe
{
public static void main(String[] args)
{
TicTacToe now=new TicTacToe();
now.startMatch();
}
private int[][] marks;
private int[][] wins;
private int[] weights;
private char[][] grid;
private final int knotcount=3;
private final int crosscount=4;
private final int totalcount=5;
private final int playerid=0;
private final int compid=1;
private final int truceid=2;
private final int playingid=3;
private String movesPlayer;
private byte override;
private char[][] overridegrid={{'o','o','o'},{'o','o','o'},{'o','o','o'}};
private char[][] numpad={{'7','8','9'},{'4','5','6'},{'1','2','3'}};
private Hashtable<Integer,Integer> crossbank;
private Hashtable<Integer,Integer> knotbank;
public void startMatch()
{
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
System.out.print("Start?(y/n):");
char choice='y';
try
{
choice=br.readLine().charAt(0);
}
catch(Exception e)
{
System.out.println(e.getMessage());
}
if(choice=='n'||choice=='N')
{
return;
}
System.out.println("Use a standard numpad as an entry grid, as so:\n ");
display(numpad);
System.out.println("Begin");
int playerscore=0;
int compscore=0;
do
{
int result=startGame();
if(result==playerid)
playerscore++;
else if(result==compid)
compscore++;
System.out.println("Score: Player-"+playerscore+" AI-"+compscore);
System.out.print("Another?(y/n):");
try
{
choice=br.readLine().charAt(0);
}
catch(Exception e)
{
System.out.println(e.getMessage());
}
}while(choice!='n'||choice=='N');
System.out.println("Game over.");
}
private void put(int cell,int player)
{
int i=-1,j=-1;;
switch(cell)
{
case 1:i=2;j=0;break;
case 2:i=2;j=1;break;
case 3:i=2;j=2;break;
case 4:i=1;j=0;break;
case 5:i=1;j=1;break;
case 6:i=1;j=2;break;
case 7:i=0;j=0;break;
case 8:i=0;j=1;break;
case 9:i=0;j=2;break;
default:display(overridegrid);return;
}
char mark='x';
if(player==0)
mark='o';
grid[i][j]=mark;
display(grid);
}
private int startGame()
{
init();
display(grid);
int status=playingid;
while(status==playingid)
{
put(playerMove(),0);
if(override==1)
{
System.out.println("O wins.");
return playerid;
}
status=checkForWin();
if(status!=playingid)
break;
try{Thread.sleep(1000);}catch(Exception e){System.out.print(e.getMessage());}
put(compMove(),1);
status=checkForWin();
}
return status;
}
private void init()
{
movesPlayer="";
override=0;
marks=new int[8][6];
wins=new int[][] //new int[8][3];
{
{7,8,9},
{4,5,6},
{1,2,3},
{7,4,1},
{8,5,2},
{9,6,3},
{7,5,3},
{9,5,1}
};
weights=new int[]{3,2,3,2,4,2,3,2,3};
grid=new char[][]{{' ',' ',' '},{' ',' ',' '},{' ',' ',' '}};
crossbank=new Hashtable<Integer,Integer>();
knotbank=new Hashtable<Integer,Integer>();
}
private void mark(int m,int player)
{
for(int i=0;i<wins.length;i++)
for(int j=0;j<wins[i].length;j++)
if(wins[i][j]==m)
{
marks[i][j]=1;
if(player==playerid)
marks[i][knotcount]++;
else
marks[i][crosscount]++;
marks[i][totalcount]++;
}
}
private void fixWeights()
{
for(int i=0;i<3;i++)
for(int j=0;j<3;j++)
if(marks[i][j]==1)
if(weights[wins[i][j]-1]!=Integer.MIN_VALUE)
weights[wins[i][j]-1]=Integer.MIN_VALUE;
for(int i=0;i<8;i++)
{
if(marks[i][totalcount]!=2)
continue;
if(marks[i][crosscount]==2)
{
int p=i,q=-1;
if(marks[i][0]==0)
q=0;
else if(marks[i][1]==0)
q=1;
else if(marks[i][2]==0)
q=2;
if(weights[wins[p][q]-1]!=Integer.MIN_VALUE)
{
weights[wins[p][q]-1]=6;
}
}
if(marks[i][knotcount]==2)
{
int p=i,q=-1;
if(marks[i][0]==0)
q=0;
else if(marks[i][1]==0)
q=1;
else if(marks[i][2]==0)
q=2;
if(weights[wins[p][q]-1]!=Integer.MIN_VALUE)
{
weights[wins[p][q]-1]=5;
}
}
}
}
private int compMove()
{
int cell=move();
System.out.println("Computer plays: "+cell);
//weights[cell-1]=Integer.MIN_VALUE;
return cell;
}
private int move()
{
int max=Integer.MIN_VALUE;
int cell=0;
for(int i=0;i<weights.length;i++)
if(weights[i]>max)
{
max=weights[i];
cell=i+1;
}
//This section ensures the computer never loses
//Remove it for a fair match
//Dirty kluge
if(movesPlayer.equals("76")||movesPlayer.equals("67"))
cell=9;
else if(movesPlayer.equals("92")||movesPlayer.equals("29"))
cell=3;
else if (movesPlayer.equals("18")||movesPlayer.equals("81"))
cell=7;
else if(movesPlayer.equals("73")||movesPlayer.equals("37"))
cell=4*((int)(Math.random()*2)+1);
else if(movesPlayer.equals("19")||movesPlayer.equals("91"))
cell=4+2*(int)(Math.pow(-1, (int)(Math.random()*2)));
mark(cell,1);
fixWeights();
crossbank.put(cell, 0);
return cell;
}
private int playerMove()
{
System.out.print("What's your move?: ");
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
int cell=0;
int okay=0;
while(okay==0)
{
try
{
cell=Integer.parseInt(br.readLine());
}
catch(Exception e)
{
System.out.println(e.getMessage());
}
if(cell==7494)
{
override=1;
return -1;
}
if((cell<1||cell>9)||weights[cell-1]==Integer.MIN_VALUE)
System.out.print("Invalid move. Try again:");
else
okay=1;
}
playerMoved(cell);
System.out.println();
return cell;
}
private void playerMoved(int cell)
{
movesPlayer+=cell;
mark(cell,0);
fixWeights();
knotbank.put(cell, 0);
}
private int checkForWin()
{
int crossflag=0,knotflag=0;
for(int i=0;i<wins.length;i++)
{
if(crossbank.containsKey(wins[i][0]))
if(crossbank.containsKey(wins[i][1]))
if(crossbank.containsKey(wins[i][2]))
{
crossflag=1;
break;
}
if(knotbank.containsKey(wins[i][0]))
if(knotbank.containsKey(wins[i][1]))
if(knotbank.containsKey(wins[i][2]))
{
knotflag=1;
break;
}
}
if(knotflag==1)
{
display(grid);
System.out.println("O wins.");
return playerid;
}
else if(crossflag==1)
{
display(grid);
System.out.println("X wins.");
return compid;
}
for(int i=0;i<weights.length;i++)
if(weights[i]!=Integer.MIN_VALUE)
return playingid;
System.out.println("Truce");
return truceid;
}
private void display(char[][] grid)
{
for(int i=0;i<3;i++)
{
System.out.println("\n-------");
System.out.print("|");
for(int j=0;j<3;j++)
System.out.print(grid[i][j]+"|");
}
System.out.println("\n-------");
}
}
Start?(y/n):y
Use a standard numpad as an entry grid, as so:
-------
|7|8|9|
-------
|4|5|6|
-------
|1|2|3|
-------
Begin
-------
| | | |
-------
| | | |
-------
| | | |
-------
What's your move?: 4
-------
| | | |
-------
|o| | |
-------
| | | |
-------
(...)
Computer plays: 7
-------
|x| |o|
-------
|o|x|o|
-------
|x|o|x|
-------
X wins.
Score: Player-0 AI-1
Another?(y/n):n
Game over.
This version uses javax.swing.
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.logging.Logger;
/**
* TicTacToe Application
* @author Steve Robinson
* @version 1.0
*/
class TicTacToeFrame extends JFrame
{
JButton [][] buttons= new JButton[3][3];
JTextField statusBar;
GamePanel panel;
Integer turn;
GameListener listener=new GameListener();
Integer count;
public TicTacToeFrame()
{
setLayout(new BorderLayout());
panel=new GamePanel();
add(panel,BorderLayout.CENTER);
statusBar=new JTextField("Player1's Turn");
statusBar.setEditable(false);
add(statusBar,BorderLayout.SOUTH);
setTitle("Tic Tac Toe!");
setVisible(true);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBounds(400,400,300,300);
}
class GamePanel extends JPanel
{
public GamePanel()
{
setLayout(new GridLayout(3,3));
turn =1;
count=0;
for(int i=0;i<3;i++)
for(int j=0;j<3;j++) {
buttons[i][j]=new JButton();
buttons[i][j].putClientProperty("INDEX", new Integer[]{i,j});
buttons[i][j].putClientProperty("OWNER", null);
buttons[i][j].addActionListener(listener);
add(buttons[i][j]);
}
}
}
class GameListener implements ActionListener
{
public void actionPerformed(ActionEvent e)
{
count++;
JButton b=(JButton)e.getSource();
Integer[]index=(Integer[]) b.getClientProperty("INDEX");
//System.out.println(turn); //turn // //System.out.println("["+index[0]+"]"+"["+index[1]+"]"); //
b.putClientProperty("OWNER", turn);
Icon ico=new ImageIcon(turn.toString()+".gif");
b.setIcon(ico);
b.setEnabled(false);
boolean result=checkVictoryCondition(index);
if(result)
{
JOptionPane.showMessageDialog(null, "Player "+turn.toString()+" Wins");
initComponents();
}
else
{
if(turn==1)
{
turn=2;
statusBar.setText("Player2's Turn");
}
else
{
turn=1;
statusBar.setText("Player1's Turn");
}
}
if(count==9)
{
JOptionPane.showMessageDialog(null, "Match is a draw!");
initComponents();
}
}
Integer getOwner(JButton b)
{
return (Integer)b.getClientProperty("OWNER");
}
//PrintButtonMap for Diagnostics
void printbuttonMap(Integer [][]bMap)
{
for(int i=0;i for(int j=0;j System.out.print(bMap[i][j]+" ");
System.out.println("");
}
}
boolean checkVictoryCondition(Integer [] index)
{
/*Integer[][]buttonMap=new Integer[][] {
{ getOwner(buttons[0][0]),getOwner(buttons[0][1]),getOwner(buttons[0][2])},
{ getOwner(buttons[1][0]),getOwner(buttons[1][1]),getOwner(buttons[1][2])},
{ getOwner(buttons[2][0]),getOwner(buttons[2][1]),getOwner(buttons[2][2])}
};
printbuttonMap(buttonMap); */
Integer a=index[0];
Integer b=index[1];
int i;
//check row
for(i=0;i<3;i++) {
if(getOwner(buttons[a][i])!=getOwner(buttons[a][b]))
break;
}
if(i==3)
return true;
//check column
for(i=0;i<3;i++) {
if(getOwner(buttons[i][b])!=getOwner(buttons[a][b]))
break;
}
if(i==3)
return true;
//check diagonal
if((a==2&&b==2)||(a==0&&b==0)||(a==1&&b==1)||(a==0&&b==2)||(a==2&&b==0))
{
//left diagonal
for(i=0;i if(getOwner(buttons[i][i])!=getOwner(buttons[a][b]))
break;
if(i==3)
return true;
//right diagonal
if((getOwner(buttons[0][2])==getOwner(buttons[a][b]))&&(getOwner(buttons[1][1])==getOwner(buttons[a][b]))&&(getOwner(buttons[2][0])==getOwner(buttons[a][b])))
return true;
}
return false;
}
}
void initComponents()
{
for(int i=0;i<3;i++)
for(int j=0;j<3;j++) {
buttons[i][j].putClientProperty("INDEX", new Integer[]{i,j});
buttons[i][j].putClientProperty("OWNER",null);
buttons[i][j].setIcon(null);
buttons[i][j].setEnabled(true);
turn=1;
count=0;
statusBar.setText("Player1's Turn");
}
}
}
class TicTacToe {
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable(){
public void run()
{
TicTacToeFrame frame=new TicTacToeFrame();
}
});
}
}
Graphical Java Example
import javax.swing.*;
import java.awt.event.*;
import java.awt.*;
//Make sure the name of the class is the same as the .java file name.
//If you change the class name you should change the class object name in runGUI method
public class ticTacToeCallum implements ActionListener {
static JFrame frame;
static JPanel contentPane;
static JLabel lblEnterFirstPlayerName, lblEnterSecondPlayerName, lblFirstPlayerScore, lblSecondPlayerScore;
static JButton btnButton1, btnButton2, btnButton3, btnButton4, btnButton5, btnButton6, btnButton7, btnButton8, btnButton9, btnClearBoard, btnClearAll, btnCloseGame;
static JTextField txtEnterFirstPlayerName, txtEnterSecondPlayerName;
static Icon imgicon = new ImageIcon("saveIcon.JPG");
Font buttonFont = new Font("Arial", Font.PLAIN, 20);
//to adjust the frame size change the values in pixels
static int width = 600;
static int length = 400;
static int firstPlayerScore = 0;
static int secondPlayerScore = 0;
static int playerTurn = 1;
static int roundComplete = 0;
static int button1 = 1, button2 = 1, button3 = 1, button4 = 1, button5 = 1, button6 = 1, button7 = 1, button8 = 1, button9 = 1; // 1 is true, 0 is false
public ticTacToeCallum(){
frame = new JFrame("Tic Tac Toe ^_^");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
contentPane = new JPanel();
contentPane.setLayout(new GridLayout(6, 3, 10, 10));
contentPane.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
btnButton1 = new JButton("");
btnButton1.setFont(buttonFont);
btnButton1.setAlignmentX(JButton.CENTER_ALIGNMENT);
btnButton1.setIcon(imgicon);
btnButton1.setActionCommand("CLICK1");
btnButton1.addActionListener(this);
contentPane.add(btnButton1);
btnButton2 = new JButton("");
btnButton2.setFont(buttonFont);
btnButton2.setAlignmentX(JButton.CENTER_ALIGNMENT);
btnButton2.setIcon(imgicon);
btnButton2.setActionCommand("CLICK2");
btnButton2.addActionListener(this);
contentPane.add(btnButton2);
btnButton3 = new JButton("");
btnButton3.setFont(buttonFont);
btnButton3.setAlignmentX(JButton.CENTER_ALIGNMENT);
btnButton3.setIcon(imgicon);
btnButton3.setActionCommand("CLICK3");
btnButton3.addActionListener(this);
contentPane.add(btnButton3);
btnButton4 = new JButton("");
btnButton4.setFont(buttonFont);
btnButton4.setAlignmentX(JButton.CENTER_ALIGNMENT);
btnButton4.setIcon(imgicon);
btnButton4.setActionCommand("CLICK4");
btnButton4.addActionListener(this);
contentPane.add(btnButton4);
btnButton5 = new JButton("");
btnButton5.setFont(buttonFont);
btnButton5.setAlignmentX(JButton.CENTER_ALIGNMENT);
btnButton5.setIcon(imgicon);
btnButton5.setActionCommand("CLICK5");
btnButton5.addActionListener(this);
contentPane.add(btnButton5);
btnButton6 = new JButton("");
btnButton6.setFont(buttonFont);
btnButton6.setAlignmentX(JButton.CENTER_ALIGNMENT);
btnButton6.setIcon(imgicon);
btnButton6.setActionCommand("CLICK6");
btnButton6.addActionListener(this);
contentPane.add(btnButton6);
btnButton7 = new JButton("");
btnButton7.setFont(buttonFont);
btnButton7.setAlignmentX(JButton.CENTER_ALIGNMENT);
btnButton7.setIcon(imgicon);
btnButton7.setActionCommand("CLICK7");
btnButton7.addActionListener(this);
contentPane.add(btnButton7);
btnButton8 = new JButton("");
btnButton8.setFont(buttonFont);
btnButton8.setAlignmentX(JButton.CENTER_ALIGNMENT);
btnButton8.setIcon(imgicon);
btnButton8.setActionCommand("CLICK8");
btnButton8.addActionListener(this);
contentPane.add(btnButton8);
btnButton9 = new JButton("");
btnButton9.setFont(buttonFont);
btnButton9.setAlignmentX(JButton.CENTER_ALIGNMENT);
btnButton9.setIcon(imgicon);
btnButton9.setActionCommand("CLICK9");
btnButton9.addActionListener(this);
contentPane.add(btnButton9);
lblEnterFirstPlayerName = new JLabel("Enter First Player's Name");
contentPane.add(lblEnterFirstPlayerName);
txtEnterFirstPlayerName = new JTextField("");
contentPane.add(txtEnterFirstPlayerName);
lblFirstPlayerScore = new JLabel("Score: " + firstPlayerScore);
contentPane.add(lblFirstPlayerScore);
lblEnterSecondPlayerName = new JLabel("Enter Second Player's Name");
contentPane.add(lblEnterSecondPlayerName);
txtEnterSecondPlayerName = new JTextField("");
contentPane.add(txtEnterSecondPlayerName);
lblSecondPlayerScore = new JLabel("Score: " + secondPlayerScore);
contentPane.add(lblSecondPlayerScore);
btnClearBoard = new JButton("Clear Board");
btnClearBoard.setAlignmentX(JButton.CENTER_ALIGNMENT);
btnClearBoard.setIcon(imgicon);
btnClearBoard.setActionCommand("CLICKClearBoard");
btnClearBoard.addActionListener(this);
contentPane.add(btnClearBoard);
btnClearAll = new JButton("Clear All");
btnClearAll.setAlignmentX(JButton.CENTER_ALIGNMENT);
btnClearAll.setIcon(imgicon);
btnClearAll.setActionCommand("CLICKClearAll");
btnClearAll.addActionListener(this);
contentPane.add(btnClearAll);
btnCloseGame = new JButton("Close Game");
btnCloseGame.setAlignmentX(JButton.CENTER_ALIGNMENT);
btnCloseGame.setIcon(imgicon);
btnCloseGame.setActionCommand("CLICKCloseGame");
btnCloseGame.addActionListener(this);
contentPane.add(btnCloseGame);
frame.setContentPane(contentPane);
frame.pack();
frame.setSize(width,length);
frame.setVisible(true);
}
public void actionPerformed(ActionEvent event) {
String eventName = event.getActionCommand();
if (eventName.equals("CLICK1")) {
if (button1 == 1){
if (playerTurn == 1){
btnButton1.setForeground(Color.RED);
btnButton1.setText("X");
playerTurn = 2;
button1 = 0;
} else if (playerTurn == 2) {
btnButton1.setForeground(Color.GREEN);
btnButton1.setText("O");
playerTurn = 1;
button1 = 0;
}
}
} else if (eventName.equals ("CLICK2")) {
if (button2 == 1){
if (playerTurn == 1){
btnButton2.setForeground(Color.RED);
btnButton2.setText("X");
playerTurn = 2;
button2 = 0;
} else if (playerTurn == 2) {
btnButton2.setForeground(Color.GREEN);
btnButton2.setText("O");
playerTurn = 1;
button2 = 0;
}
}
} else if (eventName.equals ("CLICK3")) {
if (button3 == 1){
if (playerTurn == 1){
btnButton3.setForeground(Color.RED);
btnButton3.setText("X");
playerTurn = 2;
button3 = 0;
} else if (playerTurn == 2) {
btnButton3.setForeground(Color.GREEN);
btnButton3.setText("O");
playerTurn = 1;
button3 = 0;
}
}
} else if (eventName.equals ("CLICK4")) {
if (button4 == 1){
if (playerTurn == 1){
btnButton4.setForeground(Color.RED);
btnButton4.setText("X");
playerTurn = 2;
button4 = 0;
} else if (playerTurn == 2) {
btnButton4.setForeground(Color.GREEN);
btnButton4.setText("O");
playerTurn = 1;
button4 = 0;
}
}
} else if (eventName.equals ("CLICK5")) {
if (button5 == 1){
if (playerTurn == 1){
btnButton5.setForeground(Color.RED);
btnButton5.setText("X");
playerTurn = 2;
button5 = 0;
} else if (playerTurn == 2) {
btnButton5.setForeground(Color.GREEN);
btnButton5.setText("O");
playerTurn = 1;
button5 = 0;
}
}
} else if (eventName.equals ("CLICK6")) {
if (button6 == 1){
if (playerTurn == 1){
btnButton6.setForeground(Color.RED);
btnButton6.setText("X");
playerTurn = 2;
button6 = 0;
} else if (playerTurn == 2) {
btnButton6.setForeground(Color.GREEN);
btnButton6.setText("O");
playerTurn = 1;
button6 = 0;
}
}
} else if (eventName.equals ("CLICK7")) {
if (button7 == 1){
if (playerTurn == 1){
btnButton7.setForeground(Color.RED);
btnButton7.setText("X");
playerTurn = 2;
button7 = 0;
} else if (playerTurn == 2) {
btnButton7.setForeground(Color.GREEN);
btnButton7.setText("O");
playerTurn = 1;
button7 = 0;
}
}
} else if (eventName.equals ("CLICK8")) {
if (button8 == 1){
if (playerTurn == 1){
btnButton8.setForeground(Color.RED);
btnButton8.setText("X");
playerTurn = 2;
button8 = 0;
} else if (playerTurn == 2) {
btnButton8.setForeground(Color.GREEN);
btnButton8.setText("O");
playerTurn = 1;
button8 = 0;
}
}
} else if (eventName.equals ("CLICK9")) {
if (button9 == 1){
if (playerTurn == 1){
btnButton9.setForeground(Color.RED);
btnButton9.setText("X");
playerTurn = 2;
button9 = 0;
} else if (playerTurn == 2) {
btnButton9.setForeground(Color.GREEN);
btnButton9.setText("O");
playerTurn = 1;
button9 = 0;
}
}
} else if (eventName.equals ("CLICKClearBoard")) {
btnButton1.setText("");
btnButton2.setText("");
btnButton3.setText("");
btnButton4.setText("");
btnButton5.setText("");
btnButton6.setText("");
btnButton7.setText("");
btnButton8.setText("");
btnButton9.setText("");
button1 = 1;
button2 = 1;
button3 = 1;
button4 = 1;
button5 = 1;
button6 = 1;
button7 = 1;
button8 = 1;
button9 = 1;
playerTurn = 1;
roundComplete = 0;
} else if (eventName.equals ("CLICKClearAll")) {
btnButton1.setText("");
btnButton2.setText("");
btnButton3.setText("");
btnButton4.setText("");
btnButton5.setText("");
btnButton6.setText("");
btnButton7.setText("");
btnButton8.setText("");
btnButton9.setText("");
firstPlayerScore = 0;
lblFirstPlayerScore.setText("Score: " + firstPlayerScore);
secondPlayerScore = 0;
lblSecondPlayerScore.setText("Score: " + secondPlayerScore);
txtEnterFirstPlayerName.setText("");
txtEnterSecondPlayerName.setText("");
button1 = 1;
button2 = 1;
button3 = 1;
button4 = 1;
button5 = 1;
button6 = 1;
button7 = 1;
button8 = 1;
button9 = 1;
playerTurn = 1;
roundComplete = 0;
} else if (eventName.equals ("CLICKCloseGame")) {
System.exit(0);
}
score();
}
public static void score(){
if (roundComplete == 0){
if (btnButton1.getText().equals(btnButton2.getText()) && btnButton1.getText().equals(btnButton3.getText())){
if (btnButton1.getText().equals("X")){
firstPlayerScore += 1;
lblFirstPlayerScore.setText("Score: " + firstPlayerScore);
roundComplete = 1;
} else if (btnButton1.getText().equals("O")){
secondPlayerScore += 1;
lblSecondPlayerScore.setText("Score: " + secondPlayerScore);
roundComplete = 1;
}
}
if (btnButton1.getText().equals(btnButton4.getText()) && btnButton1.getText().equals(btnButton7.getText())){
if (btnButton1.getText().equals("X")){
firstPlayerScore += 1;
lblFirstPlayerScore.setText("Score: " + firstPlayerScore);
roundComplete = 1;
} else if (btnButton1.getText().equals("O")){
secondPlayerScore += 1;
lblSecondPlayerScore.setText("Score: " + secondPlayerScore);
roundComplete = 1;
}
}
if (btnButton1.getText().equals(btnButton5.getText()) && btnButton1.getText().equals(btnButton9.getText())){
if (btnButton1.getText().equals("X")){
firstPlayerScore += 1;
lblFirstPlayerScore.setText("Score: " + firstPlayerScore);
roundComplete = 1;
} else if (btnButton1.getText().equals("O")){
secondPlayerScore += 1;
lblSecondPlayerScore.setText("Score: " + secondPlayerScore);
roundComplete = 1;
}
}
if (btnButton7.getText().equals(btnButton8.getText()) && btnButton7.getText().equals(btnButton9.getText())){
if (btnButton7.getText().equals("X")){
firstPlayerScore += 1;
lblFirstPlayerScore.setText("Score: " + firstPlayerScore);
roundComplete = 1;
} else if (btnButton7.getText().equals("O")){
secondPlayerScore += 1;
lblSecondPlayerScore.setText("Score: " + secondPlayerScore);
roundComplete = 1;
}
}
if (btnButton7.getText().equals(btnButton5.getText()) && btnButton7.getText().equals(btnButton3.getText())){
if (btnButton7.getText().equals("X")){
firstPlayerScore += 1;
lblFirstPlayerScore.setText("Score: " + firstPlayerScore);
roundComplete = 1;
} else if (btnButton7.getText().equals("O")){
secondPlayerScore += 1;
lblSecondPlayerScore.setText("Score: " + secondPlayerScore);
roundComplete = 1;
}
}
if (btnButton3.getText().equals(btnButton6.getText()) && btnButton3.getText().equals(btnButton9.getText())){
if (btnButton3.getText().equals("X")){
firstPlayerScore += 1;
lblFirstPlayerScore.setText("Score: " + firstPlayerScore);
roundComplete = 1;
} else if (btnButton3.getText().equals("O")){
secondPlayerScore += 1;
lblSecondPlayerScore.setText("Score: " + secondPlayerScore);
roundComplete = 1;
}
}
if (btnButton4.getText().equals(btnButton5.getText()) && btnButton4.getText().equals(btnButton6.getText())){
if (btnButton4.getText().equals("X")){
firstPlayerScore += 1;
lblFirstPlayerScore.setText("Score: " + firstPlayerScore);
roundComplete = 1;
} else if (btnButton4.getText().equals("O")){
secondPlayerScore += 1;
lblSecondPlayerScore.setText("Score: " + secondPlayerScore);
roundComplete = 1;
}
}
if (btnButton2.getText().equals(btnButton5.getText()) && btnButton2.getText().equals(btnButton8.getText())){
if (btnButton2.getText().equals("X")){
firstPlayerScore += 1;
lblFirstPlayerScore.setText("Score: " + firstPlayerScore);
roundComplete = 1;
} else if (btnButton2.getText().equals("O")){
secondPlayerScore += 1;
lblSecondPlayerScore.setText("Score: " + secondPlayerScore);
roundComplete = 1;
}
}
}
if (roundComplete == 1){
button1 = 0;
button2 = 0;
button3 = 0;
button4 = 0;
button5 = 0;
button6 = 0;
button7 = 0;
button8 = 0;
button9 = 0;
}
}
/**
* Create and show the GUI.
*/
private static void runGUI() {
ticTacToeCallum greeting = new ticTacToeCallum();
}
//Do not change this method
public static void main(String[] args) {
/* Methods that create and show a GUI should be run from an event-dispatching thread */
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
runGUI();
}
});
}
}
Javascript
HTML5 Canvas implementation. Should play perfectly or near-perfectly.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>TicTacToe</title>
</head>
<body>
<canvas id="canvas" width="400" height="400"></canvas>
<script>
//All helper functions
isBetween = (num, a, b) => {
return num >= a && num <= b;
}
randInt = (low, high) => {
return Math.floor(Math.random() * (high - low + 1)) + low;
}
choice = arr => {
return arr[randInt(0, arr.length - 1)];
}
//Checks if every value in an array equals an item
equals = (arr, item) => {
return arr.filter(a => {
return a === item;
}).length === arr.length;
}
//Returns number of items in array that equal an item
equallen = (arr, item) => {
return arr.filter(a => {
return a === item;
}).length
}
//Checks if any value in the array equals an item
equalanyof = (arr, item) => {
return equallen(arr, item) > 0;
}
//Should be scalable, but it uses default elements for calculations and tracking
let canvas = document.getElementById("canvas");
let ctx = canvas.getContext("2d");
const width = canvas.width;
const blockSize = canvas.width / 3;
const lineSize = blockSize / 5;
//Draws background
ctx.fillStyle = "rgb(225, 225, 225)";
ctx.fillRect(0, 0, 400, 400);
//Title page
ctx.fillStyle = "rgb(0, 0, 0)";
ctx.font = width / (250 / 17) + "px Arial"; //34
ctx.textAlign = "center";
ctx.fillText("Tic Tac Toe", width / 2, width / (2 + 2 / 3)); //200, 150
//Button for starting
ctx.fillStyle = "rgb(200, 200, 200)";
ctx.fillRect(width / 3.2, width / 2, width / (2 + 2 / 3), width / 8); //125, 200, 150, 50
ctx.fillStyle = "rgb(0, 0, 0)";
ctx.font = width / (200 / 9) + "px Arial"; //18
ctx.fillText("Start", width / 2, width / (40 / 23)); //200, 230
//Uses an array so a forEach loop can scan it for the correct tile
let tileArray = []; //Contains all tiles
let available = []; //Contains only available tiles
class Tile {
constructor(x, y) {
this.x = x * blockSize;
this.y = y * blockSize;
this.state = "none";
tileArray.push(this);
available.push(this);
}
draw() {
ctx.strokeStyle = "rgb(175, 175, 175)";
ctx.lineWidth = blockSize / 10;
if (this.state === "X") {
ctx.beginPath();
ctx.moveTo(this.x + blockSize / 4, this.y + blockSize / 4);
ctx.lineTo(this.x + blockSize / (4 / 3), this.y + blockSize / (4 / 3));
ctx.moveTo(this.x + blockSize / 4, this.y + blockSize / (4 / 3));
ctx.lineTo(this.x + blockSize / (4 / 3), this.y + blockSize / 4);
ctx.stroke();
} else if (this.state === "O") {
ctx.beginPath();
ctx.arc(this.x + blockSize / 2, this.y + blockSize / 2, blockSize / 4, 0, 2 * Math.PI);
ctx.stroke();
}
//Removes this from the available array
const ind = available.indexOf(this);
available = available.slice(0, ind).concat(available.slice(ind + 1, available.length));
}
}
//Defines the game
let game = {
state: "start",
turn: "Player",
player: "X",
opp: "O"
}
//Generates tiles
for (let x = 0; x < 3; x++) {
for (let y = 0; y < 3; y++) {
new Tile(x, y);
}
}
//Gets the mouse position
getMousePos = evt => {
let rect = canvas.getBoundingClientRect();
return {
x: evt.clientX - rect.left,
y: evt.clientY - rect.top
}
}
//Checks for win conditions
checkCondition = () => {
//Local variables are created to make access easier
let as = tileArray[0].state;
let bs = tileArray[1].state;
let cs = tileArray[2].state;
let ds = tileArray[3].state;
let es = tileArray[4].state;
let fs = tileArray[5].state;
let gs = tileArray[6].state;
let hs = tileArray[7].state;
let is = tileArray[8].state;
//Equals function checks if each value in the array has a state of X or O
if (equals([as, bs, cs], "X") || equals([ds, es, fs], "X") || equals([gs, hs, is], "X") ||
equals([as, ds, gs], "X") || equals([bs, es, hs], "X") || equals([cs, fs, is], "X") ||
equals([as, es, is], "X") || equals([cs, es, gs], "X")) {
alert("Player wins!");
game.state = "over";
} else if (equals([as, bs, cs], "O") || equals([ds, es, fs], "O") || equals([gs, hs, is], "O") ||
equals([as, ds, gs], "O") || equals([bs, es, hs], "O") || equals([cs, fs, is], "O") ||
equals([as, es, is], "O") || equals([cs, es, gs], "O")) {
alert("Opponent wins!");
game.state = "over";
//It is a tie if none of the above conditions are fulfilled and there are no available tiles
} else if (available.length === 0) {
alert("It's a tie!");
game.state = "over";
}
}
//Controls the opponent. Uses many nested switches/if-else for efficiency
oppTurn = () => {
if (game.state === "game") {
let tile = 0;
//Similar local variables as the win checker
let at = tileArray[0].state;
let bt = tileArray[1].state;
let ct = tileArray[2].state;
let dt = tileArray[3].state;
let et = tileArray[4].state;
let ft = tileArray[5].state;
let gt = tileArray[6].state;
let ht = tileArray[7].state;
let it = tileArray[8].state;
let all = [at, bt, ct, dt, et, ft, gt, ht, it];
/*The AI will automatically win if possible
I considered using a filter based system, but it was ugly and
inelegant, and also redundant
I used a nested if-else instead
Equallen checks how many values in the array equal the given value*/
if (equallen(all, "O") >= 2) {
if (equallen([at, bt, ct], "O") === 2 && equallen([at, bt, ct], "X") === 0) {
if (at === "none") {
tile = tileArray[0];
} else if (bt === "none") {
tile = tileArray[1];
} else if (ct === "none") {
tile = tileArray[2];
}
} else if (equallen([dt, et, ft], "O") === 2 && equallen([dt, et, ft], "X") === 0) {
if (dt === "none") {
tile = tileArray[3];
} else if (et === "none") {
tile = tileArray[4];
} else if (ft === "none") {
tile = tileArray[5];
}
} else if (equallen([gt, ht, it], "O") === 2 && equallen([gt, ht, it], "X") === 0) {
if (gt === "none") {
tile = tileArray[6];
} else if (ht === "none") {
tile = tileArray[7];
} else if (it === "none") {
tile = tileArray[8];
}
} else if (equallen([at, dt, gt], "O") === 2 && equallen([at, dt, gt], "X") === 0) {
if (at === "none") {
tile = tileArray[0];
} else if (dt === "none") {
tile = tileArray[3];
} else if (gt === "none") {
tile = tileArray[6];
}
} else if (equallen([bt, et, ht], "O") === 2 && equallen([bt, et, ht], "X") === 0) {
if (bt === "none") {
tile = tileArray[1];
} else if (et === "none") {
tile = tileArray[4];
} else if (ht === "none") {
tile = tileArray[7];
}
} else if (equallen([ct, ft, it], "O") === 2 && equallen([ct, ft, it], "X") === 0) {
if (ct === "none") {
tile = tileArray[2];
} else if (ft === "none") {
tile = tileArray[5];
} else if (it === "none") {
tile = tileArray[8];
}
} else if (equallen([at, et, it], "O") === 2 && equallen([at, et, it], "X") === 0) {
if (at === "none") {
tile = tileArray[0];
} else if (et === "none") {
tile = tileArray[4];
} else if (it === "none") {
tile = tileArray[8];
}
} else if (equallen([ct, et, gt], "O") === 2 && equallen([ct, et, gt], "X") === 0) {
if (ct === "none") {
tile = tileArray[2];
} else if (et === "none") {
tile = tileArray[4];
} else if (gt === "none") {
tile = tileArray[6];
}
}
}
//Stops player from winning if possible
if (equallen(all, "X") >= 2 && tile === 0) {
if (equallen([at, bt, ct], "X") === 2 && equallen([at, bt, ct], "O") === 0) {
if (at === "none") {
tile = tileArray[0];
} else if (bt === "none") {
tile = tileArray[1];
} else if (ct === "none") {
tile = tileArray[2];
}
} else if (equallen([dt, et, ft], "X") === 2 && equallen([dt, et, ft], "O") === 0) {
if (dt === "none") {
tile = tileArray[3];
} else if (et === "none") {
tile = tileArray[4];
} else if (ft === "none") {
tile = tileArray[5];
}
} else if (equallen([gt, ht, it], "X") === 2 && equallen([gt, ht, it], "O") === 0) {
if (gt === "none") {
tile = tileArray[6];
} else if (ht === "none") {
tile = tileArray[7];
} else if (it === "none") {
tile = tileArray[8];
}
} else if (equallen([at, dt, gt], "X") === 2 && equallen([at, dt, gt], "O") === 0) {
if (at === "none") {
tile = tileArray[0];
} else if (dt === "none") {
tile = tileArray[3];
} else if (gt === "none") {
tile = tileArray[6];
}
} else if (equallen([bt, et, ht], "X") === 2 && equallen([bt, et, ht], "O") === 0) {
if (bt === "none") {
tile = tileArray[1];
} else if (et === "none") {
tile = tileArray[4];
} else if (ht === "none") {
tile = tileArray[7];
}
} else if (equallen([ct, ft, it], "X") === 2 && equallen([ct, ft, it], "O") === 0) {
if (ct === "none") {
tile = tileArray[2];
} else if (ft === "none") {
tile = tileArray[5];
} else if (it === "none") {
tile = tileArray[8];
}
} else if (equallen([at, et, it], "X") === 2 && equallen([at, et, it], "O") === 0) {
if (at === "none") {
tile = tileArray[0];
} else if (et === "none") {
tile = tileArray[4];
} else if (it === "none") {
tile = tileArray[8];
}
} else if (equallen([ct, et, gt], "X") === 2 && equallen([ct, et, gt], "O") === 0) {
if (ct === "none") {
tile = tileArray[2];
} else if (et === "none") {
tile = tileArray[4];
} else if (gt === "none") {
tile = tileArray[6];
}
}
}
//Other options in case the above are not fulfilled
//Controls the course of play over the game
if (tile === 0) {
switch (9 - available.length) {
case 1:
//If the center is taken, it plays randomly in the corner
//Otherwise, it takes the center
if (et === "X") {
tile = tileArray[choice([0, 2, 6, 8])];
} else {
tile = tileArray[4];
}
break;
case 3:
if (et === "X" && (equalanyof([at, ct, gt, it], "O"))) {
/*To counter the strategy of
O - -
- X -
X - -
O - -
- X -
- - X
and related strategies*/
if (at === "X") {
if (it === "none") {
tile = tileArray[8];
} else {
tile = tileArray[choice([2, 6])];
}
} else if (ct === "X") {
if (gt === "none") {
tile = tileArray[6];
} else {
tile = tileArray[choice([0, 8])];
}
} else if (gt === "X") {
if (ct === "none") {
tile = tileArray[2];
} else {
tile = tileArray[choice([0, 8])];
}
} else if (it === "X") {
if (at === "none") {
tile = tileArray[0];
} else {
tile = tileArray[choice([2, 6])];
}
}
} else {
tile = choice(tileArray);
}
break;
}
}
//Generates a random number if it could cause an error
if (tile.state != "none") {
tile = choice(available);
}
//Draws the selection
tile.state = game.opp;
tile.draw();
checkCondition();
game.turn = "Player";
}
}
//Click handler
document.onclick = event => {
let pos = getMousePos(event);
switch (game.state) {
case "start":
//Checks if the button was clicked
if (isBetween(pos.x, width / 3.2, width / (16 / 11)) && isBetween(pos.y, width / 2, width / 1.6)) {
game.state = "game"
//Draws the setup for the game
ctx.fillStyle = "rgb(225, 225, 225)";
ctx.fillRect(0, 0, 400, 400);
//Draws the lines
ctx.fillStyle = "rgb(200, 200, 200)";
ctx.fillRect(blockSize - lineSize / 2, 0, lineSize, width);
ctx.fillRect(blockSize * 2 - lineSize / 2, 0, lineSize, width);
ctx.fillRect(0, blockSize - lineSize / 2, width, lineSize);
ctx.fillRect(0, blockSize * 2 - lineSize / 2, width, lineSize);
}
break;
case "game":
if (game.turn === "Player") {
//Goes through the tile array, checking if the click occurred there
tileArray.forEach(tile => {
if (isBetween(pos.x, tile.x, tile.x + blockSize) && isBetween(pos.y, tile.y, tile.y + blockSize)) {
if (available.indexOf(tile) != -1) {
tile.state = game.player;
tile.draw();
checkCondition();
game.turn = "Opponent";
oppTurn();
}
}
});
}
break;
}
}
</script>
</body>
</html>
A Node.js implementation using strategy heuristics as defined in the Wikipedia page linked above. Some of the steps can be embargoed until the board only has n plays left. This makes for a bit of randomness in the gameplay. Human is X and goes first.
// Board
const topLeft = 1;
const topMid = 2;
const topRight = 3;
const midLeft = 4;
const center = 5;
const midRight = 6;
const botLeft = 7;
const botMid = 8;
const botRight = 9;
const tiles = [
topLeft, topMid, topRight,
midLeft, center, midRight,
botLeft, botMid, botRight
];
const corners = [
topLeft, topRight,
botLeft, botRight
];
const sides = [
topMid,
midLeft, midRight,
botMid
];
const winningCombos = [
[topLeft, topMid, topRight],
[midLeft, center, midRight],
[botLeft, botMid, botRight],
[topLeft, midLeft, botLeft],
[topMid, center, botMid],
[topRight, midRight, botRight],
[topLeft, center, botRight],
[topRight, center, botLeft],
];
const board = new Map();
// Utility
const reset = () => tiles.forEach(e => board.set(e, ' '));
const repeat = (s, n) => Array(n).fill(s).join('');
const fromBoard = e => board.get(e);
const notSpace = e => e !== ' ';
const occupied = e => notSpace(fromBoard(e));
const isAvailable = e => !occupied(e);
const notString = s => e => fromBoard(e) !== s;
const containsOnly = s => a => a.filter(occupied).map(fromBoard).join('') === s;
const chooseRandom = a => a[Math.floor(Math.random() * a.length)];
const legalPlays = () => tiles.filter(isAvailable);
const legalCorners = () => corners.filter(isAvailable);
const legalSides = () => sides.filter(isAvailable);
const opponent = s => s === 'X' ? 'O' : 'X';
const hasElements = a => a.length > 0;
const compose = (...fns) => (...x) => fns.reduce((a, b) => c => a(b(c)))(...x);
const isDef = t => t !== undefined;
const flatten = a => a.reduce((p, c) => [...p, ...c], []);
const findShared = a => [...flatten(a).reduce((p, c) =>
p.has(c) ? p.set(c, [...p.get(c), c]) : p.set(c, [c]),
new Map()).values()].filter(e => e.length > 1).map(e => e[0]);
const wrap = (f, s, p = 9) => n => {
if (isDef(n) || legalPlays().length > p) {
return n;
}
const r = f(n);
if (isDef(r)) {
console.log(`${s}: ${r}`);
}
return r;
};
const drawBoard = () => console.log(`
${[fromBoard(topLeft), fromBoard(topMid), fromBoard(topRight)].join('|')}
-+-+-
${[fromBoard(midLeft), fromBoard(center), fromBoard(midRight)].join('|')}
-+-+-
${[fromBoard(botLeft), fromBoard(botMid), fromBoard(botRight)].join('|')}
`);
const win = s => () => {
if (winningCombos.find(containsOnly(repeat(s, 3)))) {
console.log(`${s} wins!`);
reset()
} else if (hasElements(legalPlays())) {
console.log(`${opponent(s)}s turn:`);
} else {
console.log('Draw!');
reset();
}
};
const play = s => n => occupied(n) ? console.log('Illegal') : board.set(n, s);
// Available strategy steps
const attack = (s, t = 2) => () => {
const m = winningCombos.filter(containsOnly(repeat(s, t)));
if (hasElements(m)) {
return chooseRandom(chooseRandom(m).filter(notString(s)))
}
};
const fork = (s, isDefence = false) => () => {
let result;
const p = winningCombos.filter(containsOnly(s));
const forks = findShared(p).filter(isAvailable);
// On defence, when there is only one fork, play it, else choose a
// two-in-a row attack to force the opponent to not execute the fork.
if (forks.length > 1 && isDefence) {
const me = opponent(s);
const twoInRowCombos = winningCombos.filter(containsOnly(repeat(me, 1)));
const chooseFrom = twoInRowCombos.reduce((p, a) => {
const avail = a.filter(isAvailable);
avail.forEach((e, i) => {
board.set(e, me).set(i ? avail[i - 1] : avail[i + 1], opponent(me));
winningCombos.filter(containsOnly(repeat(s, 2))).length < 2
? p.push(e)
: undefined;
});
avail.forEach(e => board.set(e, ' '));
return p;
}, []);
result = hasElements(chooseFrom)
? chooseRandom(chooseFrom)
: attack(opponent(s), 1)()
}
return result || chooseRandom(forks);
};
const defend = (s, t = 2) => attack(opponent(s), t);
const defendFork = s => fork(opponent(s), true);
const chooseCenter = () => isAvailable(center) ? center : undefined;
const chooseCorner = () => chooseRandom(legalCorners());
const chooseSide = () => chooseRandom(legalSides());
const randLegal = () => chooseRandom(legalPlays());
// Implemented strategy
const playToWin = s => compose(
win(s),
drawBoard,
play(s),
wrap(randLegal, 'Chose random'),
wrap(chooseSide, 'Chose random side', 8),
wrap(chooseCorner, 'Chose random corner', 8),
wrap(chooseCenter, 'Chose center', 7),
wrap(defendFork(s), 'Defended fork'),
wrap(fork(s), 'Forked'),
wrap(defend(s), 'Defended'),
wrap(attack(s), 'Attacked')
);
// Prep players
const O = n => playToWin('O')(n);
const X = n => playToWin('X')(n);
// Begin
reset();
console.log("Let's begin...");
drawBoard();
console.log('X Begins: Enter a number from 1 - 9');
// Manage user input.
const standard_input = process.stdin;
const overLog = s => {
process.stdout.moveCursor(0, -9);
process.stdout.cursorTo(0);
process.stdout.clearScreenDown();
process.stdout.write(s);
};
standard_input.setEncoding('utf-8');
standard_input.on('data', (data) => {
if (data === '\n') {
overLog('O: ');
O();
} else {
overLog(`X: Plays ${data}`);
X(Number(data));
}
});
{{out}}
Let's begin...
X: Plays 1
O: Chose random corner: 7
X: Plays 5
O: Defended: 9
X: Plays 8
O: Defended: 2
X: Plays 3
O: Chose random side: 6
X: Plays 4
X|O|X
-+-+-
X|X|O
-+-+-
O|X|O
Draw!
Julia
One move look-ahead algorithm. Computer plays to win or at least draw.
const winningpositions = [[1, 2, 3], [4, 5, 6], [7, 8, 9], [1, 4, 7],
[2, 5, 8], [3, 6, 9],[1, 5, 9], [7, 5, 3]]
function haswon(brd, xoro)
marked = findall(x -> x == xoro, brd)
for pos in winningpositions
if length(pos) <= length(marked) && pos == sort(marked)[1:3]
return true
end
end
false
end
function readcharwithprompt(prompt, expected)
ret = '*'
while !(ret in expected)
print("\n", prompt, " -> ")
ret = lowercase(chomp(readline()))[1]
end
ret
end
availablemoves(brd) = findall(x -> x == ' ', brd)
cornersopen(brd) = [x for x in [1, 3, 7, 9] if brd[x] == ' ']
int2char(x) = Char(x + UInt8('0'))
char2int(x) = UInt8(x) - UInt8('0')
getyn(query) = readcharwithprompt(query, ['y', 'n'])
gettheirmove(brd) = char2int(readcharwithprompt("Your move(1-9)", int2char.(availablemoves(brd))))
function findwin(brd, xoro)
tmpbrd = deepcopy(brd)
for mv in availablemoves(tmpbrd)
tmpbrd[mv] = xoro
if haswon(tmpbrd, xoro)
return mv
end
tmpbrd[mv] = ' '
end
return nothing
end
function choosemove(brd, mychar, theirchar)
if all(x -> x == ' ', brd)
brd[rand(cornersopen(brd))] = mychar # corner trap if starting game
elseif availablemoves(brd) == [] # no more moves
println("Game is over. It was a draw.")
exit(0)
elseif (x = findwin(brd, mychar)) != nothing || (x = findwin(brd, theirchar)) != nothing
brd[x] = mychar # win if possible, block their win otherwise if their win is possible
elseif brd[5] == ' '
brd[5] = mychar # take center if open and not doing corner trap
elseif (corners = cornersopen(brd)) != []
brd[rand(corners)] = mychar # choose a corner over a side middle move
else
brd[rand(availablemoves(brd))] = mychar # random otherwise
end
end
function display(brd)
println("+-----------+")
println("| ", brd[1], " | ", brd[2], " | ", brd[3], " |")
println("| ", brd[4], " | ", brd[5], " | ", brd[6], " |")
println("| ", brd[7], " | ", brd[8], " | ", brd[9], " |")
println("+-----------+")
end
function tictactoe()
board = fill(' ', 9)
println("Board move grid:\n 1 2 3\n 4 5 6\n 7 8 9")
yn = getyn("Would you like to move first (y/n)?")
if yn == 'y'
mychar = 'O'
theirchar = 'X'
board[gettheirmove(board)] = theirchar
else
mychar = 'X'
theirchar = 'O'
end
while true
choosemove(board, mychar, theirchar)
println("Computer has moved.")
display(board)
if haswon(board, mychar)
println("Game over. Computer wins!")
exit(0)
elseif availablemoves(board) == []
break
end
board[gettheirmove(board)] = theirchar
println("Player has moved.")
display(board)
if haswon(board, theirchar)
println("Game over. Player wins!")
exit(0)
elseif availablemoves(board) == []
break
end
end
println("Game over. It was a draw.")
end
tictactoe()
{{output}}
Board move grid:
1 2 3
4 5 6
7 8 9
Would you like to move first (y/n)? -> y
Your move(1-9) -> 5
Computer has moved.
+-----------+
| | | |
| | X | |
| O | | |
+-----------+
Your move(1-9) -> 1
Player has moved.
+-----------+
| X | | |
| | X | |
| O | | |
+-----------+
Computer has moved.
+-----------+
| X | | |
| | X | |
| O | | O |
+-----------+
Your move(1-9) -> 3
Player has moved.
+-----------+
| X | | X |
| | X | |
| O | | O |
+-----------+
Computer has moved.
+-----------+
| X | | X |
| | X | |
| O | O | O |
+-----------+
Game over. Computer wins!
Kotlin
{{trans|C}}
// version 1.1.51
import java.util.Random
val r = Random()
val b = Array(3) { IntArray(3) } // board -> 0: blank; -1: computer; 1: human
var bestI = 0
var bestJ = 0
fun checkWinner(): Int {
for (i in 0..2) {
if (b[i][0] != 0 && b[i][1] == b[i][0] && b[i][2] == b[i][0]) return b[i][0]
if (b[0][i] != 0 && b[1][i] == b[0][i] && b[2][i] == b[0][i]) return b[0][i]
}
if (b[1][1] == 0) return 0
if (b[1][1] == b[0][0] && b[2][2] == b[0][0]) return b[0][0]
if (b[1][1] == b[2][0] && b[0][2] == b[1][1]) return b[1][1]
return 0
}
fun showBoard() {
val t = "X O"
for (i in 0..2) {
for (j in 0..2) print("${t[b[i][j] + 1]} ")
println()
}
println("-----")
}
fun testMove(value: Int, depth: Int): Int {
var best = -1
var changed = 0
var score = checkWinner()
if (score != 0) return if (score == value) 1 else -1
for (i in 0..2) {
for (j in 0..2) {
if (b[i][j] != 0) continue
b[i][j] = value
changed = value
score = -testMove(-value, depth + 1)
b[i][j] = 0
if (score <= best) continue
if (depth == 0) {
bestI = i
bestJ = j
}
best = score
}
}
return if (changed != 0) best else 0
}
fun game(user: Boolean): String {
var u = user
for (i in 0..2) b[i].fill(0)
print("Board postions are numbered so:\n1 2 3\n4 5 6\n7 8 9\n")
print("You have O, I have X.\n\n")
for (k in 0..8) {
while (u) {
var move: Int?
do {
print("Your move: ")
move = readLine()!!.toIntOrNull()
}
while (move != null && move !in 1..9)
move = move!! - 1
val i = move / 3
val j = move % 3
if (b[i][j] != 0) continue
b[i][j] = 1
break
}
if (!u) {
if (k == 0) { // randomize if computer opens, less boring
bestI = r.nextInt(Int.MAX_VALUE) % 3
bestJ = r.nextInt(Int.MAX_VALUE) % 3
}
else testMove(-1, 0)
b[bestI][bestJ] = -1
val myMove = bestI * 3 + bestJ + 1
println("My move: $myMove")
}
showBoard()
val win = checkWinner()
if (win != 0) return (if (win == 1) "You win" else "I win") + ".\n\n"
u = !u
}
return "A draw.\n\n"
}
fun main(args: Array<String>) {
var user = false
while (true) {
user = !user
print(game(user))
var yn: String
do {
print("Play again y/n: ")
yn = readLine()!!.toLowerCase()
}
while (yn != "y" && yn != "n")
if (yn != "y") return
println()
}
}
Sample game:
Board postions are numbered so:
1 2 3
4 5 6
7 8 9
You have O, I have X.
Your move: 2
O
-----
My move: 1
X O
-----
Your move: 8
X O
O
-----
My move: 5
X O
X
O
-----
Your move: 9
X O
X
O O
-----
My move: 7
X O
X
X O O
-----
Your move: 3
X O O
X
X O O
-----
My move: 4
X O O
X X
X O O
-----
I win.
Play again y/n: n
Lasso
{{incomplete|Lasso|Computer doesn't ''play'' - it merely manages the board.}} This example uses an HTML form for the UI, buttons representing the game state, and Lasso's built inn session handler to keep track of who's turn it is, what the game matrix state is, and the winner history.
As image uploads has been disabled, a live version can be viewed at: [http://jono.guthrie.net.nz/rosetta/Tic-tac-toe.lasso http://jono.guthrie.net.nz/rosetta/Tic-tac-toe.lasso]
[
session_start('user')
session_addvar('user', 'matrix')
session_addvar('user', 'winrecord')
session_addvar('user', 'turn')
var(matrix)->isNotA(::array) ? var(matrix = array('-','-','-','-','-','-','-','-','-'))
var(winrecord)->isNotA(::array) ? var(winrecord = array)
var(turn)->isNotA(::string) ? var(turn = 'x')
if(web_request->params->asStaticArray >> 'reset') => {
$matrix = array('-','-','-','-','-','-','-','-','-')
$turn = 'x'
}
with i in web_request->params->asStaticArray do => {
if(#i->name->beginswith('p')) => {
local(num = #i->name->asCopy)
#num->removeLeading('p')
#num = integer(#num)
#num > 0 && $matrix->get(#num) == '-' ? $matrix->get(#num) = #i->value
$turn == 'o' ? $turn = 'x' | $turn = 'o'
}
}
local(
istie = false,
winner = 'noone',
clear = false
)
// determine if we have a winner
if($matrix->find('-')->size < 9) => {
local(winners = array('123','456','789','147','258','369','159','357'))
loop(8) => {
local(xscore = 0,oscore = 0,use = #winners->get(loop_count))
with v in #use->values do => {
$matrix->findposition('x') >> integer(#v) ? #xscore++
$matrix->findposition('o') >> integer(#v) ? #oscore++
}
if(#xscore == 3) => {
#winner = 'x'
$winrecord->insert('x')
#clear = true
loop_abort
}
if(#oscore == 3) => {
#winner = 'o'
$winrecord->insert('o')
#clear = true
loop_abort
}
}
}
// determine if tie
if(not $matrix->find('-')->size && #winner == 'noone') => {
#istie = true
#winner = 'tie'
$winrecord->insert('tie')
#clear = true
}
]
<form action="?" method="post">
<table>
<tr>
[loop(3) => {^]<td><button name="p[loop_count]" value="[$turn]"[
$matrix->get(loop_count) != '-' || #winner != 'noone' ? ' disabled="disabled"'
]>[$matrix->get(loop_count) != '-' ? $matrix->get(loop_count) | ' ']</button></td>[^}]
</tr>
<tr>
[loop(-from=4,-to=6) => {^]<td><button name="p[loop_count]" value="[$turn]"[
$matrix->get(loop_count) != '-' || #winner != 'noone' ? ' disabled="disabled"'
]>[$matrix->get(loop_count) != '-' ? $matrix->get(loop_count) | ' ']</button></td>[^}]
</tr>
<tr>
[loop(-from=7,-to=9) => {^]<td><button name="p[loop_count]" value="[$turn]"[
$matrix->get(loop_count) != '-' || #winner != 'noone' ? ' disabled="disabled"'
]>[$matrix->get(loop_count) != '-' ? $matrix->get(loop_count) | ' ']</button></td>[^}]
</tr>
</table>
</form>
[if(#istie && #winner == 'tie')]
<p><b>It's a tie!</b></p>
[else(#winner != 'noone')]
<p>[#winner->uppercase&] won! Congratulations.</p>
[else]<math>Insert formula here</math>
<p>It is now [$turn]'s turn!</p>
[/if]
<p><a href="?reset">Reset</a></p>
[if($winrecord->size)]<p>Win record: [$winrecord->join(', ')]</p>[/if]
[if(#clear == true) => {
$matrix = array('-','-','-','-','-','-','-','-','-')
$turn = 'x'
}]
Lingo
The standard way to create GUI apps in Lingo is to use the authoring tool "Director" as GUI builder. The code below instead uses a simple framework (stored in global "$") that eases programmatic GUI creation.
Screenshot of application window: http://valentin.dasdeck.com/lingo/tic-tac-toe/tic-tac-toe-lingo.png
"Human" cannot win this game.
global $ -- object representing simple framework
global gBoard -- current board image
global gBoardTemplate -- empty board image
global gHumanChip -- cross image
global gComputerChip -- circle image
global gM -- 3x3 matrix storing game state: 0=free cell, 1=human cell, -1=computer cell
global gStep -- index of current move (1..9)
global gGameOverFlag -- TRUE if current game is over
----------------------------------------
-- Entry point
----------------------------------------
on startMovie
-- libs
$.import("sprite")
-- window properties
_movie.stage.title = "Tic-Tac-Toe"
_movie.stage.rect = rect(0, 0, 224, 310)
_movie.centerStage = TRUE
-- load images from filesystem
m = new(#bitmap)
m.importFileInto($.@("resources/cross.bmp"), [#trimWhiteSpace:FALSE])
gHumanChip = m.image
m = new(#bitmap)
m.importFileInto($.@("resources/circle.bmp"), [#trimWhiteSpace:FALSE])
gComputerChip = m.image
-- create GUI
m = new(#bitmap)
m.importFileInto($.@("resources/board.bmp"))
m.regpoint = point(0, 0)
s = $.sprite.make(m, [#loc:point(20, 20)], TRUE)
s.addListener(#mouseDown, _movie, #humanMove)
gBoard = m.image
gBoardTemplate = gBoard.duplicate()
m = $.sprite.newMember(#button, [#text:"New Game (Human starts)", #fontstyle:"bold", #rect:rect(0, 0, 180, 0)])
s = $.sprite.make(m, [#loc:point(20, 220)], TRUE)
s.addListener(#mouseDown, _movie, #newGame, 1)
m = $.sprite.newMember(#button, [#text:"New Game (Computer starts)", #fontstyle:"bold", #rect:rect(0, 0, 180, 0)])
s = $.sprite.make(m, [#loc:point(20, 250)], TRUE)
s.addListener(#mouseDown, _movie, #newGame, -1)
m = $.sprite.newMember(#field, [#name:"feedback", #editable:FALSE, #fontstyle:"bold", #alignment:"center",\
#border:0, #color:rgb(255, 0, 0), #rect:rect(0, 0, 180, 0)])
s = $.sprite.make(m, [#loc:point(20, 280)], TRUE)
newGame(1)
-- show the application window
_movie.updateStage()
_movie.stage.visible = TRUE
end
----------------------------------------
-- Starts a new game
----------------------------------------
on newGame (whoStarts)
-- reset board
gBoard.copyPixels(gBoardTemplate, gBoardTemplate.rect, gBoardTemplate.rect)
-- clear feedback
member("feedback").text = ""
-- reset states
gM = [[0, 0, 0], [0, 0, 0], [0, 0, 0]]
gStep = 0
gGameOverFlag = FALSE
if whoStarts=-1 then computerMove()
end
----------------------------------------
-- Handles a human move (mouse click)
----------------------------------------
on humanMove ()
if gGameOverFlag then return
-- find cell for mouse position
p = _mouse.clickLoc - sprite(1).loc
if p.locH mod 60<4 or p.locV mod 60<4 then return
p = p / 60
x = p[1] + 1
y = p[2] + 1
if gM[x][y] then return -- ignore illegal moves
gM[x][y] = 1
-- update cell image
p = p * 60
gBoard.copyPixels(gHumanChip, gHumanChip.rect.offset(4+p[1], 4+p[2]), gHumanChip.rect)
-- proceed (unless game over)
gStep = gStep + 1
if not checkHumanMove(x, y) then computerMove()
end
----------------------------------------
-- Checks if human has won or game ended with draw
----------------------------------------
on checkHumanMove (x, y)
if sum([gM[x][1], gM[x][2], gM[x][3]])=3 then return gameOver(1, [[x, 1], [x, 2], [x, 3]])
if sum([gM[1][y], gM[2][y], gM[3][y]])=3 then return gameOver(1, [[1, y], [2, y], [3, y]])
if x=y and sum([gM[1][1], gM[2][2], gM[3][3]])=3 then return gameOver(1, [[1, 1], [2, 2], [3, 3]])
if x+y=4 and sum([gM[1][3], gM[2][2], gM[3][1]])=3 then return gameOver(1, [[1, 3], [2, 2], [3, 1]])
if gStep=9 then return gameOver(0)
return FALSE
end
----------------------------------------
-- Checks if selecting specified empty cell makes computer or human win
----------------------------------------
on checkCellWins (x, y, who)
wins = who*2
if sum([gM[1][y], gM[2][y], gM[3][y]]) = wins then return [[1, y], [2, y], [3, y]]
if sum([gM[x][1], gM[x][2], gM[x][3]]) = wins then return [[x, 1], [x, 2], [x, 3]]
if x=y and sum([gM[1][1], gM[2][2], gM[3][3]]) = wins then return [[1, 1], [2, 2], [3, 3]]
if x+y=4 and sum([gM[1][3], gM[2][2], gM[3][1]]) = wins then return [[1, 3], [2, 2], [3, 1]]
return FALSE
end
----------------------------------------
-- Handles game over
----------------------------------------
on gameOver (winner, cells)
gGameOverFlag = TRUE
if winner = 0 then
member("feedback").text = "It's a draw!"
else
-- hilite winning line with yellow
img = image(56, 56, 32)
img.fill(img.rect, rgb(255, 255, 0))
repeat with c in cells
x = (c[1]-1)*60 + 4
y = (c[2]-1)*60 + 4
gBoard.copyPixels(img, img.rect.offset(x, y), img.rect, [#ink:#darkest])
end repeat
member("feedback").text = ["Human", "Computer"][1+(winner=-1)] & " has won!"
end if
return TRUE
end
----------------------------------------
-- Calculates next computer move
----------------------------------------
on computerMove ()
gStep = gStep + 1
-- move 1: select center
if gStep=1 then return doComputerMove(2, 2)
-- move 2 (human started)
if gStep=2 then
if gM[2][2]=1 then
-- if center, select arbitrary corner
return doComputerMove(1, 1)
else
-- otherwise select center
return doComputerMove(2, 2)
end if
end if
-- move 3 (computer started)
if gStep=3 then
-- if corner, select diagonally opposite corner
if gM[1][1]=1 then return doComputerMove(3, 3)
if gM[3][3]=1 then return doComputerMove(1, 1)
if gM[1][3]=1 then return doComputerMove(3, 1)
return doComputerMove(1, 1) -- top left corner as default
end if
-- get free cells
free = []
repeat with x = 1 to 3
repeat with y = 1 to 3
if gM[x][y]=0 then free.add([x, y])
end repeat
end repeat
-- check if computer can win now
repeat with c in free
res = checkCellWins(c[1], c[2], -1)
if res<>FALSE then
doComputerMove(c[1], c[2])
return gameOver(-1, res)
end if
end repeat
-- check if human could win with next move (if yes, prevent it)
repeat with c in free
res = checkCellWins(c[1], c[2], 1)
if res<>FALSE then return doComputerMove(c[1], c[2], TRUE)
end repeat
-- move 4 (human started): prevent "double mills"
if gStep=4 then
if gM[2][2]=1 and (gM[1][1]=1 or gM[3][3]=1) then return doComputerMove(3, 1)
if gM[2][2]=1 and (gM[1][3]=1 or gM[3][1]=1) then return doComputerMove(1, 1)
if gM[2][3]+gM[3][2]=2 then return doComputerMove(3, 3)
if gM[1][2]+gM[2][3]=2 then return doComputerMove(1, 3)
if gM[1][2]+gM[2][1]=2 then return doComputerMove(1, 1)
if gM[2][1]+gM[3][2]=2 then return doComputerMove(3, 1)
if (gM[1][3]+gM[3][1]=2) or (gM[1][1]+gM[3][3]=2) then return doComputerMove(2, 1)
end if
-- move 5 (computer started): try to create a "double mill"
if gStep=5 then
repeat with x = 1 to 3
col = [gM[x][1], gM[x][2], gM[x][3]]
if not (sum(col)=-1 and max(col)=0) then next repeat
repeat with y = 1 to 3
row = [gM[1][y], gM[2][y], gM[3][y]]
if not (sum(row)=-1 and max(row)=0 and gM[x][y]=0) then next repeat
return doComputerMove(x, y)
end repeat
end repeat
end if
-- otherwise use first free cell
c = free[1]
doComputerMove(c[1], c[2])
end
----------------------------------------
-- Updates state matrix and cell image
----------------------------------------
on doComputerMove (x, y, checkDraw)
gM[x][y] = -1
gBoard.copyPixels(gComputerChip, gComputerChip.rect.offset(4+(x-1)*60, 4+(y-1)*60), gComputerChip.rect)
if checkDraw and gStep=9 then gameOver(0)
end
----------------------------------------
--
----------------------------------------
on sum (aLine)
return aLine[1]+aLine[2]+aLine[3]
end
M2000 Interpreter
Computer May loose;
Module Tic.Tac.Toe {
Dim Board$(1 to 3, 1 to 3)=" "
WinGame=False
p=Board$()
RandomPosition=lambda -> {
=(random(1,3), random(1,3))
}
BoardItemEmpty=Lambda p (x, y) -> {
=Array$(p, x, y)=" "
}
BoardSetItem=Lambda p (x, y, w$) -> {
link p to a$()
a$(x, y)=w$
}
T=9
R=0
C=0
Repeat {
Print "Computer Move:"
CompMove()
T--
DrawBoard()
CheckWin()
if WinGame Then Print "Computer Win": Exit
if T=0 then exit
Repeat {
GetRowCol("Input Row", &R)
GetRowCol("Input Column", &C)
If BoardItemEmpty(R,C) then call boardsetitem(R,C,"O") : exit
} Always
T--
DrawBoard()
CheckWin()
if WinGame Then Print "You Win": Exit
} until T=0 or WinGame
Sub DrawBoard()
Print "R/C 1 2 3"
Print " 1) "; Board$(1,1);"|";Board$(1,2);"|";Board$(1,3)
Print " -+-+-"
Print " 2) "; Board$(2,1);"|";Board$(2,2);"|";Board$(2,3)
Print " -+-+-"
Print " 3) "; Board$(3,1);"|";Board$(3,2);"|";Board$(3,3)
End Sub
Sub CheckWin()
WinGame=false
local i,j,three$
For i=1 to 3
three$=""
For j=1 to 3 : three$+=Board$(i,j) : Next j
CheckThree()
three$=""
For j=1 to 3 : three$+=Board$(j,i) :Next j
CheckThree()
Next i
three$=""
For i=1 to 3 : three$+=Board$(i,i): Next i
CheckThree()
three$=""
For i=1 to 3:three$+=Board$(i,4-i): Next i
CheckThree()
End Sub
Sub CheckThree()
if instr(three$," ")=0 then WinGame=WinGame or Filter$(three$, left$(three$,1))=""
End Sub
Sub CompMove()
if T<9 and Board$(2,2)=" " then {
call boardsetitem(2,2,"X")
} Else {
local i=3, j=3, found=false
if T<=6 then {
CompThink("X","X")
}
let i=3, j=3
If Not found And T<6 then {
CompThink("O","X")
}
If not found then {
Repeat {
comp=RandomPosition()
If BoardItemEmpty(!comp) then call boardsetitem(!comp, "X") : exit
} Always
}
}
End Sub
Sub CompThink(Bad$, Good$)
While i>0 {
j=3
While j>0 {
if Board$(i,j)=" " then {
Board$(i,j)=Bad$
CheckWin()
if WinGame then {
Board$(i,j)=Good$:i=0:j=0: found=true
} Else Board$(i,j)=" "
}
j--
}
i--
}
End Sub
Sub GetRowCol(What$, &W)
Print What$;":";
Repeat {
W=Val("0"+Key$)
} until W>=1 and W<=3
Print Str$(W,"")
End Sub
}
Tic.Tac.Toe
{{out}}
Computer Move: R/C 1 2 3 1) | | -+-+- 2) | | -+-+- 3) | |X Input Row:2 Input Column:2 R/C 1 2 3 1) | | -+-+- 2) |O| -+-+- 3) | |X Computer Move: R/C 1 2 3 1) | | -+-+- 2) |O| -+-+- 3) X| |X Input Row:3 Input Column:2 R/C 1 2 3 1) | | -+-+- 2) |O| -+-+- 3) X|O|X Computer Move: R/C 1 2 3 1) |X| -+-+- 2) |O| -+-+- 3) X|O|X Input Row:2 Input Column:1 R/C 1 2 3 1) |X| -+-+- 2) O|O| -+-+- 3) X|O|X Computer Move: R/C 1 2 3 1) |X| -+-+- 2) O|O|X -+-+- 3) X|O|X Input Row:1 Input Column:3 R/C 1 2 3 1) |X|O -+-+- 2) O|O|X -+-+- 3) X|O|X Computer Move: R/C 1 2 3 1) X|X|O -+-+- 2) O|O|X -+-+- 3) X|O|X## Mathematica
───────── computer places a marker [O] at cell number 5 ║ ║ │ │ ║ ║ 1 │ 2 │ 3 ║ ║ │ │ ═════╬═════╬═════ ─────┼─────┼───── ║ ║ │ │ ║ O ║ 4 │ 5 │ 6 ║ ║ │ │ ═════╬═════╬═════ ─────┼─────┼───── ║ ║ │ │ ║ ║ 7 │ 8 │ 9 ║ ║ │ │ ───────── Please enter a cell number to place your next marker [X] (or Quit): 2 ◄■■■■■■■■■■ human player's move. ║ ║ │ │ ║ X ║ 1 │ 2 │ 3 ║ ║ │ │ ═════╬═════╬═════ ─────┼─────┼───── ║ ║ │ │ ║ O ║ 4 │ 5 │ 6 ║ ║ │ │ ═════╬═════╬═════ ─────┼─────┼───── ║ ║ │ │ ║ ║ 7 │ 8 │ 9 ║ ║ │ │ ───────── computer places a marker [O] at cell number 9 ║ ║ │ │ ║ X ║ 1 │ 2 │ 3 ║ ║ │ │ ═════╬═════╬═════ ─────┼─────┼───── ║ ║ │ │ ║ O ║ 4 │ 5 │ 6 ║ ║ │ │ ═════╬═════╬═════ ─────┼─────┼───── ║ ║ │ │ ║ ║ O 7 │ 8 │ 9 ║ ║ │ │ ───────── Please enter a cell number to place your next marker [X] (or Quit): 1 ◄■■■■■■■■■■ human player's move. ║ ║ │ │ X ║ X ║ 1 │ 2 │ 3 ║ ║ │ │ ═════╬═════╬═════ ─────┼─────┼───── ║ ║ │ │ ║ O ║ 4 │ 5 │ 6 ║ ║ │ │ ═════╬═════╬═════ ─────┼─────┼───── ║ ║ │ │ ║ ║ O 7 │ 8 │ 9 ║ ║ │ │ ───────── computer places a marker [O] at cell number 3 ║ ║ │ │ X ║ X ║ O 1 │ 2 │ 3 ║ ║ │ │ ═════╬═════╬═════ ─────┼─────┼───── ║ ║ │ │ ║ O ║ 4 │ 5 │ 6 ║ ║ │ │ ═════╬═════╬═════ ─────┼─────┼───── ║ ║ │ │ ║ ║ O 7 │ 8 │ 9 ║ ║ │ │ ───────── Please enter a cell number to place your next marker [X] (or Quit): 7 ◄■■■■■■■■■■ human player's move. ║ ║ │ │ X ║ X ║ O 1 │ 2 │ 3 ║ ║ │ │ ═════╬═════╬═════ ─────┼─────┼───── ║ ║ │ │ ║ O ║ 4 │ 5 │ 6 ║ ║ │ │ ═════╬═════╬═════ ─────┼─────┼───── ║ ║ │ │ X ║ ║ O 7 │ 8 │ 9 ║ ║ │ │ ───────── computer places a marker [O] at cell number 6 ║ ║ │ │ X ║ X ║ O 1 │ 2 │ 3 ║ ║ │ │ ═════╬═════╬═════ ─────┼─────┼───── ║ ║ │ │ ║ O ║ O 4 │ 5 │ 6 ║ ║ │ │ ═════╬═════╬═════ ─────┼─────┼───── ║ ║ │ │ X ║ ║ O 7 │ 8 │ 9 ║ ║ │ │ ────────────────────────────── The computer (O) won. ────────────────────────────── ``` '''output''' when using the input of: 5 db (which indicates a grid of '''5x5''' and that the marker for the human is a hexadecimal '''db''' [█].)║ ║ ║ ║ │ │ │ │ ║ ║ ║ ║ 1 │ 2 │ 3 │ 4 │ 5 ║ ║ ║ ║ │ │ │ │ ═════╬═════╬═════╬═════╬═════ ─────┼─────┼─────┼─────┼───── ║ ║ ║ ║ │ │ │ │ ║ ║ ║ ║ 6 │ 7 │ 8 │ 9 │ 10 ║ ║ ║ ║ │ │ │ │ ═════╬═════╬═════╬═════╬═════ ─────┼─────┼─────┼─────┼───── ║ ║ ║ ║ │ │ │ │ ║ ║ ║ ║ 11 │ 12 │ 13 │ 14 │ 15 ║ ║ ║ ║ │ │ │ │ ═════╬═════╬═════╬═════╬═════ ─────┼─────┼─────┼─────┼───── ║ ║ ║ ║ │ │ │ │ ║ ║ ║ ║ 16 │ 17 │ 18 │ 19 │ 20 ║ ║ ║ ║ │ │ │ │ ═════╬═════╬═════╬═════╬═════ ─────┼─────┼─────┼─────┼───── ║ ║ ║ ║ │ │ │ │ ║ ║ ║ ║ 21 │ 22 │ 23 │ 24 │ 25 ║ ║ ║ ║ │ │ │ │ ───────── Please enter a cell number to place your next marker [█] (or Quit): 5 ◄■■■■■■■■■■ human player's move. ║ ║ ║ ║ │ │ │ │ ║ ║ ║ ║ █ 1 │ 2 │ 3 │ 4 │ 5 ║ ║ ║ ║ │ │ │ │ ═════╬═════╬═════╬═════╬═════ ─────┼─────┼─────┼─────┼───── ║ ║ ║ ║ │ │ │ │ ║ ║ ║ ║ 6 │ 7 │ 8 │ 9 │ 10 ║ ║ ║ ║ │ │ │ │ ═════╬═════╬═════╬═════╬═════ ─────┼─────┼─────┼─────┼───── ║ ║ ║ ║ │ │ │ │ ║ ║ ║ ║ 11 │ 12 │ 13 │ 14 │ 15 ║ ║ ║ ║ │ │ │ │ ═════╬═════╬═════╬═════╬═════ ─────┼─────┼─────┼─────┼───── ║ ║ ║ ║ │ │ │ │ ║ ║ ║ ║ 16 │ 17 │ 18 │ 19 │ 20 ║ ║ ║ ║ │ │ │ │ ═════╬═════╬═════╬═════╬═════ ─────┼─────┼─────┼─────┼───── ║ ║ ║ ║ │ │ │ │ ║ ║ ║ ║ 21 │ 22 │ 23 │ 24 │ 25 ║ ║ ║ ║ │ │ │ │ ───────── computer places a marker [O] at cell number 14 ║ ║ ║ ║ │ │ │ │ ║ ║ ║ ║ █ 1 │ 2 │ 3 │ 4 │ 5 ║ ║ ║ ║ │ │ │ │ ═════╬═════╬═════╬═════╬═════ ─────┼─────┼─────┼─────┼───── ║ ║ ║ ║ │ │ │ │ ║ ║ ║ ║ 6 │ 7 │ 8 │ 9 │ 10 ║ ║ ║ ║ │ │ │ │ ═════╬═════╬═════╬═════╬═════ ─────┼─────┼─────┼─────┼───── ║ ║ ║ ║ │ │ │ │ ║ ║ ║ O ║ 11 │ 12 │ 13 │ 14 │ 15 ║ ║ ║ ║ │ │ │ │ ═════╬═════╬═════╬═════╬═════ ─────┼─────┼─────┼─────┼───── ║ ║ ║ ║ │ │ │ │ ║ ║ ║ ║ 16 │ 17 │ 18 │ 19 │ 20 ║ ║ ║ ║ │ │ │ │ ═════╬═════╬═════╬═════╬═════ ─────┼─────┼─────┼─────┼───── ║ ║ ║ ║ │ │ │ │ ║ ║ ║ ║ 21 │ 22 │ 23 │ 24 │ 25 ║ ║ ║ ║ │ │ │ │ ───────── Please enter a cell number to place your next marker [█] (or Quit): q ◄■■■■■■■■■■ human player's response. ────────────────────────────────── quitting. ────────────────────────────────── ``` ## Ring Easy and simple implementation of tecTacToe in ring programming language with human-human type of game(for now). This implementation is a gui implementation using the default gui provided by the language The tecTacToe.ring is provided [https://github.com/AbdelrahmanGIT/RingSamples/blob/master/src/TecTacToe.ring here] ```ring Load "guilib.ring" #Provide a list to save each button status in numeric readable format #0=nothing 1=X 2=O lst=[] #Provide onScreen button status and style btns=[] #Define who has the turn isXTurn=true app=new qApp { frmMain=new qMainWindow() { setWindowTitle("TicTacToe!") resize(300,320) move(200,200) //buttons pos=0 for y=0 to 2 for x=0 to 2 //Creating Buttons on the screen pos++ Add(lst,0) Add(btns,new qPushButton(frmMain) { setGeometry(x*100,y*100,100,100) setText("-") setclickevent("Disp(" + pos +")") setstylesheet("font-size:24pt ; font: bold ; color:yellow ; background-color: green") }) next next //StatusBar status=new qStatusBar(frmMain) { showMessage("Ready",0) } setwindowflags(Qt_dialog) setStatusbar(status) show() } exec() } //Restart the game by re init buttons status func reStart for i=1 to 9 lst[i]=0 btns[i].setText("-") next isXTurn=true func Disp x if isXTurn=true and lst[x]=0 btns[x].setText("X") lst[x]=1 isXTurn=false but isXTurn=false and lst[x]=0 btns[x].setText("O") lst[x]=2 isXTurn=true ok winner = CheckWinner() #if there is no Winner and still there is ability to winner #continue playing. if winner<1 return ok //Who is the winner! switch winner on 1 new qMessagebox(frmMain) { SetWindowTitle("We have a winner!") SetText("Good job X you won!") show() } on 2 new qMessagebox(frmMain) { SetWindowTitle("We have a winner!") SetText("Good job O you won!") show() } on 3 new qMessagebox(frmMain) { SetWindowTitle("Oh no it's a tie") SetText("Oh no it's a tie!") show() } off reStart() func CheckWinner //vertical check for v=1 to 9 step 3 if lst[v]!=0 and lst[v+1]!=0 and lst[v+2]!=0 if lst[v]=lst[v+1] and lst[v+1]=lst[v+2] return lst[v] ok ok next //horzintal for h=1 to 3 if lst[h]!=0 and lst[h+3]!=0 and lst[h+6]!=0 if lst[h]=lst[h+3] and lst[h+3]=lst[h+6] return lst[h] ok ok next //Cross if lst[1]!=0 and lst[5]!=0 and lst[9]!=0 if lst[1]=lst[5] and lst[5]=lst[9] return lst[1] ok ok if lst[3]!=0 and lst[5]!=0 and lst[7]!=0 if lst[3]=lst[5] and lst[5]=lst[7] return lst[3] ok ok //tie tie=true for i=1 to 9 if lst[i]=0 tie=false exit ok next if tie=true return 3 ok return 0 ``` {{out}} [[http://ring-lang.sourceforge.net/tictactoe.jpg image]] ## Ruby This implementation splits functionality into four classes:Game
,Player
,HumanPlayer
, andComputerPlayer
. A game can be played between any combination ofHumanPlayer
s andComputerPlayer
s. The AI for the computer uses certain heuristics to ensure that it never loses, but the AI is not as aggressive as it could be. The AI defends against the opening corner trap, but does not exploit it in its offense. This implementation stores the board as a one-dimensional array and hardcodes all possible straight lines inLINES
, rather than storing the board as a two-dimensional matrix and identifying straight lines dynamically. ```ruby require 'set' module TicTacToe LINES = [[1,2,3],[4,5,6],[7,8,9],[1,4,7],[2,5,8],[3,6,9],[1,5,9],[3,5,7]] class Game def initialize(player_1_class, player_2_class) @board = Array.new(10) # we ignore index 0 for convenience @current_player_id = 0 @players = [player_1_class.new(self, "X"), player_2_class.new(self, "O")] puts "#{current_player} goes first." end attr_reader :board, :current_player_id def play loop do place_player_marker(current_player) if player_has_won?(current_player) puts "#{current_player} wins!" print_board return elsif board_full? puts "It's a draw." print_board return end switch_players! end end def free_positions Set.new((1..9).select {|position| @board[position].nil?}) end def place_player_marker(player) position = player.select_position! puts "#{player} selects #{player.marker} position #{position}" @board[position] = player.marker end def player_has_won?(player) LINES.any? do |line| line.all? {|position| @board[position] == player.marker} end end def board_full? free_positions.empty? end def other_player_id 1 - @current_player_id end def switch_players! @current_player_id = other_player_id end def current_player @players[current_player_id] end def opponent @players[other_player_id] end def turn_num 10 - free_positions.size end def print_board col_separator, row_separator = " | ", "--+---+--" label_for_position = lambda{|position| @board[position] ? @board[position] : position} row_for_display = lambda{|row| row.map(&label_for_position).join(col_separator)} row_positions = [[1,2,3], [4,5,6], [7,8,9]] rows_for_display = row_positions.map(&row_for_display) puts rows_for_display.join("\n" + row_separator + "\n") end end class Player def initialize(game, marker) @game = game @marker = marker end attr_reader :marker end class HumanPlayer < Player def select_position! @game.print_board loop do print "Select your #{marker} position: " selection = gets.to_i return selection if @game.free_positions.include?(selection) puts "Position #{selection} is not available. Try again." end end def to_s "Human" end end class ComputerPlayer < Player DEBUG = false # edit this line if necessary def group_positions_by_markers(line) markers = line.group_by {|position| @game.board[position]} markers.default = [] markers end def select_position! opponent_marker = @game.opponent.marker winning_or_blocking_position = look_for_winning_or_blocking_position(opponent_marker) return winning_or_blocking_position if winning_or_blocking_position if corner_trap_defense_needed? return corner_trap_defense_position(opponent_marker) end # could make this smarter by sometimes doing corner trap offense return random_prioritized_position end def look_for_winning_or_blocking_position(opponent_marker) for line in LINES markers = group_positions_by_markers(line) next if markers[nil].length != 1 if markers[self.marker].length == 2 log_debug "winning on line #{line.join}" return markers[nil].first elsif markers[opponent_marker].length == 2 log_debug "could block on line #{line.join}" blocking_position = markers[nil].first end end if blocking_position log_debug "blocking at #{blocking_position}" return blocking_position end end def corner_trap_defense_needed? corner_positions = [1, 3, 7, 9] opponent_chose_a_corner = corner_positions.any?{|pos| @game.board[pos] != nil} return @game.turn_num == 2 && opponent_chose_a_corner end def corner_trap_defense_position(opponent_marker) # if you respond in the center or the opposite corner, the opponent can force you to lose log_debug "defending against corner start by playing adjacent" # playing in an adjacent corner could also be safe, but would require more logic later on opponent_position = @game.board.find_index {|marker| marker == opponent_marker} safe_responses = {1=>[2,4], 3=>[2,6], 7=>[4,8], 9=>[6,8]} return safe_responses[opponent_position].sample end def random_prioritized_position log_debug "picking random position, favoring center and then corners" ([5] + [1,3,7,9].shuffle + [2,4,6,8].shuffle).find do |pos| @game.free_positions.include?(pos) end end def log_debug(message) puts "#{self}: #{message}" if DEBUG end def to_s "Computer#{@game.current_player_id}" end end end include TicTacToe Game.new(ComputerPlayer, ComputerPlayer).play puts players_with_human = [HumanPlayer, ComputerPlayer].shuffle Game.new(*players_with_human).play ``` {{out}} ```txt Computer0 goes first. Computer0 selects X position 5 Computer1 selects O position 9 Computer0 selects X position 3 Computer1 selects O position 7 Computer0 selects X position 8 Computer1 selects O position 2 Computer0 selects X position 1 Computer1 selects O position 4 Computer0 selects X position 6 It's a draw. X | O | X --+---+-- O | X | X --+---+-- O | X | O Human goes first. 1 | 2 | 3 --+---+-- 4 | 5 | 6 --+---+-- 7 | 8 | 9 Select your X position: 3 Human selects X position 3 Computer1 selects O position 6 1 | 2 | X --+---+-- 4 | 5 | O --+---+-- 7 | 8 | 9 Select your X position: 7 Human selects X position 7 Computer1 selects O position 5 1 | 2 | X --+---+-- 4 | O | O --+---+-- X | 8 | 9 Select your X position: 4 Human selects X position 4 Computer1 selects O position 1 O | 2 | X --+---+-- X | O | O --+---+-- X | 8 | 9 Select your X position: 9 Human selects X position 9 Computer1 selects O position 8 O | 2 | X --+---+-- X | O | O --+---+-- X | O | X Select your X position: 2 Human selects X position 2 It's a draw. O | X | X --+---+-- X | O | O --+---+-- X | O | X ``` ## Run BASIC ```runbasic ' --------------------------- ' TIC TAC TOE ' --------------------------- winBox$ = "123 456 789 159 147 258 369 357" boxPos$ = "123 231 456 564 789 897 159 591 357 753 132 465 798 174 285 396 159 471 582 693 147 258 369 195 375" ai$ = "519628374" ox$ = "OX" [newGame] for i = 1 to 9 box$(i) = "" next i goto [shoTic] [loop] for j = 1 to 2 tic$ = mid$(ox$,j,1) for i = 1 to 25 b$ = word$(boxPos$,i," ") b1 = val(mid$(b$,1,1)) b2 = val(mid$(b$,2,1)) b3 = val(mid$(b$,3,1)) if box$(b1) = tic$ AND box$(b2) = tic$ AND box$(b3) = "" then box$(b3) = "O" goto [shoTic] end if next i next j if box$(1) = "O" AND box$(5) = "X" and box$(9) = "X" then if box$(3) = "" then box$(3) = "O" goto [shoTic] end if if box$(7) = "" then box$(7) = "O" goto [shoTic] end if end if for i = 1 to 9 b1 = val(mid$(ai$,i,1)) if box$(b1) = "" then box$(b1) = "O" exit for end if next i [shoTic] cls ' ---------------------------------------- ' show tic tac toe screen ' ---------------------------------------- html "
" if box$(i) <> "" then html box$(i) else button #box, " ";box$(i);" ", [doTic] #box setkey(str$(i)) end if if i mod 3 = 0 then html " |