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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
|
local error = error
local insert = table.insert
local ipairs = ipairs
local setmetatable = setmetatable
local type = type
local unpack = unpack
require 'curses'
local addstr = curses.addstr
local addch = curses.addch
local clrtoeol = curses.clrtoeol
local move = curses.move
module 'tictactoe_board'
-- constants
local board_background = {
" | | ",
" | | ",
" | | ",
"---+---+---",
" | | ",
" | | ",
" | | ",
"---+---+---",
" | | ",
" | | ",
" | | ",
}
-- constructor arguments are the coordinates on the screen where the board
-- should be drawn
function new(y, x)
-- the board uses empty tables to represent 'empty tile', since the
-- table constructor is guaranteed to produce unique tables, which will
-- compare not equal with each other
local board = {
{ {}, {}, {} },
{ {}, {}, {} },
{ {}, {}, {} },
}
return setmetatable({board = board, yorig = y, xorig = x}, {__index = _M})
end
function clone(self)
local copy = new(self.yorig, self.xorig)
for row in ipairs(self.board) do
for col in ipairs(self.board[row]) do
if self:at(row, col) ~= nil then
copy.board[row][col] = self.board[row][col]
end
end
end
return copy
end
function size()
return #board_background, board_background[1]:len()
end
-- translate board positions to positions on the screen
-- return a table with named members, to pass directly to the curses functions
function position(self, row, col)
return {y = self.yorig + (row - 1) * 4 + 1,
x = self.xorig + (col - 1) * 4 + 1}
end
function draw(self)
-- draw the board
for i, line in ipairs(board_background) do
addstr({y = self.yorig + i - 1, x = self.xorig}, line)
end
-- draw the x's and o's
for row in ipairs(self.board) do
for col, tile in ipairs(self.board[row]) do
if tile == "x" then
addch(self:position(row, col), "X", {color = "red"})
elseif tile == "o" then
addch(self:position(row, col), "O", {color = "blue"})
end
end
end
end
function at(self, y, x)
if type(self.board[y][x]) == "table" then
return nil
else
return self.board[y][x]
end
end
-- return a list of empty tiles on the board, where tiles are 2 element lists
-- of {y, x}
function empty_tiles(self)
local ret = {}
for row in ipairs(self.board) do
for col in ipairs(self.board[row]) do
if self:at(row, col) == nil then
insert(ret, {row, col})
end
end
end
return ret
end
function mark(self, turn, row, col)
if turn == "x" or turn == "o" then
self.board[row][col] = turn
else
error("mark called with \'" .. turn .. "\'")
end
end
-- check whether there is a winner on the board
-- returns the symbol for the winner (or nil if there is no winner), as well as
-- a list of the tiles that make up the win, so that we can mark them later
function winner(self)
-- check rows and columns
for i = 1, 3 do
if self.board[i][1] == self.board[i][2] and
self.board[i][2] == self.board[i][3] then
return self.board[i][i], {{i, 1}, {i, 2}, {i, 3}}
elseif self.board[1][i] == self.board[2][i] and
self.board[2][i] == self.board[3][i] then
return self.board[i][i], {{1, i}, {2, i}, {3, i}}
end
end
-- check diagonals
if self.board[1][1] == self.board[2][2] and
self.board[2][2] == self.board[3][3] then
return self.board[2][2], {{1, 1}, {2, 2}, {3, 3}}
elseif self.board[3][1] == self.board[2][2] and
self.board[2][2] == self.board[1][3] then
return self.board[2][2], {{3, 1}, {2, 2}, {1, 3}}
end
end
function mark_winner(self, winner, winner_tiles)
for _, loc in ipairs(winner_tiles) do
addch(self:position(unpack(loc)), winner:upper(), {color = "green"})
end
end
-- draw a string centered under the board
function caption(self, str)
move(self.yorig + #board_background + 1, 0)
clrtoeol()
addstr({y = self.yorig + #board_background + 1,
x = self.xorig + (board_background[1]:len() - 1) / 2 -
str:len() / 2},
str)
end
|