5bitesofcookies

2028 by AlmtyBob for Sapphari

May 16th, 2014
145
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. SCORE_Y = 1         --Y value to draw the score text; it'll be centered on the X axis
  2. CONTROLS_Y = 18     --Y value to draw RESET button; it'll be centered on the X axis
  3. ANIMATION_TIME = 0.1--Animation sleep time, lower is faster
  4.  
  5. m = nil --Monitor peripheral, if connected
  6.  
  7. function getMonitor()
  8.     --Sets m to equal an attached monitor if available and large enough.
  9.     --Returns true if a monitor is found
  10.     local i = 1
  11.     local sides = rs.getSides() --Returns a list of sides (e.g. "left", "back", etc)
  12.     while i <= #sides and m == nil do --Loop every side or until a monitor is found
  13.         if peripheral.getType(sides[i]) == "monitor" then
  14.             local w, h = peripheral.call(sides[i], "getSize")
  15.             if w >= 18 and h >= 19 then --Attached monitor must be at least 18x19
  16.                 print("Monitor detected.  Outputting game to "..sides[i].." monitor.")
  17.                 m = peripheral.wrap(sides[i])
  18.                 return true
  19.             else
  20.                 print("Monitor size "..w.."x"..h.." detected but at least 18x19 is required.")
  21.                 print("Press any key to continue...")
  22.                 sapphUtils.pullEvent("key") --Wait for a keypress
  23.                 term.clear()
  24.             end
  25.         end
  26.         i = i + 1
  27.     end
  28.    
  29.     return false
  30. end
  31.  
  32. function drawScreen()
  33.     --Draws all screen elements.  Use drawScore() and drawGrid() to refresh just those
  34.     m.setBackgroundColor(colors.black)
  35.     m.clear()
  36.    
  37.     --Draw the border
  38.     m.setBackgroundColor(colors.white)
  39.     for x = tlGridX - 1, tlGridX + 16 do --Top & bottom borders
  40.         m.setCursorPos(x, tlGridY - 1)
  41.         m.write(" ")
  42.         m.setCursorPos(x, tlGridY - 1 + 13)
  43.         m.write(" ")
  44.     end
  45.     for y = tlGridY - 1, tlGridY + 12 do --Left & right borders
  46.         m.setCursorPos(tlGridX - 1, y)
  47.         m.write(" ")
  48.         m.setCursorPos(tlGridX - 1 +  17, y)
  49.         m.write(" ")
  50.     end
  51.    
  52.     if isValidMonitorConnected then
  53.         --Draw reset button
  54.         m.setBackgroundColor(colors.orange)
  55.         m.setTextColor(colors.white)
  56.         m.setCursorPos(termWidth / 2 - string.len("RESET") / 2 + 1, CONTROLS_Y)
  57.         m.write("RESET")
  58.        
  59.         --Draw quit "X" at top right
  60.         m.setBackgroundColor(colors.red)
  61.         m.setCursorPos(termWidth, 1)
  62.         m.write("X")
  63.     else
  64.         --Draw control description
  65.         m.setBackgroundColor(colors.black)
  66.         m.setTextColor(colors.white)
  67.         m.setCursorPos(1, 8)
  68.         m.write("Controls:")
  69.         m.setCursorPos(1, 9)
  70.         m.write("Arrow keys")
  71.         m.setCursorPos(1, 10)
  72.         m.write("(R)eset")
  73.         m.setCursorPos(1, 11)
  74.         m.write("Switch site - X")
  75.     end
  76.    
  77.     drawScore()
  78.  
  79.     drawGrid()
  80. end
  81.  
  82. function drawScore()
  83.     m.setBackgroundColor(colors.black)
  84.     m.setTextColor(colors.white)
  85.     scoreText = "Score: "..score
  86.     m.setCursorPos(termWidth / 2 - string.len(scoreText) / 2 + 1, SCORE_Y) --Center the score on the X axis
  87.     m.write(scoreText) 
  88. end
  89.  
  90. function drawGrid()
  91.     for r = 0, 3 do --Row
  92.         for c = 0, 3 do --Column
  93.             drawSquare(r, c, grid[r][c])
  94.         end
  95.     end
  96. end
  97.  
  98. function drawSquare(r, c, value, randomColor)
  99.     --Draws an individual square on the board
  100.     --Board squares are 4 wide and 3 tall so multiply c & r values respectively
  101.     --randomColor and -1 value used for intro sequence only
  102.     if value == 0 then --Black out the square
  103.         m.setBackgroundColor(colors.black)
  104.         m.setCursorPos(c * 4 + tlGridX, r * 3 + tlGridY)
  105.         m.write("    ")
  106.         m.setCursorPos(c * 4 + tlGridX, r * 3 + 1 + tlGridY)
  107.         m.write("    ")
  108.         m.setCursorPos(c * 4 + tlGridX, r * 3 + 2 + tlGridY)
  109.         m.write("    ")
  110.     elseif value == -1 then --Only used for rendering the 0, which normally is invisible, during the intro sequence
  111.         if not randomColor then
  112.             m.setBackgroundColor(colors.green)
  113.         else
  114.             m.setBackgroundColor(2^math.random(2, 14)) --Any color but white or black
  115.         end
  116.         m.setCursorPos(c * 4 + tlGridX, r * 3 + tlGridY)
  117.         m.write("    ")
  118.         m.setCursorPos(c * 4 + tlGridX, r * 3 + 1 + tlGridY)
  119.         m.write("  0 ")
  120.         m.setCursorPos(c * 4 + tlGridX, r * 3 + 2 + tlGridY)
  121.         m.write("    ")    
  122.     else
  123.         if not randomColor then
  124.             m.setBackgroundColor(value)
  125.         else
  126.             m.setBackgroundColor(2^math.random(2, 14)) --Any color but white or black
  127.         end
  128.         m.setCursorPos(c * 4 + tlGridX, r * 3 + tlGridY)
  129.         m.write("    ")
  130.         m.setCursorPos(c * 4 + tlGridX, r * 3 + 1 + tlGridY)
  131.         --Center the number string within spaces (e.g. "  2 " or " 32 ")
  132.         local strlen = string.len(value)
  133.         if strlen == 1 then
  134.             m.write("  "..value.." ")
  135.         elseif strlen == 2 then
  136.             m.write(" "..value.." ")
  137.         elseif strlen == 3 then
  138.             m.write(" "..value)
  139.         else
  140.             m.write(tostring(value))
  141.         end
  142.         m.setCursorPos(c * 4 + tlGridX, r * 3 + 2 + tlGridY)
  143.         m.write("    ")            
  144.     end
  145. end
  146.  
  147. function clearGrid()
  148.     --Initializes the grid
  149.     for r = 0, 3 do
  150.         grid[r] = {}
  151.         for c = 0, 3 do
  152.             grid[r][c] = 0
  153.         end
  154.     end
  155. end
  156.  
  157. function placeNewTile()
  158.     --Places a 2 or 4 at a random empty location in the grid
  159.     local number
  160.    
  161.     if math.random(10) == 10 then --Place a 4 10% of the time, else 2
  162.         number = 4
  163.     else
  164.         number = 2
  165.     end
  166.     --Get a count of empty cells in the grid
  167.     local emptyCount = 0
  168.     for r = 0, 3 do
  169.         for c = 0, 3 do
  170.             if grid[r][c] == 0 then
  171.                 emptyCount = emptyCount + 1
  172.             end
  173.         end
  174.     end
  175.     --Choose an empty cell and place the number
  176.     local target = math.random(emptyCount)
  177.     emptyCount = 0
  178.     for r = 0, 3 do
  179.         for c = 0, 3 do
  180.             if grid[r][c] == 0 then
  181.                 emptyCount = emptyCount + 1
  182.                 if emptyCount == target then
  183.                     grid[r][c] = number
  184.                 end
  185.             end
  186.         end
  187.     end
  188. end
  189.  
  190. function transpose()
  191.     --[[ Turns rows into columns and vice-versa, like so:
  192.         0, 1, 2, 3,             0, 4, 8, C,
  193.         4, 5, 6, 7,             1, 5, 9, D,
  194.         8, 9, A, B,     becomes 2, 6, A, E,
  195.         C, D, E, F              3, 7, B, F
  196.     ]]--
  197.     local res = {}
  198.  
  199.     for r = 0, 3 do
  200.         res[r] = {}
  201.         for c = 0, 3 do
  202.             res[r][c] = grid[c][r]
  203.         end
  204.     end
  205.  
  206.     grid = res
  207. end
  208.  
  209. function rotateClockwise()
  210.     transpose() --Transposing then reversing the order of the rows is equivalent to rotating the grid clockwise
  211.     reverseRows()
  212. end
  213.  
  214. function rotateCounterClockwise()
  215.     transpose() --Transposing then reversing the order of the columns is equivalent to rotating the grid counterclockwise
  216.     reverseColumns()
  217. end
  218.  
  219. function reverseRows()
  220.     for r = 0, 3 do
  221.         grid[r][0], grid[r][3] = grid[r][3], grid[r][0] --Swap the first and fourth values
  222.         grid[r][1], grid[r][2] = grid[r][2], grid[r][1] --Swap the second and third values
  223.     end
  224. end
  225.  
  226. function reverseColumns()
  227.     for c = 0, 3 do
  228.         grid[0][c], grid[3][c] = grid[3][c], grid[0][c] --Swap the first and fourth values
  229.         grid[1][c], grid[2][c] = grid[2][c], grid[1][c] --Swap the second and third values
  230.     end
  231. end
  232.  
  233. function collapseLeft()
  234.     --For each empty (0) cell, shift the next non-zero number into the cell
  235.     --For each cell with a number, merge with an equal number to the right if possible
  236.     local hadMove = false
  237.     local tempC
  238.    
  239.     for r = 0, 3 do --For each row
  240.         for c = 0, 3 do --For each column in row r
  241.             if grid[r][c] == 0 then
  242.                 --Find the next non-zero and shift it here
  243.                 tempC = c + 1
  244.                 while tempC < 4 and grid[r][tempC] == 0 do
  245.                     tempC = tempC + 1
  246.                 end
  247.                 if tempC < 4 then
  248.                     hadMove = true
  249.                     grid[r][c] = grid[r][tempC]
  250.                     grid[r][tempC] = 0
  251.                 end
  252.             end
  253.             --Check for merges
  254.             --If the next non-zero in the row matches this cell then merge
  255.             tempC = c + 1
  256.             while tempC < 4 and grid[r][tempC] == 0 do
  257.                 tempC = tempC + 1
  258.             end
  259.             if grid[r][c] == grid[r][tempC] then
  260.                 hadMove = true
  261.                 score = score + grid[r][c] + grid[r][tempC]
  262.                 grid[r][c] = grid[r][c] + grid[r][tempC]
  263.                 grid[r][tempC] = 0
  264.             end
  265.         end
  266.     end
  267.    
  268.     return hadMove
  269. end
  270.  
  271. function isGameOver()
  272.     --If there are any zeroes in the grid then there's a valid move
  273.     --Otherwise, if the cell to the right or underneath the current
  274.     --  cell has the same value then a merge is possible.
  275.     local r = 0
  276.     while r < 4 do
  277.         local c = 0
  278.         while c < 4 do
  279.             if grid[r][c] == 0 then
  280.                 return false
  281.             elseif r < 3 and grid[r][c] == grid[r+1][c] then
  282.                 return false
  283.             elseif c < 3 and grid[r][c] == grid[r][c+1] then
  284.                 return false
  285.             end
  286.             c = c + 1
  287.         end
  288.         r = r + 1
  289.     end
  290.    
  291.     return true
  292. end
  293.  
  294. function moveLeft()
  295.     return collapseLeft()
  296. end
  297.  
  298. function moveRight()
  299.     --Mirror the grid so it can be collapsed to the left, then mirror it back
  300.     reverseRows()
  301.     hadMove = collapseLeft()
  302.     reverseRows()
  303.     return hadMove
  304. end
  305.  
  306. function moveDown()
  307.     --Rotate the grid so it's down becomes left, collapse, revert
  308.     rotateClockwise()
  309.     hadMove = collapseLeft()
  310.     rotateCounterClockwise()
  311.     return hadMove
  312. end
  313.  
  314. function moveUp()
  315.     --Rotate the grid so it's up becomes left, collapse, revert
  316.     rotateCounterClockwise()
  317.     hadMove = collapseLeft()
  318.     rotateClockwise()
  319.     return hadMove
  320. end
  321.  
  322. function introShiftColumns(column, counter)
  323.     --Weird, hacky code to animate the intro sequence.
  324.     counter = counter - column
  325.     if counter >= 0 and counter < 3 then
  326.         grid[counter + 1][column] = grid[counter][column]
  327.         grid[counter][column] = 0
  328.     elseif counter >= 0 and counter < 5 then
  329.         grid[5 - counter][column] = grid[5 - counter + 1][column]
  330.         grid[5 - counter + 1][column] = 0      
  331.     end
  332. end
  333.  
  334. function intro()
  335.     --This is a bit ugly
  336.     clearGrid()
  337.     for counter = 0, 8 do
  338.         if counter == 0 then
  339.             grid[0][0] = 2
  340.         elseif counter == 1 then
  341.             grid[0][1] = -1
  342.         elseif counter == 2 then
  343.             grid[0][2] = 4
  344.         elseif counter == 3 then
  345.             grid[0][3] = 8
  346.         end
  347.         drawScreen(true)
  348.         sleep(ANIMATION_TIME)
  349.         for i = 0, 3 do
  350.             introShiftColumns(i, counter)
  351.         end
  352.     end
  353.     --Color cycle
  354.     for counter = 0, 10 do
  355.         drawSquare(1, 0, 2, true)
  356.         drawSquare(1, 1, -1, true)
  357.         drawSquare(1, 2, 4, true)
  358.         drawSquare(1, 3, 8, true)
  359.         sleep(ANIMATION_TIME)
  360.     end
  361.     m.setBackgroundColor(colors.black)
  362.     if isValidMonitorConnected then
  363.         m.setCursorPos(termWidth / 2 - string.len("Touch to begin") / 2 + 1, tlGridY + 7)
  364.         m.write("Touch to begin")
  365.         m.setCursorPos(tlGridX + 1, tlGridY + 9)
  366.         m.write("Controls: Click")
  367.         m.setCursorPos(tlGridX + 1, tlGridY + 10)
  368.         m.write("a side to move.")
  369.         _, _, x, y = sapphUtils.pullEvent("monitor_touch") --Wait for click after intro
  370.         if x == termWidth and y == 1 then --If quit was pressed
  371.         while true do sapphUtils.pullEvent() end     --Ghetto quit
  372.         end
  373.     else
  374.         m.setCursorPos(termWidth / 2 - string.len("Press any key...") / 2 + 1, tlGridY + 7)
  375.         m.write("Press any key...")    
  376.         _, keycode = sapphUtils.pullEvent("key") --Wait for keypress after intro
  377.         if keys.getName(keycode) == "x" then --If quit was pressed
  378.             m.setBackgroundColor(colors.black)
  379.             m.clear()
  380.             while true do sapphUtils.pullEvent() end --Ghetto quit
  381.         end
  382.     end
  383. end
  384.        
  385.  --
  386.  --MAIN GAME LOOP
  387.  --
  388. grid = {} --Main 2D array for the game grid
  389. score = 0 --Score is the sum of all merges performed during a game
  390.  
  391. isValidMonitorConnected = getMonitor() --Detect a monitor for output instead of the term if one is connected
  392. if isValidMonitorConnected then
  393.     termWidth, termHeight = m.getSize()
  394.     tlGridX = termWidth / 2 - 7 --Top left X coord of the game grid (not including borders)
  395.     tlGridY = 3 --Top left Y coord of the game grid (not including borders)
  396. else
  397.     m = term --Use the computer's terminal as output
  398.     termWidth, termHeight = term.getSize()
  399.     tlGridX = termWidth / 2 - 7
  400.     tlGridY = 3
  401. end
  402.  
  403. if not m.isColor() then --Color monitors/computers only
  404.     print("An advanced monitor or computer is required.")
  405.     print("Press any key to exit...")
  406.     sapphUtils.pullEvent("key")
  407.     return
  408. end
  409.  
  410. while true do
  411.     intro() --Comment this out to disable the intro.  I won't judge
  412.     clearGrid() --Initialize the game board array
  413.    
  414.     --Place the initial two tiles
  415.     score = 0
  416.     placeNewTile()
  417.     placeNewTile()
  418.     drawScreen()
  419.        
  420.     --Begin main round loop
  421.     gameOver = false
  422.     hadMove = false
  423.     while not gameOver do
  424.         hadMove = false
  425.         if isValidMonitorConnected then --Use touch if a monitor is being used
  426.             --Detect a click event for the buttons
  427.             _, _, xT, yT = sapphUtils.pullEvent("monitor_touch")
  428.             --If a direction button was pressed
  429.             if yT >= tlGridY - 1 and yT <= tlGridY + 1 and xT >= tlGridX + 4 and xT <= tlGridX + 11 then
  430.                 hadMove = moveUp()
  431.             elseif yT >= tlGridY + 9 and yT <= tlGridY + 12 and xT >= tlGridX + 4 and xT <= tlGridX + 11 then
  432.                 hadMove = moveDown()
  433.             elseif yT >= tlGridY + 3 and yT <= tlGridY + 8 and xT >= tlGridX - 1 and xT <= tlGridX + 3 then
  434.                 hadMove = moveLeft()
  435.             elseif yT >= tlGridY + 3 and yT <= tlGridY + 8 and xT >= tlGridX + 12 and xT <= tlGridX + 16 then                  
  436.                 hadMove = moveRight()
  437.             elseif yT == CONTROLS_Y and xT >= (termWidth / 2 - string.len("RESET") / 2 + 1) and xT <= termWidth / 2 - string.len("RESET") / 2 + 1 + string.len("RESET") then
  438.                 hadMove = false
  439.                 gameOver = true --Reset button hit
  440.             elseif yT == 1 and xT == termWidth then
  441.                 m.setBackgroundColor(colors.black)
  442.                 m.clear()
  443.                 return --Quit
  444.             end
  445.         else --Use keyboard controls if playing directly on a computer
  446.             _, keycode = os.pullEvent("key")
  447.             if keys.getName(keycode) == "up" then
  448.                 hadMove = moveUp()
  449.             elseif keys.getName(keycode) == "down" then
  450.                 hadMove = moveDown()
  451.             elseif keys.getName(keycode) == "left" then
  452.                 hadMove = moveLeft()
  453.             elseif keys.getName(keycode) == "right" then
  454.                 hadMove = moveRight()
  455.             elseif keys.getName(keycode) == "r" then --Reset
  456.                 hadMove = false
  457.                 gameOver = true
  458.             elseif keys.getName(keycode) == "x" then --Quit
  459.                 m.setBackgroundColor(colors.black)
  460.                 m.clear()
  461.                 m.setCursorPos(1,1)
  462.                 return --Quit
  463.             end
  464.         end
  465.        
  466.         if hadMove then
  467.             placeNewTile()
  468.             drawGrid()
  469.             drawScore()
  470.         end
  471.  
  472.         if isGameOver() then
  473.             gameOver = true
  474.         end
  475.     end
  476.    
  477.     --Draw game over
  478.     m.setTextColor(colors.white)
  479.     m.setBackgroundColor(colors.red)
  480.     m.setCursorPos(termWidth / 2 - string.len("GAME OVER") / 2 + 1, CONTROLS_Y - 1)
  481.     m.write("GAME OVER")
  482.     m.setBackgroundColor(colors.black)
  483.     if isValidMonitorConnected then
  484.         m.setCursorPos(termWidth / 2 - string.len("Touch to reset...") / 2 + 1, CONTROLS_Y)
  485.         m.write("Touch to reset...")
  486.         --Wait for any click to continue
  487.         sapphUtils.pullEvent("monitor_touch")
  488.     else
  489.         m.setCursorPos(termWidth / 2 - string.len("Press any key...") / 2 + 1, CONTROLS_Y)
  490.         m.write("Press any key...")
  491.         --Wait for any click to continue
  492.         sapphUtils.pullEvent("key")
  493.     end
  494. end
  495. while true do
  496.   sapphUtils.pullEvent()
  497. --keeps sapphari on "life support"
  498. end
Advertisement
Add Comment
Please, Sign In to add comment