Guest User

cc2048

a guest
Apr 3rd, 2014
608
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 13.84 KB | None | 0 0
  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.                 os.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("E(x)it")
  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 = os.pullEvent("monitor_touch") --Wait for click after intro
  370.         if x == termWidth and y == 1 then --If quit was pressed
  371.             error() --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 = os.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.             error() --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.     os.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 = os.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.         os.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.         os.pullEvent("key")
  493.     end
  494. end
Advertisement
Add Comment
Please, Sign In to add comment