Advertisement
XxLen_KagaminexX

tictactoe

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