Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- --[[
- Author: theoriginalbit
- Version: 1.2 for ComputerCraft 1.6+
- Created: 26 APR 2013
- Last Update: 18 OCT 2014
- Licensed under the MIT License [http://opensource.org/licenses/MIT]
- Copyright © 2013 Joshua Asbury a.k.a theoriginalbit [theoriginalbit@gmail.com]
- --]]
- -- make sure that its only a computer terminal that is displaying
- local sw, sh = term.getSize()
- if sw ~= 51 and sh ~= 19 then
- error("Sorry this game can only run on computers", 0)
- end
- -- the wining directions
- local winCombos = {
- -- horizontal
- {1,2,3}, {4,5,6}, {7,8,9},
- -- vertical
- {1,4,7}, {2,5,8}, {3,6,9},
- -- diagonal
- {1,5,9}, {3,5,7}
- }
- local players = {x = 'Player', o = 'The Computer'}
- -- whether an AI is active, could be used later to allow SP
- local activeAI = true
- local currentPlayer
- local opposites = { x = 'o', o = 'x' }
- local board
- local winner
- local move
- local allowedBgColors = { colors.orange, colors.lightBlue, colors.gray, colors.cyan, colors.purple, colors.blue, colors.brown, colors.green, colors.red, colors.black }
- local bg
- local function clear(col)
- term.setBackgroundColor(col or colors.black)
- term.clear()
- term.setCursorPos(1,1)
- end
- -- function thanks to Mads... found here: http://www.computercraft.info/forums2/index.php?/topic/11771-print-coloured-text-easily/page__p__105389#entry105389
- local function writeWithFormat(...)
- local s = "&0"
- for k, v in ipairs(arg) do
- s = s .. v
- end
- s = s .. "&0"
- local fields = {}
- local lastcolor, lastpos = "0", 0
- for pos, clr in s:gmatch"()&(%x)" do
- table.insert(fields, {s:sub(lastpos + 2, pos - 1), lastcolor})
- lastcolor, lastpos = clr , pos
- end
- for i = 2, #fields do
- term.setTextColor(2 ^ (tonumber(fields[i][2], 16)))
- write(fields[i][1])
- end
- end
- -- modification of Mads' function to get the length of the string without the color modifiers
- local function countFormatters(text)
- return #(text:gsub("()&(%x)", ''))
- end
- -- print a color formatted string in the center of the screen
- local function cwriteWithFormat(text, y)
- local sw,sh = term.getSize()
- local _,cy = term.getCursorPos()
- term.setCursorPos(math.floor((sw-countFormatters(text))/2)+(countFormatters(text) % 2 == 0 and 1 or 0), y or cy)
- writeWithFormat(text)
- end
- -- writes the text at the give location
- local function writeAt(text, x, y)
- local _,cy = term.getCursorPos()
- term.setCursorPos(x or 1, y or cy)
- write(text)
- end
- local function reset()
- bg = allowedBgColors[math.random(1, #allowedBgColors)]
- currentPlayer = 'x'
- board = {}
- for i = 1, 9 do
- board[i] = ' '
- end
- winner = nil
- move = nil
- end
- local function search(match)
- for _, check in ipairs(winCombos) do
- if board[check[1]] == board[check[2]] and board[check[1]] == match and board[check[3]] == ' ' then
- return check[3]
- elseif board[check[1]] == board[check[3]] and board[check[1]] == match and board[check[2]] == ' ' then
- return check[2]
- elseif board[check[2]] == board[check[3]] and board[check[2]] == match and board[check[1]] == ' ' then
- return check[1]
- end
- end
- end
- local function getAIMove()
- -- make it seem like the computer actually has to think about its move
- sleep(0.8)
- -- check if AI can win and return the 3rd tile to create a win, if it cannot, check for a human attempt at winning and stop it, if there is none, return a random
- return (search(currentPlayer) or search(opposites[currentPlayer])) or math.random(1,9)
- end
- local function modread( _mask, _history, _limit )
- term.setCursorBlink(true)
- local input = ""
- local pos = 0
- if _mask then
- _mask = _mask:sub(1,1)
- end
- local historyPos = nil
- local sw, sh = term.getSize()
- local sx, sy = term.getCursorPos()
- local function redraw( _special )
- local scroll = (sx + pos >= sw and (sx + pos) - sw or 0)
- local replace = _special or _mask
- term.setCursorPos( sx, sy )
- term.write( replace and string.rep(replace, #input - scroll) or input:sub(scroll + 1) )
- term.setCursorPos( sx + pos - scroll, sy )
- end
- while true do
- local event = {os.pullEvent()}
- if event[1] == 'char' and (not _limit or #input < _limit) then
- input = input:sub(1, pos)..event[2]..input:sub(pos + 1)
- pos = pos + 1
- elseif event[1] == 'key' then
- if event[2] == keys.enter then
- break
- elseif event[2] == keys.backspace and pos > 0 then
- redraw(' ')
- input = input:sub(1, pos - 1)..input:sub(pos + 1)
- pos = pos - 1
- elseif event[2] == keys.delete and pos < #input then
- redraw(' ')
- input = input:sub(1, pos)..input:sub(pos + 2)
- elseif event[2] == keys.home then
- pos = 0
- elseif event[2] == keys['end'] then
- pos = #input
- elseif event[2] == keys.left and pos > 0 then
- pos = pos - 1
- elseif event[2] == keys.right and pos < #input then
- pos = pos + 1
- elseif _history and event[2] == keys.up or event[2] == keys.down then
- redraw(' ')
- if event[2] == keys.up then
- if not historyPos then
- historyPos = #_history
- elseif historyPos > 1 then
- historyPos = historyPos - 1
- end
- else
- if historyPos ~= nil and historyPos < #_history then
- historyPos = historyPos + 1
- elseif historyPos == #_history then
- historyPos = nil
- end
- end
- if historyPos then
- input = string.sub(_history[historyPos], 1, _limit) or ""
- pos = #input
- else
- input = ""
- pos = 0
- end
- end
- elseif event[1] == 'mouse_click' then
- local xPos, yPos = event[3], event[4]
- if xPos == sw and yPos == 1 then
- -- exit and make sure to fool the catch-all
- error('Terminated', 0)
- end
- local row = (xPos >= 16 and xPos <= 21) and 1 or (xPos >= 23 and xPos <= 28) and 2 or (xPos >= 30 and xPos <= 35) and 3 or 10
- local col = (yPos >= 4 and yPos <= 6) and 1 or (yPos >= 8 and yPos <= 10) and 2 or (yPos >= 12 and yPos <= 16) and 3 or 10
- local ret = (col - 1) * 3 + row
- if ret >= 1 and ret <= 9 then
- return ret
- end
- end
- redraw(_mask)
- end
- term.setCursorBlink(false)
- term.setCursorPos(1, sy + 1)
- return input
- end
- local function getHumanMove()
- writeWithFormat('&b[1-9] >>&f ')
- return modread()
- end
- local function processInput()
- -- set the cursor pos ready for the input
- term.setCursorPos(3, sh-1)
- move = (currentPlayer == 'x' and getHumanMove or getAIMove)()
- end
- local function output(msg)
- -- if the player is not an AI, print the error
- if not (activeAI and currentPlayer == 'o') then
- term.setCursorPos(3, sh-1)
- writeWithFormat('&eERROR >> '..msg)
- sleep(2)
- end
- end
- local function checkMove()
- -- if the user typed exit
- if not tonumber(move) and move:lower() == 'exit' then
- -- exit and make sure to fool the catch-all
- error('Terminated', 0)
- end
- -- attempt to convert the move to a number
- local nmove = tonumber(move)
- -- if it wasn't a number
- if not nmove then
- output(tostring(move)..' is not a number between 1 and 9!')
- return false
- end
- -- if it is not within range of the board
- if nmove > 9 or nmove < 1 then
- output('Must be a number between 1 and 9!')
- return false
- end
- -- if the space is already taken
- if board[nmove] ~= ' ' then
- output('Position already taken!')
- return false
- end
- -- keep the conversion
- move = tonumber(move)
- return true
- end
- local function checkWin()
- for _, check in ipairs(winCombos) do
- if board[check[1]] ~= ' ' and board[check[1]] == board[check[2]] and board[check[1]] == board[check[3]] then
- return board[check[1]]
- end
- end
- for _, tile in ipairs(board) do
- if tile == ' ' then
- return nil
- end
- end
- return 'tie'
- end
- local function update()
- if checkMove() then
- board[move] = currentPlayer
- winner = checkWin()
- currentPlayer = currentPlayer == 'x' and 'o' or 'x'
- end
- end
- local function render()
- -- clear the screen light blue
- clear(bg)
- -- draw the ascii borders
- term.setTextColor(colors.white)
- for i = 2, sh-1 do
- writeAt('|', 1, i)
- writeAt('|', sw, i)
- end
- writeAt('+'..string.rep('-', sw-2)..'+', 1, 1)
- writeAt('+'..string.rep('-', sw-2)..'+', 1, 3)
- writeAt('+'..string.rep('-', sw-2)..'+', 1, sh-2)
- writeAt('+'..string.rep('-', sw-2)..'+', 1, sh)
- if term.isColor and term.isColor() then
- term.setCursorPos(sw, 1)
- term.setBackgroundColor(colors.red)
- term.setTextColor(colors.black)
- writeWithFormat('X')
- end
- -- set our colours
- term.setBackgroundColor(colors.white)
- term.setTextColor(colors.black)
- -- clear an area for the title
- writeAt(string.rep(' ', sw-2), 2, 2)
- writeAt('Tic-Tac-Toe!', sw/2-5, 2)
- -- clear an area for the input
- writeAt(string.rep(' ', sw-2), 2, sh-1)
- -- clear the area for the board
- local h = sh - 6
- for i = 0, h - 1 do
- writeAt(string.rep(' ', sw - 2), 2, 4+i)
- end
- -- draw the grid
- for i = 0, 10 do
- writeAt(((i == 3 or i == 7) and '------+------+------' or ' | | '), 16, i + 4)
- end
- -- draw the first line moves
- for i = 1, 3 do
- if board[i] ~= ' ' then
- writeAt((board[i] == 'x' and '\\/' or '/\\'), 18+((i-1)*7), 5)
- writeAt((board[i] == 'x' and '/\\' or '\\/'), 18+((i-1)*7), 6)
- end
- end
- -- draw the second line moves
- for i = 1, 3 do
- if board[i + 3] ~= ' ' then
- writeAt((board[i + 3] == 'x' and '\\/' or '/\\'), 18+((i-1)*7), 9)
- writeAt((board[i + 3] == 'x' and '/\\' or '\\/'), 18+((i-1)*7), 10)
- end
- end
- -- draw the third line moves
- for i = 1, 3 do
- if board[i + 6] ~= ' ' then
- writeAt((board[i + 6] == 'x' and '\\/' or '/\\'), 18+((i-1)*7), 13)
- writeAt((board[i + 6] == 'x' and '/\\' or '\\/'), 18+((i-1)*7), 14)
- end
- end
- -- draw the current player
- term.setCursorPos(3, sh - 3)
- if not winner then
- writeWithFormat('&bCurrent Player: &f'..players[currentPlayer])
- end
- end
- local function main(arc, argv)
- clear()
- writeWithFormat('&0Welcome to CCTicTacToe by &8TheOriginal&3BIT&0\n\nPlease enter your name\n\n&4>>&0 ')
- players.x = read() or 'Player'
- -- setup the game, will later be used to
- reset()
- -- initial render
- render()
- -- game loop
- while not winner do
- processInput()
- update()
- render()
- -- highly unorthodox having something that isn't in input, update, render!
- -- print the winner info
- if winner then
- writeWithFormat('&f'..(winner == 'tie' and 'There was no winner :(&f' or players[winner]..'&f is the winner!'))
- -- allow the player to start a new game or quit
- writeAt("Press 'R' to play again, 'Q' to quit...", 3, sh - 1)
- while true do
- local _, k = os.pullEvent('key')
- if k == 16 then
- break
- elseif k == 19 then
- reset() -- reset the game
- render() -- render the new game ready to wait for input
- break
- end
- end
- os.pullEvent() -- remove the char event that would be waiting
- end
- end
- return true
- end
- -- create a terminal object with a non-advanced computer safe version of setting colors
- local oldTermObj = term.current()
- local termObj = {
- setTextColor = function(n) if term.isColor and term.isColor() then local ok, err = pcall(oldTermObj.setTextColor , n) if not ok then error(err, 2) end end end,
- setBackgroundColor = function(n) if term.isColor and term.isColor() then local ok, err = pcall(oldTermObj.setBackgroundColor , n) if not ok then error(err, 2) end end end
- }
- -- also override the English spelling of the colour functions
- termObj.setTextColour = termObj.setTextColor
- termObj.setBackgroundColour = termObj.setBackgroundColor
- -- make the terminal object refer to the native terminal for every other function
- termObj.__index = oldTermObj
- setmetatable(termObj, termObj)
- -- redirect the terminal to the new object
- term.redirect(termObj)
- -- run the program
- local ok, err = pcall(main, #{...}, {...})
- -- catch-all
- if not ok and err ~= 'Terminated' then
- clear()
- print('Error in runtime!')
- print(err)
- sleep(5)
- end
- -- print thank you message
- clear()
- cwriteWithFormat('&4Thank you for playing CCTicTacToe v1.0', 1)
- cwriteWithFormat('&4By &8TheOriginal&3BIT\n', 2)
- -- restore the default terminal object
- term.redirect( oldTermObj )
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement