forked from minecartchris/CC-Casnio
385 lines
9.6 KiB
Lua
385 lines
9.6 KiB
Lua
os.pullEvent=os.pullEventRaw
|
|
if fs.exists("/disk/terminate") then
|
|
error("Service mode active",2)
|
|
end
|
|
--tic
|
|
--[[
|
|
|
|
TIC-TAC-TOE
|
|
by Evan Hahn (http://www.evanhahn.com/)
|
|
|
|
This is a program that allows you to play tic-tac-toe against the
|
|
computer.
|
|
|
|
You may change the configuration (below) to make the board more than 3x3,
|
|
or play with "white" and "black" instead of "x" and "o", and change how
|
|
the board is displayed.
|
|
|
|
How it works:
|
|
|
|
The board is represented by a 2D table of spaces. They are filled with
|
|
nil to start. When you play, you put an "x" or an "o" into the table. The
|
|
board is used to keep track of piece locations and to display them. It
|
|
does not calculate wins.
|
|
|
|
The board also has regions. A region is a place where a player may win
|
|
(horizontal, vertical, or diagonal). It holds pointers to the board table.
|
|
Player 1 is represented by +, and Player 2 by -. Each piece in the region
|
|
increments or decrements the checking of the region. Basically, two X's
|
|
returns as 2. Two O's returns as -2. 3 or -3 is a winning region.
|
|
|
|
Some notes:
|
|
|
|
- There are other ways to program this, but I did not elect to use them.
|
|
One alternate way: Generate a table that holds string representations
|
|
of all the winning boards. Check for wins against that table instead.
|
|
|
|
- I am unfamiliar with accepted style and best practices of Lua, and
|
|
my code may reflect that.
|
|
|
|
--]]
|
|
|
|
----------------------------------------------
|
|
-- Configuration (change this if you wish!) --
|
|
----------------------------------------------
|
|
|
|
-- Are they playable by human or computer-controlled?
|
|
PLAYER_1_HUMAN = true
|
|
PLAYER_2_HUMAN = false
|
|
|
|
-- Board size
|
|
BOARD_RANK = 3 -- The board will be this in both dimensions.
|
|
|
|
-- Display stuff
|
|
PLAYER_1 = "x" -- Player 1 is represented by this. Player 1 goes first.
|
|
PLAYER_2 = "o" -- Player 2 is represented by this.
|
|
EMPTY_SPACE = " " -- An empty space is displayed like this.
|
|
DISPLAY_HORIZONTAL_SEPARATOR = "-" -- Horizontal lines look like this.
|
|
DISPLAY_VERTICAL_SEPARATOR = " | " -- Vertical lines look like this
|
|
|
|
|
|
--[[ ###################################################################
|
|
#### Don't mess with things below here unless you are brave ####
|
|
################################################################### --]]
|
|
|
|
------------------------
|
|
-- More configuration --
|
|
------------------------
|
|
|
|
MAX_BOARD_RANK = 100 -- Won't run above this number. Prevents crashes.
|
|
|
|
-------------------------------------------------------
|
|
-- Don't run if the board is larger than the maximum --
|
|
-------------------------------------------------------
|
|
|
|
if BOARD_RANK > MAX_BOARD_RANK then os.exit(0) end
|
|
|
|
-----------------------------
|
|
-- Create board (2D table) --
|
|
-----------------------------
|
|
|
|
space = {}
|
|
for i = 0, (BOARD_RANK - 1) do
|
|
space[i] = {}
|
|
for j = 0, (BOARD_RANK - 1) do
|
|
space[i][j] = nil -- start each space with nil
|
|
end
|
|
end
|
|
|
|
---------------------
|
|
-- Board functions --
|
|
---------------------
|
|
|
|
-- get the piece at a given spot
|
|
function getPiece(x, y)
|
|
return space[x][y]
|
|
end
|
|
|
|
-- get the piece at a given spot; if nil, return " "
|
|
-- this is useful for output.
|
|
function getPieceNoNil(x, y)
|
|
if getPiece(x, y) ~= nil then
|
|
return getPiece(x, y)
|
|
else
|
|
return EMPTY_SPACE
|
|
end
|
|
end
|
|
|
|
-- is that space empty?
|
|
function isEmpty(x, y)
|
|
if getPiece(x, y) == nil then
|
|
return true
|
|
else
|
|
return false
|
|
end
|
|
end
|
|
|
|
-- place a piece there, but make sure nothing is there already.
|
|
-- if you can't play there, return false.
|
|
function placePiece(x, y, piece)
|
|
if isEmpty(x, y) == true then
|
|
space[x][y] = piece
|
|
return true
|
|
else
|
|
return false
|
|
end
|
|
end
|
|
|
|
-- is the game over?
|
|
function isGameOver()
|
|
if checkWin() == false then -- if there is no win...
|
|
for i = 0, (BOARD_RANK - 1) do -- is the board empty?
|
|
for j = 0, (BOARD_RANK - 1) do
|
|
if isEmpty(i, j) == true then return false end
|
|
end
|
|
end
|
|
return true
|
|
else -- there is a win; the game is over
|
|
return true
|
|
end
|
|
end
|
|
|
|
-- create a string made up of a certain number of smaller strings
|
|
-- this is useful for the display.
|
|
function repeatString(to_repeat, amount)
|
|
if amount <= 0 then return "" end
|
|
local to_return = ""
|
|
for i = 1, amount do
|
|
to_return = to_return .. to_repeat
|
|
end
|
|
return to_return
|
|
end
|
|
|
|
-- display the board.
|
|
-- this uses the configuration file pretty much entirely.
|
|
function displayBoard()
|
|
|
|
-- find the widest player
|
|
local widest_piece = math.max(string.len(PLAYER_1), string.len(PLAYER_2), string.len(EMPTY_SPACE))
|
|
|
|
-- display board, top to bottom
|
|
io.write("\n") -- make sure it starts on a new line
|
|
for i = (BOARD_RANK - 1), 0, -1 do
|
|
local row = "" -- start with an empty row
|
|
for j = 0, (BOARD_RANK - 1) do -- generate that row
|
|
local piece = getPieceNoNil(j, i)
|
|
row = row .. piece
|
|
row = row .. repeatString(" ", widest_piece - string.len(piece))
|
|
if j ~= (BOARD_RANK - 1) then
|
|
row = row .. DISPLAY_VERTICAL_SEPARATOR
|
|
end
|
|
end
|
|
io.write(row) -- output row
|
|
if i ~= 0 then -- output horizontal line as long as the row
|
|
io.write("\n")
|
|
local repeats = math.ceil(string.len(row) / string.len(DISPLAY_HORIZONTAL_SEPARATOR))
|
|
io.write(repeatString(DISPLAY_HORIZONTAL_SEPARATOR, repeats))
|
|
io.write("\n")
|
|
end
|
|
end
|
|
|
|
-- finish off with a line break
|
|
io.write("\n")
|
|
end
|
|
|
|
-------------------------------------------------
|
|
-- Create regions (I admit this is a bit ugly) --
|
|
-------------------------------------------------
|
|
|
|
-- declare region and a number to increment
|
|
region = {}
|
|
region_number = 0
|
|
|
|
-- vertical
|
|
for i = 0, (BOARD_RANK - 1) do
|
|
region[region_number] = {}
|
|
for j = 0, (BOARD_RANK - 1) do
|
|
region[region_number][j] = {}
|
|
region[region_number][j]["x"] = i
|
|
region[region_number][j]["y"] = j
|
|
end
|
|
region_number = region_number + 1
|
|
end
|
|
|
|
-- horizontal
|
|
for i = 0, (BOARD_RANK - 1) do
|
|
region[region_number] = {}
|
|
for j = 0, (BOARD_RANK - 1) do
|
|
region[region_number][j] = {}
|
|
region[region_number][j]["x"] = j
|
|
region[region_number][j]["y"] = i
|
|
end
|
|
region_number = region_number + 1
|
|
end
|
|
|
|
-- diagonal, bottom-left to top-right
|
|
region[region_number] = {}
|
|
for i = 0, (BOARD_RANK - 1) do
|
|
region[region_number][i] = {}
|
|
region[region_number][i]["x"] = i
|
|
region[region_number][i]["y"] = i
|
|
end
|
|
region_number = region_number + 1
|
|
|
|
-- diagonal, top-left to bottom-right
|
|
region[region_number] = {}
|
|
for i = (BOARD_RANK - 1), 0, -1 do
|
|
region[region_number][i] = {}
|
|
region[region_number][i]["x"] = BOARD_RANK - i - 1
|
|
region[region_number][i]["y"] = i
|
|
end
|
|
region_number = region_number + 1
|
|
|
|
----------------------
|
|
-- Region functions --
|
|
----------------------
|
|
|
|
-- get a region
|
|
function getRegion(number)
|
|
return region[number]
|
|
end
|
|
|
|
-- check for a win in a particular region.
|
|
-- returns a number representation of the region. occurrences of player 1
|
|
-- add 1, occurrences of player 2 subtract 1. so if there are two X pieces,
|
|
-- it will return 2. one O will return -1.
|
|
function checkWinInRegion(number)
|
|
local to_return = 0
|
|
for i, v in pairs(getRegion(number)) do
|
|
local piece = getPiece(v["x"], v["y"])
|
|
if piece == PLAYER_1 then to_return = to_return + 1 end
|
|
if piece == PLAYER_2 then to_return = to_return - 1 end
|
|
end
|
|
return to_return
|
|
end
|
|
|
|
-- check for a win in every region.
|
|
-- returns false if no winner.
|
|
-- returns the winner if there is one.
|
|
function checkWin()
|
|
for i in pairs(region) do
|
|
local win = checkWinInRegion(i)
|
|
if math.abs(win) == BOARD_RANK then
|
|
if win == math.abs(win) then
|
|
return PLAYER_1
|
|
else
|
|
return PLAYER_2
|
|
end
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
|
|
------------------
|
|
-- UI Functions --
|
|
------------------
|
|
|
|
-- human play
|
|
function humanPlay(piece)
|
|
|
|
io.write(piece .. ", here's the board:\n")
|
|
displayBoard()
|
|
local placed = false
|
|
while placed == false do -- loop until they play correctly
|
|
io.write("\nWhere would you like to play your " .. piece .. "?\n")
|
|
io.write("Give the X-coordinate (starting with 0). ")
|
|
local x = tonumber(io.read())
|
|
io.write("Now give the Y-coordinate (starting with 0). ")
|
|
local y = tonumber(io.read())
|
|
placed = placePiece(x, y, piece)
|
|
if placed == false then
|
|
io.write("I'm afraid you can't play there!")
|
|
end
|
|
end
|
|
displayBoard()
|
|
io.write("\n")
|
|
|
|
end
|
|
|
|
-- AI play
|
|
function AIPlay(piece)
|
|
|
|
-- am I negative or positive?
|
|
local me = 0
|
|
if piece == PLAYER_1 then me = 1 end
|
|
if piece == PLAYER_2 then me = -1 end
|
|
|
|
-- look for a region in which I can win
|
|
for i in pairs(region) do
|
|
local win = checkWinInRegion(i)
|
|
if win == ((BOARD_RANK - 1) * me) then
|
|
for j, v in pairs(getRegion(i)) do
|
|
if isEmpty(v["x"], v["y"]) == true then
|
|
placePiece(v["x"], v["y"], piece)
|
|
return
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- look for a region in which I can block
|
|
for i in pairs(region) do
|
|
local win = checkWinInRegion(i)
|
|
if win == ((BOARD_RANK - 1) * (me * -1)) then
|
|
for j, v in pairs(getRegion(i)) do
|
|
if isEmpty(v["x"], v["y"]) == true then
|
|
placePiece(v["x"], v["y"], piece)
|
|
return
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- play first empty space, if no better option
|
|
for i = 0, (BOARD_RANK - 1) do
|
|
for j = 0, (BOARD_RANK - 1) do
|
|
if placePiece(i, j, piece) ~= false then return end
|
|
end
|
|
end
|
|
|
|
end
|
|
|
|
----------
|
|
-- Main --
|
|
----------
|
|
|
|
-- welcome!
|
|
io.write("Welcome to Tic-Tac-Toe!\n\n")
|
|
|
|
-- play the game until someone wins
|
|
while true do
|
|
|
|
-- break if the game is won
|
|
if isGameOver() == true then break end
|
|
|
|
-- player 1
|
|
if PLAYER_1_HUMAN == true then humanPlay(PLAYER_1)
|
|
else AIPlay(PLAYER_1) end
|
|
sleep(1)
|
|
shell.run("clear all")
|
|
-- break if the game is won
|
|
if isGameOver() == true then break end
|
|
|
|
-- player 2
|
|
if PLAYER_2_HUMAN == true then humanPlay(PLAYER_2)
|
|
else AIPlay(PLAYER_2) end
|
|
|
|
end
|
|
|
|
-- show the final board
|
|
io.write("The final board:\n")
|
|
displayBoard()
|
|
io.write("\n")
|
|
|
|
-- write who won, or if there is a tie
|
|
win = checkWin()
|
|
if win == false then
|
|
io.write("Tie game!\n")
|
|
else
|
|
io.write(win)
|
|
io.write(" wins!\n")
|
|
end
|
|
sleep(5)
|
|
shell.run("clear all")
|
|
shell.run("startup")
|