aboutsummaryrefslogtreecommitdiffstats
path: root/test/tictactoe/tictactoe_board.lua
blob: 89622570db517a96dadd7372c58472aa2293d22d (plain) (blame)
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