Advertisement
theoriginalbit

CCTicTacToe

Apr 25th, 2013
546
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 12.46 KB | None | 0 0
  1. --[[
  2. Author: theoriginalbit
  3. Version: 1.2 for ComputerCraft 1.6+
  4. Created: 26 APR 2013
  5. Last Update: 18 OCT 2014
  6.  
  7. Licensed under the MIT License [http://opensource.org/licenses/MIT]
  8. Copyright © 2013 Joshua Asbury a.k.a theoriginalbit [theoriginalbit@gmail.com]
  9. --]]
  10.  
  11. -- make sure that its only a computer terminal that is displaying
  12. local sw, sh = term.getSize()
  13.  
  14. if sw ~= 51 and sh ~= 19 then
  15.   error("Sorry this game can only run on computers", 0)
  16. end
  17.  
  18. -- the wining directions
  19. local winCombos = {
  20.   -- horizontal
  21.   {1,2,3}, {4,5,6}, {7,8,9},
  22.   -- vertical
  23.   {1,4,7}, {2,5,8}, {3,6,9},
  24.   -- diagonal
  25.   {1,5,9}, {3,5,7}
  26. }
  27.  
  28. local players = {x = 'Player', o = 'The Computer'}
  29. -- whether an AI is active, could be used later to allow SP
  30. local activeAI = true
  31. local currentPlayer
  32. local opposites = { x = 'o', o = 'x' }
  33. local board
  34. local winner
  35. local move
  36. local allowedBgColors = { colors.orange, colors.lightBlue, colors.gray, colors.cyan, colors.purple, colors.blue, colors.brown, colors.green, colors.red, colors.black }
  37. local bg
  38.  
  39. local function clear(col)
  40.   term.setBackgroundColor(col or colors.black)
  41.   term.clear()
  42.   term.setCursorPos(1,1)
  43. end
  44.  
  45. -- function thanks to Mads... found here: http://www.computercraft.info/forums2/index.php?/topic/11771-print-coloured-text-easily/page__p__105389#entry105389
  46. local function writeWithFormat(...)
  47.   local s = "&0"
  48.   for k, v in ipairs(arg) do
  49.     s = s .. v
  50.   end
  51.   s = s .. "&0"
  52.   local fields = {}
  53.   local lastcolor, lastpos = "0", 0
  54.   for pos, clr in s:gmatch"()&(%x)" do
  55.     table.insert(fields, {s:sub(lastpos + 2, pos - 1), lastcolor})
  56.     lastcolor, lastpos = clr , pos
  57.   end
  58.   for i = 2, #fields do
  59.     term.setTextColor(2 ^ (tonumber(fields[i][2], 16)))
  60.     write(fields[i][1])
  61.   end
  62. end
  63.  
  64. -- modification of Mads' function to get the length of the string without the color modifiers
  65. local function countFormatters(text)
  66.   return #(text:gsub("()&(%x)", ''))
  67. end
  68.  
  69. -- print a color formatted string in the center of the screen
  70. local function cwriteWithFormat(text, y)
  71.   local sw,sh = term.getSize()
  72.   local _,cy = term.getCursorPos()
  73.   term.setCursorPos(math.floor((sw-countFormatters(text))/2)+(countFormatters(text) % 2 == 0 and 1 or 0), y or cy)
  74.   writeWithFormat(text)
  75. end
  76.  
  77. -- writes the text at the give location
  78. local function writeAt(text, x, y)
  79.   local _,cy = term.getCursorPos()
  80.   term.setCursorPos(x or 1, y or cy)
  81.   write(text)
  82. end
  83.  
  84. local function reset()
  85.   bg = allowedBgColors[math.random(1, #allowedBgColors)]
  86.   currentPlayer = 'x'
  87.   board = {}
  88.   for i = 1, 9 do
  89.     board[i] = ' '
  90.   end
  91.   winner = nil
  92.   move = nil
  93. end
  94.  
  95. local function search(match)
  96.   for _, check in ipairs(winCombos) do
  97.     if board[check[1]] == board[check[2]] and board[check[1]] == match and board[check[3]] == ' ' then
  98.       return check[3]
  99.     elseif board[check[1]] == board[check[3]] and board[check[1]] == match and board[check[2]] == ' ' then
  100.       return check[2]
  101.     elseif board[check[2]] == board[check[3]] and board[check[2]] == match and board[check[1]] == ' ' then
  102.       return check[1]
  103.     end
  104.   end
  105. end
  106.  
  107. local function getAIMove()
  108.   -- make it seem like the computer actually has to think about its move
  109.   sleep(0.8)
  110.  
  111.   -- 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
  112.   return (search(currentPlayer) or search(opposites[currentPlayer])) or math.random(1,9)
  113. end
  114.  
  115. local function modread( _mask, _history, _limit )
  116.   term.setCursorBlink(true)
  117.  
  118.   local input = ""
  119.   local pos = 0
  120.   if _mask then
  121.     _mask = _mask:sub(1,1)
  122.   end
  123.   local historyPos = nil
  124.  
  125.   local sw, sh = term.getSize()
  126.   local sx, sy = term.getCursorPos()
  127.  
  128.   local function redraw( _special )
  129.     local scroll = (sx + pos >= sw and (sx + pos) - sw or 0)
  130.     local replace = _special or _mask
  131.     term.setCursorPos( sx, sy )
  132.     term.write( replace and string.rep(replace, #input - scroll) or input:sub(scroll + 1) )
  133.     term.setCursorPos( sx + pos - scroll, sy )
  134.   end
  135.  
  136.   while true do
  137.     local event = {os.pullEvent()}
  138.     if event[1] == 'char' and (not _limit or #input < _limit) then
  139.       input = input:sub(1, pos)..event[2]..input:sub(pos + 1)
  140.       pos = pos + 1
  141.     elseif event[1] == 'key' then
  142.       if event[2] == keys.enter then
  143.         break
  144.       elseif event[2] == keys.backspace and pos > 0 then
  145.         redraw(' ')
  146.         input = input:sub(1, pos - 1)..input:sub(pos + 1)
  147.         pos = pos - 1
  148.       elseif event[2] == keys.delete and pos < #input then
  149.         redraw(' ')
  150.         input = input:sub(1, pos)..input:sub(pos + 2)
  151.       elseif event[2] == keys.home then
  152.         pos = 0
  153.       elseif event[2] == keys['end'] then
  154.         pos = #input
  155.       elseif event[2] == keys.left and pos > 0 then
  156.         pos = pos - 1
  157.       elseif event[2] == keys.right and pos < #input then
  158.         pos = pos + 1
  159.       elseif _history and event[2] == keys.up or event[2] == keys.down then
  160.         redraw(' ')
  161.         if event[2] == keys.up then
  162.           if not historyPos then
  163.             historyPos = #_history
  164.           elseif historyPos > 1 then
  165.             historyPos = historyPos - 1
  166.           end
  167.         else
  168.           if historyPos ~= nil and historyPos < #_history then
  169.             historyPos = historyPos + 1
  170.           elseif historyPos == #_history then
  171.             historyPos = nil
  172.           end
  173.         end
  174.  
  175.         if historyPos then
  176.           input = string.sub(_history[historyPos], 1, _limit) or ""
  177.           pos = #input
  178.         else
  179.           input = ""
  180.           pos = 0
  181.         end
  182.       end
  183.     elseif event[1] == 'mouse_click' then
  184.       local xPos, yPos = event[3], event[4]
  185.       if xPos == sw and yPos == 1 then
  186.         -- exit and make sure to fool the catch-all
  187.         error('Terminated', 0)
  188.       end
  189.       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
  190.       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
  191.       local ret = (col - 1) * 3 + row
  192.       if ret >= 1 and ret <= 9 then
  193.         return ret
  194.       end
  195.     end
  196.  
  197.     redraw(_mask)
  198.   end
  199.  
  200.   term.setCursorBlink(false)
  201.   term.setCursorPos(1, sy + 1)
  202.  
  203.   return input
  204. end
  205.  
  206. local function getHumanMove()
  207.   writeWithFormat('&b[1-9] >>&f ')
  208.   return modread()
  209. end
  210.  
  211. local function processInput()
  212.   -- set the cursor pos ready for the input
  213.   term.setCursorPos(3, sh-1)
  214.   move = (currentPlayer == 'x' and getHumanMove or getAIMove)()
  215. end
  216.  
  217. local function output(msg)
  218.   -- if the player is not an AI, print the error
  219.   if not (activeAI and currentPlayer == 'o') then
  220.     term.setCursorPos(3, sh-1)
  221.     writeWithFormat('&eERROR >> '..msg)
  222.     sleep(2)
  223.   end
  224. end
  225.  
  226. local function checkMove()
  227.   -- if the user typed exit
  228.   if not tonumber(move) and move:lower() == 'exit' then
  229.     -- exit and make sure to fool the catch-all
  230.     error('Terminated', 0)
  231.   end
  232.  
  233.   -- attempt to convert the move to a number
  234.   local nmove = tonumber(move)
  235.   -- if it wasn't a number
  236.   if not nmove then
  237.     output(tostring(move)..' is not a number between 1 and 9!')
  238.     return false
  239.   end
  240.   -- if it is not within range of the board
  241.   if nmove > 9 or nmove < 1 then
  242.     output('Must be a number between 1 and 9!')
  243.     return false
  244.   end
  245.   -- if the space is already taken
  246.   if board[nmove] ~= ' ' then
  247.     output('Position already taken!')
  248.     return false
  249.   end
  250.   -- keep the conversion
  251.   move = tonumber(move)
  252.   return true
  253. end
  254.  
  255. local function checkWin()
  256.   for _, check in ipairs(winCombos) do
  257.     if board[check[1]] ~= ' ' and board[check[1]] == board[check[2]] and board[check[1]] == board[check[3]] then
  258.       return board[check[1]]
  259.     end
  260.   end
  261.  
  262.   for _, tile in ipairs(board) do
  263.     if tile == ' ' then
  264.       return nil
  265.     end
  266.   end
  267.  
  268.   return 'tie'
  269. end
  270.  
  271. local function update()
  272.   if checkMove() then
  273.     board[move] = currentPlayer
  274.     winner = checkWin()
  275.  
  276.     currentPlayer = currentPlayer == 'x' and 'o' or 'x'
  277.   end
  278. end
  279.  
  280. local function render()
  281.   -- clear the screen light blue
  282.   clear(bg)
  283.  
  284.   -- draw the ascii borders
  285.   term.setTextColor(colors.white)
  286.   for i = 2, sh-1 do
  287.     writeAt('|', 1, i)
  288.     writeAt('|', sw, i)
  289.   end
  290.   writeAt('+'..string.rep('-', sw-2)..'+', 1, 1)
  291.   writeAt('+'..string.rep('-', sw-2)..'+', 1, 3)
  292.   writeAt('+'..string.rep('-', sw-2)..'+', 1, sh-2)
  293.   writeAt('+'..string.rep('-', sw-2)..'+', 1, sh)
  294.  
  295.   if term.isColor and term.isColor() then
  296.     term.setCursorPos(sw, 1)
  297.     term.setBackgroundColor(colors.red)
  298.     term.setTextColor(colors.black)
  299.     writeWithFormat('X')
  300.   end
  301.  
  302.   -- set our colours
  303.   term.setBackgroundColor(colors.white)
  304.   term.setTextColor(colors.black)
  305.  
  306.   -- clear an area for the title
  307.   writeAt(string.rep(' ', sw-2), 2, 2)
  308.   writeAt('Tic-Tac-Toe!', sw/2-5, 2)
  309.  
  310.   -- clear an area for the input
  311.   writeAt(string.rep(' ', sw-2), 2, sh-1)
  312.  
  313.   -- clear the area for the board
  314.   local h = sh - 6
  315.   for i = 0, h - 1 do
  316.     writeAt(string.rep(' ', sw - 2), 2, 4+i)
  317.   end
  318.  
  319.   -- draw the grid
  320.   for i = 0, 10 do
  321.     writeAt(((i == 3 or i == 7) and '------+------+------' or '      |      |      '), 16, i + 4)
  322.   end
  323.  
  324.   -- draw the first line moves
  325.   for i = 1, 3 do
  326.     if board[i] ~= ' ' then
  327.       writeAt((board[i] == 'x' and '\\/' or '/\\'), 18+((i-1)*7), 5)
  328.       writeAt((board[i] == 'x' and '/\\' or '\\/'), 18+((i-1)*7), 6)
  329.     end
  330.   end
  331.   -- draw the second line moves
  332.   for i = 1, 3 do
  333.     if board[i + 3] ~= ' ' then
  334.       writeAt((board[i + 3] == 'x' and '\\/' or '/\\'), 18+((i-1)*7), 9)
  335.       writeAt((board[i + 3] == 'x' and '/\\' or '\\/'), 18+((i-1)*7), 10)
  336.     end
  337.   end
  338.   -- draw the third line moves
  339.   for i = 1, 3 do
  340.     if board[i + 6] ~= ' ' then
  341.       writeAt((board[i + 6] == 'x' and '\\/' or '/\\'), 18+((i-1)*7), 13)
  342.       writeAt((board[i + 6] == 'x' and '/\\' or '\\/'), 18+((i-1)*7), 14)
  343.     end
  344.   end
  345.  
  346.   -- draw the current player
  347.   term.setCursorPos(3, sh - 3)
  348.   if not winner then
  349.     writeWithFormat('&bCurrent Player: &f'..players[currentPlayer])
  350.   end
  351. end
  352.  
  353. local function main(arc, argv)
  354.   clear()
  355.   writeWithFormat('&0Welcome to CCTicTacToe by &8TheOriginal&3BIT&0\n\nPlease enter your name\n\n&4>>&0 ')
  356.   players.x = read() or 'Player'
  357.  
  358.   -- setup the game, will later be used to
  359.   reset()
  360.  
  361.   -- initial render
  362.   render()
  363.  
  364.   -- game loop
  365.   while not winner do
  366.     processInput()
  367.     update()
  368.     render()
  369.  
  370.     -- highly unorthodox having something that isn't in input, update, render!
  371.     -- print the winner info
  372.     if winner then
  373.       writeWithFormat('&f'..(winner == 'tie' and 'There was no winner :(&f' or players[winner]..'&f is the winner!'))
  374.       -- allow the player to start a new game or quit
  375.       writeAt("Press 'R' to play again, 'Q' to quit...", 3, sh - 1)
  376.       while true do
  377.         local _, k = os.pullEvent('key')
  378.         if k == 16 then
  379.           break
  380.         elseif k == 19 then
  381.           reset() -- reset the game
  382.           render() -- render the new game ready to wait for input
  383.           break
  384.         end
  385.       end
  386.       os.pullEvent() -- remove the char event that would be waiting
  387.     end
  388.   end
  389.  
  390.   return true
  391. end
  392.  
  393. -- create a terminal object with a non-advanced computer safe version of setting colors
  394. local oldTermObj = term.current()
  395. local termObj = {
  396.   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,
  397.   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
  398. }
  399. -- also override the English spelling of the colour functions
  400. termObj.setTextColour = termObj.setTextColor
  401. termObj.setBackgroundColour = termObj.setBackgroundColor
  402.  
  403. -- make the terminal object refer to the native terminal for every other function
  404. termObj.__index = oldTermObj
  405. setmetatable(termObj, termObj)
  406.  
  407. -- redirect the terminal to the new object
  408. term.redirect(termObj)
  409.  
  410. -- run the program
  411. local ok, err = pcall(main, #{...}, {...})
  412.  
  413. -- catch-all
  414. if not ok and err ~= 'Terminated' then
  415.   clear()
  416.   print('Error in runtime!')
  417.   print(err)
  418.   sleep(5)
  419. end
  420.  
  421. -- print thank you message
  422. clear()
  423. cwriteWithFormat('&4Thank you for playing CCTicTacToe v1.0', 1)
  424. cwriteWithFormat('&4By &8TheOriginal&3BIT\n', 2)
  425.  
  426. -- restore the default terminal object
  427. term.redirect( oldTermObj )
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement