Dojnaz

BBTetris Always on

Mar 13th, 2018
351
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. -- +---------------------+------------+---------------------+
  2. -- |   ##      #     #   |            |  ##      #     ##   |
  3. -- |    ##     #     ##  |  BBTetris  |  ##      ##     #   |
  4. -- |          ##     #   |    ####    |           #     #   |
  5. -- +---------------------+------------+---------------------+
  6.  
  7. local version = "Version 1.0.6"
  8.  
  9. -- Yet another version of Tetris, by Jeffrey Alexander (aka Bomb Bloke).
  10. -- Heavily based on the old "Brick Game"s you may've once seen.
  11. -- http://www.computercraft.info/forums2/index.php?/topic/15878-bbtetris
  12. -- Automatically goes into monitor mode, added by Hannez04
  13.  
  14.  
  15. ---------------------------------------------
  16. ------------Variable Declarations------------
  17. ---------------------------------------------
  18.  
  19. -- Seven regular blocks, six "advanced" blocks, three "trick" blocks.
  20. local block = {
  21. {{1,1,0},{0,1,1}},{{0,1,0},{0,1,0},{0,1,0},{0,1,0}},{{0,1,1},{1,1,0}},{{1,1},{1,1}},{{1,1,1},{0,1,0},
  22. {0,0,0}},{{1,1,1},{0,0,1}},{{1,1,1},{1,0,0}},{{1,1,0},{0,1,0},{0,1,1}},{{1,1},{0,1}},{{1,0,0},{1,1,1},
  23. {1,0,0}},{{0,1,0},{1,1,1},{0,1,0}},{{1},{1}},{{1,1,1},{1,0,1}},{{1}},{{1},{1}},{{1},{1},{1}}}
  24.  
  25. -- The menu numerals. Eight in all.
  26. local number = {
  27. {{5,1},{4,1,1},{5,1},{5,1},{5,1},{5,1},{4,1,1,1}},{{4,1,1,1},{3,1,0,0,0,1},{7,1},{6,1},{5,1},{4,1},
  28. {3,1,1,1,1,1}},{{4,1,1,1},{3,1,0,0,0,1},{7,1},{4,1,1,1},{7,1},{3,1,0,0,0,1},{4,1,1,1}},{{6,1},{5,1,1},
  29. {4,1,0,1},{3,1,0,0,1},{3,1,0,0,1},{3,1,1,1,1,1},{6,1}},{{3,1,1,1,1,1},{3,1},{3,1,1,1,1},{7,1},{7,1},
  30. {3,1,0,0,0,1},{4,1,1,1}},{{4,1,1,1},{3,1,0,0,0,1},{3,1},{3,1,1,1,1},{3,1,0,0,0,1},{3,1,0,0,0,1},
  31. {4,1,1,1}},{{3,1,1,1,1,1},{7,1},{6,1},{5,1},{5,1},{4,1},{4,1}},{{4,1,1,1},{3,1,0,0,0,1},{3,1,0,0,0,1},
  32. {4,1,1,1},{3,1,0,0,0,1},{3,1,0,0,0,1},{4,1,1,1}}}
  33.  
  34. local gamemode, speed, level, running, grid, saves, canSave, playMusic, pocket, depth = 1, 0, 0, true, {}, {}, true, true
  35. local highScore, screenx, screeny, myEvent, mon, OGeventPuller, skipIntro, skipMonitor, defaultMonitor
  36.  
  37. local musicFile = "moarp/songs/Tetris A Theme.nbs"
  38.  
  39. ---------------------------------------------
  40. ------------Function Declarations------------
  41. ---------------------------------------------
  42.  
  43. -- Return to shell.
  44. local function exitGame(erroring)
  45.   if canSave then
  46.     myEvent = fs.open(shell.resolve(".").."\\bbtetris.dat", "w")
  47.     myEvent.writeLine("Save data file for Bomb Bloke's Tetris game.")
  48.     myEvent.writeLine(string.gsub(textutils.serialize(highScore),"\n%s*",""))
  49.     myEvent.writeLine(string.gsub(textutils.serialize(saves),"\n%s*",""))
  50.     myEvent.close()
  51.   end
  52.  
  53.   term.setBackgroundColor(colours.black)
  54.   term.setTextColor(colours.white)
  55.  
  56.   if mon then
  57.     term.clear()
  58.     if term.restore then term.restore() else term.redirect(mon.restoreTo) end
  59.   end
  60.  
  61.   if note then os.unloadAPI("note") end
  62.  
  63.   term.clear()
  64.   term.setCursorPos(1,1)
  65.   if erroring then print(erroring.."\n") end  
  66.   print("Thanks for playing!")
  67.  
  68.   if (shell.resolve(".") == "disk") and not fs.exists("\\bbtetris") then
  69.     print("\nIf you wish to copy me to your internal drive (for play without the disk - this allows saving of score data etc), type:\n\ncp \\disk\\bbtetris \\bbtetris\n")
  70.   end
  71.  
  72.   os.pullEvent = OGeventPuller
  73.   error()
  74. end
  75.  
  76. -- Writes regular text at the specified location on the screen.
  77. local function writeAt(text, x , y)
  78.   term.setBackgroundColor(colours.black)
  79.   term.setTextColor(colours.white)
  80.   term.setCursorPos(screenx+x,screeny+y)
  81.   term.write(text)
  82. end
  83.  
  84. -- Returns whether a given event was a touch event this program should listen to.
  85. local function touchedMe()
  86.   if myEvent[1] == "mouse_click" then return true
  87.   elseif myEvent[1] ~= "monitor_touch" or not mon then return false
  88.   else return mon.side == myEvent[2] end
  89. end
  90.  
  91. -- Returns whether one of a given set of keys was pressed.
  92. local function pressedKey(...)
  93.   if myEvent[1] ~= "key" then return false end
  94.   for i=1,#arg do if arg[i] == myEvent[2] then return true end end
  95.   return false
  96. end
  97.  
  98. -- Returns whether a click was performed at a given location.
  99. -- If two parameters are passed, it checks to see if x is [1] and y is [2].
  100. -- If three parameters are passed, it checks to see if x is [1] and y is between [2]/[3] (non-inclusive).
  101. -- If four paramaters are passed, it checks to see if x is between [1]/[2] and y is between [3]/[4] (non-inclusive).
  102. local function clickedAt(...)
  103.   if not touchedMe() then return false end
  104.   if #arg == 2 then return (myEvent[3] == arg[1]+screenx and myEvent[4] == arg[2]+screeny)
  105.   elseif #arg == 3 then return (myEvent[3] == arg[1]+screenx and myEvent[4] > arg[2]+screeny and myEvent[4] < arg[3]+screeny)
  106.   else return (myEvent[3] > arg[1]+screenx and myEvent[3] < arg[2]+screenx and myEvent[4] > arg[3]+screeny and myEvent[4] < arg[4]+screeny) end
  107. end
  108.  
  109. -- Ensures the wrapped monitor is suitable for play.
  110. local function enforceScreenSize()
  111.   term.setBackgroundColor(colours.black)
  112.   term.setTextColor(colours.white)
  113.  
  114.   while true do
  115.     local scale = 5
  116.     mon.setTextScale(scale)
  117.     screenx,screeny = term.getSize()
  118.  
  119.     term.clear()
  120.     term.setCursorPos(1,1)
  121.  
  122.     while (screenx < 50 or screeny < (depth or 19)) and scale > 0.5 do
  123.       scale = scale - 0.5
  124.       mon.setTextScale(scale)
  125.       screenx,screeny = term.getSize()
  126.     end
  127.  
  128.     if screenx > 49 and screeny > (depth or 19) - 1 then
  129.       screenx,screeny = math.floor(screenx/2)-10, math.floor(screeny/2)-9
  130.       sleep(0.1)
  131.       return
  132.     else print("Make this display out of at least three by two monitor blocks, or tap me to quit.") end
  133.    
  134.     while true do
  135.       myEvent = {os.pullEvent()}
  136.    
  137.       if myEvent[1] == "monitor_resize" then break
  138.       elseif myEvent[1] == "key" or touchedMe() or myEvent[1] == "terminate" then exitGame()
  139.       elseif myEvent[1] == "peripheral_detach" and mon then
  140.         if myEvent[2] == mon.side then exitGame("I've lost my monitor - your turtle didn't mine it, I hope?") end
  141.       elseif myEvent[1] == "musicFinished" then os.queueEvent("musicPlay",musicFile) end
  142.     end
  143.   end
  144. end
  145.  
  146. -- Draws the frame around the playing area, along with other static stuff.
  147. local function drawBorder()
  148.   term.setBackgroundColor(colours.black)
  149.   term.clear()
  150.  
  151.   writeAt("High",14,11)
  152.   writeAt("Level",14,14)
  153.   writeAt("Speed",14,17)
  154.  
  155.   if not pocket then
  156.     writeAt("[H]elp",-6,2)
  157.     writeAt("[ ] Advanced",22,14)
  158.     writeAt("[ ] Tricks",22,16)
  159.     writeAt("[ ] Quota",22,18)
  160.   end
  161.    
  162.   writeAt("[Q]uit",pocket and 21 or 22,2)
  163.  
  164.   term.setBackgroundColor(term.isColor() and colours.yellow or colours.white)
  165.   term.setTextColor(term.isColor() and colours.lightGrey or colours.black)
  166.  
  167.   term.setCursorPos(screenx+1,screeny)
  168.   term.write(string.rep("L",20))
  169.   term.setCursorPos(screenx+1,screeny+depth+1)
  170.   term.write(string.rep("L",20))
  171.   term.setCursorPos(screenx+13,screeny+6)
  172.   term.write(string.rep("L",7))
  173.  
  174.   for i=1,depth do
  175.     term.setCursorPos(screenx+1,screeny+i)
  176.     term.write("L")
  177.     term.setCursorPos(screenx+12,screeny+i)
  178.     term.write("L")
  179.     term.setCursorPos(screenx+20,screeny+i)
  180.     term.write("L")
  181.   end
  182. end
  183.  
  184. -- Draws the big numbers indicating the game mode on the main menu (plus associated data).
  185. local function drawNumeral()
  186.   term.setTextColor(term.isColor() and colours.lightGrey or colours.black)
  187.  
  188.   for i=1,7 do
  189.     term.setCursorPos(screenx+2,screeny+i+6)
  190.     term.setBackgroundColor(colours.black)
  191.     term.write(string.rep(" ",10))
  192.     term.setBackgroundColor(term.isColor() and colours.blue or colours.white)
  193.    
  194.     for j=2,#number[gamemode][i] do if number[gamemode][i][j] == 1 then
  195.       term.setCursorPos(screenx+number[gamemode][i][1]+j,screeny+i+6)
  196.       term.write("L")
  197.     end end
  198.   end
  199.  
  200.   term.setBackgroundColor(colours.black)
  201.   term.setTextColor(term.isColor() and colours.green or colours.white)
  202.  
  203.   if not pocket then for i=0,2 do
  204.     term.setCursorPos(screenx+23,screeny+14+i*2)
  205.     term.write(bit.band(gamemode-1,bit.blshift(1,i))==bit.blshift(1,i) and "O" or " ")
  206.   end end
  207.  
  208.   writeAt(string.rep(" ",6-#tostring(highScore[gamemode]))..tostring(highScore[gamemode]),13,12)
  209. end
  210.  
  211. -- Fill the grid with random blocks according to the chosen level.
  212. local function fillGrid()
  213.   term.setBackgroundColor(bit.band(gamemode-1,4)==4 and colours.red or colours.white)
  214.   term.setTextColor(term.isColor() and colours.lightGrey or colours.black)
  215.  
  216.   for i=1,level+(bit.band(gamemode-1,4)==4 and 1 or 0) do
  217.     grid[i] = {}
  218.     for j=1,10 do if math.random(2) > 1 then
  219.       term.setCursorPos(screenx+j+1,screeny+depth+1-i)
  220.       term.write("L")
  221.       grid[i][j] = bit.band(gamemode-1,4)==4 and 14 or 32
  222.     end end
  223.   end
  224.  
  225.   if bit.band(gamemode-1,4)==4 then for i=level+2,depth do grid[i] = {} end end
  226. end
  227.  
  228. -- Do the game over animation.
  229. local function gameover()
  230.   term.setBackgroundColor(term.isColor() and colours.black or colours.white)
  231.   term.setTextColor(term.isColor() and colours.lightGrey or colours.black)
  232.   for i=depth,1,-1 do
  233.     term.setCursorPos(screenx+2,screeny+i)
  234.     term.write(string.rep("L",10))
  235.     blockTimer = os.startTimer(0.1)
  236.     while myEvent[2] ~= blockTimer do myEvent = {os.pullEvent("timer")} end
  237.   end
  238.  
  239.   term.setBackgroundColor(colours.black)
  240.   for i=1,depth do
  241.     term.setCursorPos(screenx+2,screeny+i)
  242.     term.write(string.rep(" ",10))
  243.     blockTimer = os.startTimer(0.1)
  244.     while myEvent[2] ~= blockTimer do myEvent = {os.pullEvent("timer")} end
  245.   end
  246. end
  247.  
  248. -- Renders the block (or clears that area of the screen if not "drawing").
  249. local function drawBlock(thisBlock,rotation,xpos,ypos,drawing,flicker)
  250.   if thisBlock > 13 and not drawing then
  251.     term.setTextColor(term.isColor() and colours.lightGrey or colours.black)
  252.     for y=1,#block[thisBlock] do
  253.       term.setCursorPos(screenx+xpos+1,screeny+ypos+y-1)
  254.       if grid[depth+2-ypos-y][xpos] ~= nil then
  255.         term.setBackgroundColor(term.isColor() and bit.blshift(1,grid[depth+1-ypos][xpos]) or colours.white)
  256.         term.write("L")
  257.       else
  258.         term.setBackgroundColor(colours.black)
  259.         term.write(" ")
  260.       end
  261.     end
  262.     return
  263.   end
  264.  
  265.   if drawing and thisBlock > 13 then
  266.     term.setBackgroundColor(term.isColor() and (flicker and colours.white or colours.black) or (flicker and colours.black or colours.white))
  267.   else
  268.     term.setBackgroundColor(drawing and (term.isColor() and bit.blshift(1,thisBlock) or colours.white) or colours.black)
  269.   end
  270.  
  271.   term.setTextColor(term.isColor() and colours.lightGrey or (flicker and colours.white or colours.black))
  272.    
  273.   for y=1,#block[thisBlock] do for x=1,#block[thisBlock][1] do if block[thisBlock][y][x] == 1 then
  274.     if rotation == 0 then
  275.       term.setCursorPos(screenx+xpos+x,screeny+ypos+y-1)
  276.     elseif rotation == 1 then
  277.       term.setCursorPos(screenx+xpos+#block[thisBlock]+1-y,screeny+ypos+x-1)
  278.     elseif rotation == 2 then
  279.       term.setCursorPos(screenx+xpos+#block[thisBlock][1]+1-x,screeny+ypos+#block[thisBlock]-y)
  280.     elseif rotation == 3 then
  281.       term.setCursorPos(screenx+xpos+y,screeny+ypos+#block[thisBlock][1]-x)
  282.     end
  283.    
  284.     term.write(drawing and "L" or " ")
  285.   end end end
  286. end
  287.  
  288. -- Returns whether the block can move into the specified position.
  289. local function checkBlock(thisBlock,rotation,xpos,ypos)
  290.   if thisBlock == 14 then
  291.     if xpos < 1 or xpos > 10 then return false end
  292.     for y = 1,depth+1-ypos do if grid[y][xpos] == nil then return true end end
  293.     return false
  294.   end      
  295.  
  296.   local checkX, checkY
  297.  
  298.   for y=1,#block[thisBlock] do for x=1,#block[thisBlock][1] do if block[thisBlock][y][x] == 1 then
  299.     if rotation == 0 then
  300.       checkX, checkY = xpos+x-1, ypos+y-1
  301.     elseif rotation == 1 then
  302.       checkX, checkY = xpos+#block[thisBlock]-y, ypos+x-1
  303.     elseif rotation == 2 then
  304.       checkX, checkY = xpos+#block[thisBlock][1]-x, ypos+#block[thisBlock]-y
  305.     elseif rotation == 3 then
  306.       checkX, checkY = xpos+y-1, ypos+#block[thisBlock][1]-x
  307.     end
  308.    
  309.     if checkX < 1 or checkX > 10 or checkY < 1 or checkY > depth or grid[depth+1-checkY][checkX] ~= nil then return false end
  310.   end end end
  311.  
  312.   return true
  313. end
  314.  
  315. -- Redraw the game view after a monitor re-size.
  316. local function redrawGame(score)
  317.   drawBorder()
  318.  
  319.   writeAt("[P]ause",pocket and 21 or 22,4)
  320.   if note then writeAt("[M]usic",22,6) end
  321.   writeAt("Next",14,1)
  322.   writeAt("Score",14,8)
  323.   writeAt(string.rep(" ",6-#tostring(score))..tostring(score),13,9)
  324.   writeAt(string.rep(" ",6-#tostring(highScore[gamemode]))..tostring(highScore[gamemode]),13,12)
  325.   writeAt((level>9 and "" or " ")..tostring(level),17,15)
  326.   writeAt(tostring(speed),18,18)
  327.  
  328.   term.setBackgroundColor(colours.black)
  329.   term.setTextColor(term.isColor() and colours.green or colours.white)
  330.  
  331.   if not pocket then for i=0,2 do
  332.     term.setCursorPos(screenx+23,screeny+14+i*2)
  333.     term.write(bit.band(gamemode-1,bit.blshift(1,i))==bit.blshift(1,i) and "O" or " ")
  334.   end end
  335.  
  336.   term.setTextColor(term.isColor() and colours.lightGrey or colours.black)
  337.   for yy=1,depth do
  338.     term.setCursorPos(screenx+2,screeny+yy)
  339.     for xx=1,10 do if grid[depth+1-yy][xx] ~= nil then
  340.       term.setBackgroundColor(term.isColor() and bit.blshift(1,grid[depth+1-yy][xx]) or colours.white)
  341.       term.write("L")
  342.     else
  343.       term.setBackgroundColor(colours.black)
  344.       term.write(" ")
  345.     end end
  346.   end
  347. end
  348.  
  349. local function intro()
  350.   local xSize, ySize, temp = term.getSize()
  351.  
  352.   local introBlock = xSize > 49 and
  353.  
  354.   {{11,2,4},{12,2,4},{13,2,4},{14,2,4},{15,2,4},{16,2,4},{17,2,4},{18,2,4},{19,2,4},{20,2,4},{21,2,4},{22,2,4},{23,2,4},
  355.   {24,2,4},{25,2,4},{8,3,4},{9,3,4},{10,3,4},{26,3,4},{27,3,4},{29,3,11},{5,4,4},{6,4,4},{7,4,4},{13,4,11},{14,4,11},
  356.   {15,4,11},{16,4,11},{17,4,11},{18,4,11},{29,4,11},{31,4,4},{32,4,4},{33,4,4},{46,4,4},{9,5,11},{10,5,11},{11,5,11},
  357.   {12,5,11},{14,5,11},{29,5,11},{34,5,4},{44,5,4},{45,5,4},{14,6,11},{25,6,11},{26,6,11},{27,6,11},{29,6,11},{35,6,4},
  358.   {36,6,4},{37,6,4},{43,6,4},{14,7,11},{20,7,11},{21,7,11},{22,7,11},{23,7,11},{28,7,11},{29,7,11},{38,7,4},{39,7,4},
  359.   {40,7,4},{41,7,4},{42,7,4},{15,8,11},{19,8,11},{24,8,11},{28,8,11},{30,8,11},{31,8,11},{8,9,14},{9,9,14},{15,9,11},
  360.   {19,9,11},{20,9,11},{25,9,11},{28,9,11},{45,9,11},{46,9,11},{47,9,11},{4,10,14},{5,10,14},{7,10,14},{10,10,14},
  361.   {15,10,11},{19,10,11},{21,10,11},{22,10,11},{23,10,11},{24,10,11},{28,10,11},{35,10,11},{36,10,11},{37,10,11},
  362.   {44,10,11},{48,10,11},{3,11,14},{6,11,14},{8,11,14},{9,11,14},{10,11,14},{11,11,14},{14,11,11},{19,11,11},{29,11,11},
  363.   {32,11,11},{34,11,11},{38,11,11},{41,11,11},{44,11,11},{4,12,14},{5,12,14},{6,12,14},{9,12,14},{12,12,14},{14,12,11},
  364.   {20,12,11},{23,12,11},{29,12,11},{33,12,11},{44,12,11},{45,12,11},{4,13,14},{7,13,14},{9,13,14},{10,13,14},
  365.   {11,13,14},{21,13,11},{22,13,11},{29,13,11},{34,13,11},{39,13,11},{40,13,11},{46,13,11},{47,13,11},{4,14,14},
  366.   {5,14,14},{6,14,14},{7,14,14},{29,14,11},{34,14,11},{40,14,11},{48,14,11},{10,15,4},{11,15,4},{12,15,4},{13,15,4},
  367.   {14,15,4},{15,15,4},{40,15,11},{44,15,11},{48,15,11},{9,16,4},{16,16,4},{17,16,4},{18,16,4},{19,16,4},{20,16,4},
  368.   {21,16,4},{22,16,4},{40,16,11},{45,16,11},{46,16,11},{47,16,11},{6,17,4},{7,17,4},{8,17,4},{23,17,4},{24,17,4},
  369.   {25,17,4},{26,17,4},{42,17,4},{43,17,4},{44,17,4},{27,18,4},{28,18,4},{29,18,4},{30,18,4},{31,18,4},{32,18,4},
  370.   {33,18,4},{34,18,4},{35,18,4},{36,18,4},{37,18,4},{38,18,4},{39,18,4},{40,18,4},{41,18,4}}
  371.  
  372.   or
  373.  
  374.   {{2,2,4},{3,2,4},{4,2,4},{5,2,4},{6,2,4},{7,2,4},{9,3,14},{10,3,14},{11,3,14},{4,4,14},{5,4,14},{6,4,14},{8,4,14},
  375.   {9,4,14},{12,4,14},{3,5,14},{4,5,14},{7,5,14},{9,5,14},{10,5,14},{11,5,14},{12,5,14},{13,5,14},{4,6,14},{5,6,14},
  376.   {6,6,14},{7,6,14},{8,6,14},{10,6,14},{13,6,14},{15,6,4},{24,6,4},{25,6,4},{5,7,14},{8,7,14},{10,7,14},{11,7,14},
  377.   {12,7,14},{16,7,4},{23,7,4},{5,8,14},{6,8,14},{7,8,14},{17,8,4},{18,8,4},{19,8,4},{20,8,4},{21,8,4},{22,8,4},{3,9,11},
  378.   {4,9,11},{5,10,11},{6,10,11},{7,10,11},{5,11,11},{8,11,11},{9,11,11},{14,11,11},{5,12,11},{14,12,11},{5,13,11},
  379.   {8,13,11},{9,13,11},{12,13,11},{13,13,11},{14,13,11},{15,13,11},{16,13,11},{22,13,11},{23,13,11},{4,14,11},{7,14,11},
  380.   {10,14,11},{14,14,11},{19,14,11},{21,14,11},{4,15,11},{7,15,11},{8,15,11},{9,15,11},{10,15,11},{13,15,11},{16,15,11},
  381.   {17,15,11},{22,15,11},{23,15,11},{4,16,11},{7,16,11},{13,16,11},{15,16,11},{19,16,11},{24,16,11},{8,17,11},{9,17,11},
  382.   {12,17,11},{15,17,11},{19,17,11},{21,17,11},{22,17,11},{23,17,11},{2,18,4},{3,18,4},{4,18,4},{25,18,4},{5,19,4},
  383.   {6,19,4},{7,19,4},{8,19,4},{9,19,4},{10,19,4},{11,19,4},{12,19,4},{13,19,4},{14,19,4},{15,19,4},{16,19,4},{17,19,4},
  384.   {18,19,4},{19,19,4},{20,19,4},{21,19,4},{22,19,4},{23,19,4},{24,19,4}}
  385.  
  386.   term.setBackgroundColor(colours.black)
  387.   term.clear()
  388.  
  389.   writeAt(version,(pocket and -2) or 36-#version,depth)
  390.  
  391.   term.setBackgroundColor(term.isColor() and colours.yellow or colours.white)
  392.   term.setTextColor(term.isColor() and colours.lightGrey or colours.black)
  393.  
  394.   if xSize > 48 then
  395.     term.setCursorPos(screenx-15,screeny)
  396.     term.write(string.rep("L",53))
  397.     term.setCursorPos(screenx-15,screeny+20)
  398.     term.write(string.rep("L",53))
  399.  
  400.     for i=1,depth do
  401.       term.setCursorPos(screenx-15,screeny+i)
  402.       term.write("L")
  403.       term.setCursorPos(screenx+37,screeny+i)
  404.       term.write("L")
  405.     end
  406.   end
  407.  
  408.   while #introBlock > 0 do
  409.     for i=1,5 do if #introBlock > 0 then
  410.       temp = math.random(#introBlock)
  411.       if term.isColor() then term.setBackgroundColor(math.pow(2,introBlock[temp][3])) end
  412.       term.setCursorPos((xSize > 26 and screenx-15 or 0)+introBlock[temp][1],screeny+introBlock[temp][2])
  413.       term.write("L")
  414.       table.remove(introBlock,temp)
  415.     end end
  416.    
  417.     temp = os.startTimer(0.05)
  418.    
  419.     while true do
  420.       myEvent = {os.pullEvent()}
  421.      
  422.       if myEvent[1] == "timer" and myEvent[2] == temp then
  423.         break
  424.       elseif myEvent[1] ~= "timer" and myEvent[1] ~= "key_up" and myEvent[1] ~= "mouse_up" then
  425.         return
  426.       end
  427.     end    
  428.   end
  429.  
  430.   temp = os.startTimer(2)
  431.   os.pullEvent()
  432. end
  433.  
  434. ---------------------------------------------
  435. ------------      Help Pages     ------------
  436. ---------------------------------------------
  437.  
  438. -- Draw the frame for the help screen.
  439. local function prepareHelp()
  440.   term.setBackgroundColor(colours.black)
  441.   term.clear()
  442.  
  443.   writeAt("^",35,2)
  444.   writeAt("|",35,3)
  445.   writeAt("X",35,10)
  446.   writeAt("|",35,17)
  447.   writeAt("v",35,18)
  448.  
  449.   term.setBackgroundColor(term.isColor() and colours.yellow or colours.white)
  450.   term.setTextColor(term.isColor() and colours.lightGrey or colours.black)
  451.  
  452.   term.setCursorPos(screenx-15,screeny)
  453.   term.write(string.rep("L",53))
  454.   term.setCursorPos(screenx-15,screeny+depth+1)
  455.   term.write(string.rep("L",53))
  456.  
  457.   for i=1,depth do
  458.     term.setCursorPos(screenx-15,screeny+i)
  459.     term.write("L")
  460.     term.setCursorPos(screenx+37,screeny+i)
  461.     term.write("L")
  462.   end
  463. end
  464.  
  465. -- Write a given page of the manual.
  466. local function writeHelp(page)
  467.   local manual
  468.  
  469.   if page == 1 then
  470.     manual =
  471.     {"BBTetris ("..version..")",
  472.     string.rep("-",11+#version),
  473.     "",
  474.     "By Jeffrey Alexander, aka Bomb Bloke.",
  475.     "",
  476.     "Yet another Tetris clone, this one for",
  477.     "ComputerCraft.",
  478.     "",
  479.     "Playable via mouse when used with a touch-",
  480.     "capable display. If using an external",
  481.     "monitor, at least 3x2 blocks is required,",
  482.     "while 5x3 is recommended."}
  483.   elseif page == 2 then
  484.     manual =
  485.     {"Basic Controls",
  486.     "--------------",
  487.     "",
  488.     "Use your arrow keys (or WSAD) for most",
  489.     "actions.",
  490.     "",
  491.     "On the main menu, left alters your level,",
  492.     "whilst right alters your speed. Use up/down",
  493.     "to change your game mode and space to start.",
  494.     "",
  495.     "Mouse-users may instead click the speed /",
  496.     "level / game mode toggles to change them,",
  497.     "then click the play area to begin."}
  498.   elseif page == 3 then
  499.     manual =
  500.     {"Playing",
  501.     "-------",
  502.     "",
  503.     "Blocks fall according to the current speed,",
  504.     "which increases every 10k points. You win",
  505.     "if you somehow manage to reach 100k.",
  506.     "",
  507.     "Use the down arrow to drop the block",
  508.     "quickly, or the up arrow to rotate it.",
  509.     "",
  510.     "Space / enter can be used to rotate in the",
  511.     "opposite direction.",
  512.     "",
  513.     "100 points for a line, 300 for two, 700 for",
  514.     "three and 1500 for a Tetris (four lines)!"}
  515.   elseif page == 4 then
  516.     manual =
  517.     {"Playing (Mouse)",
  518.     "---------------",
  519.     "",
  520.     "Clicking the left side of the grid moves the",
  521.     "block to the left, and clicking the right",
  522.     "functions in a similar manner.",
  523.     "",
  524.     "Click the bottom of the grid to move the",
  525.     "block down quickly, or on the block itself",
  526.     "to rotate. You may click outside the main",
  527.     "play area to rotate the other way.",
  528.     "",
  529.     "The scroll-wheel can also be used to rotate",
  530.     "or move downwards quickly."}
  531.   elseif page == 5 then
  532.     manual =
  533.     {"Alternate Modes",
  534.     "---------------",
  535.     "",
  536.     "There are three optional game modes",
  537.     "available, which can be combined together to",
  538.     "make your game easier/harder as is your",
  539.     "whim.",
  540.     "",
  541.     "Advanced mode increases the block pool to",
  542.     "nearly double, making for a more complex",
  543.     "game. Tricks come in the form of one of",
  544.     "three rare blocks, each of which has a",
  545.     "unique power. Quota mode tasks you to clear",
  546.     "pre-filled grids in order to advance levels."}
  547.   end
  548.  
  549.   for i=1,depth-2 do
  550.     if manual[i] then
  551.       writeAt(manual[i]..string.rep(" ",44-#manual[i]),-13,i+1)
  552.     else
  553.       writeAt(string.rep(" ",44),-13,i+1)
  554.     end
  555.   end
  556. end
  557.  
  558. -- Explain stuff.
  559. local function help()
  560.   local page = 1
  561.   prepareHelp()
  562.   writeHelp(page)
  563.  
  564.   while true do
  565.     myEvent = {os.pullEvent()}
  566.    
  567.     if clickedAt(35,10) or pressedKey(keys.h,keys.x,keys.q) then
  568.       return
  569.     elseif myEvent[1] == "monitor_resize" and mon then
  570.       if myEvent[2] == mon.side then
  571.         enforceScreenSize()
  572.         prepareHelp()
  573.         writeHelp(page)
  574.       end
  575.     elseif clickedAt(35,1,4) or pressedKey(keys.w,keys.up) or (myEvent[1] == "mouse_scroll" and myEvent[2] == -1) then
  576.       page = (page==1) and 5 or (page-1)
  577.       writeHelp(page)
  578.     elseif clickedAt(35,16,19) or pressedKey(keys.s,keys.down) or (myEvent[1] == "mouse_scroll" and myEvent[2] == 1) then
  579.       page = (page==5) and 1 or (page+1)
  580.       writeHelp(page)
  581.     elseif myEvent[1] == "terminate" then
  582.       exitGame()
  583.     elseif myEvent[1] == "musicFinished" then
  584.       os.queueEvent("musicPlay",musicFile)
  585.     end    
  586.   end  
  587. end
  588.  
  589. ---------------------------------------------
  590. ------------      Main Menu      ------------
  591. ---------------------------------------------
  592.  
  593. local function menu()
  594.   running = true
  595.   drawNumeral()
  596.   writeAt((level>9 and "" or " ")..tostring(level),17,15)
  597.   writeAt(tostring(speed),18,18)
  598.  
  599.   while running do
  600.     myEvent = {os.pullEvent()}
  601.    
  602.     if pressedKey(keys.left,keys.a) or clickedAt(13,19,13,16) then
  603.       level = (level == 12) and 0 or (level+1)
  604.       writeAt((level>9 and "" or " ")..tostring(level),17,15)
  605.     elseif pressedKey(keys.right,keys.d) or clickedAt(13,19,16,19) then
  606.       speed = (speed == 9) and 0 or (speed+1)
  607.       writeAt(tostring(speed),18,18)
  608.     elseif pressedKey(keys.space,keys.enter) or clickedAt(1,12,0,20) then
  609.       running = false
  610.     elseif ((touchedMe() and myEvent[3] < screenx and myEvent[4] == screeny + 2) or pressedKey(keys.h)) and not pocket then
  611.       help()
  612.       drawBorder()
  613.       drawNumeral()
  614.       writeAt((level>9 and "" or " ")..tostring(level),17,15)
  615.       writeAt(tostring(speed),18,18)
  616.     elseif pressedKey(keys.up,keys.w) or (myEvent[1] == "mouse_scroll" and myEvent[2] == -1) then
  617.       gamemode = (gamemode == 1) and 8 or (gamemode-1)
  618.       drawNumeral()
  619.     elseif pressedKey(keys.down,keys.s) or (myEvent[1] == "mouse_scroll" and myEvent[2] == 1) then
  620.       gamemode = (gamemode == 8) and 1 or (gamemode+1)
  621.       drawNumeral()
  622.     elseif pressedKey(keys.x,keys.q) then
  623.       os.pullEvent("char")
  624.       exitGame()
  625.     elseif myEvent[1] == "terminate" then
  626.       exitGame()
  627.     elseif touchedMe() and myEvent[3] > screenx+21 then
  628.       term.setTextColor(term.isColor() and colours.red or colours.white)
  629.       term.setCursorPos(myEvent[3],myEvent[4])
  630.       for i=0,2 do if myEvent[4] == screeny + 14 + i * 2 then
  631.         gamemode = gamemode + (bit.band(gamemode-1,bit.blshift(1,i))==bit.blshift(1,i) and (-bit.blshift(1,i)) or bit.blshift(1,i))
  632.         drawNumeral()
  633.         break
  634.       end end
  635.       if myEvent[4] == screeny + 2 then exitGame() end
  636.     elseif myEvent[1] == "monitor_resize" and mon then
  637.       if myEvent[2] == mon.side then
  638.         enforceScreenSize()
  639.         drawBorder()
  640.         drawNumeral()
  641.         writeAt((level>9 and "" or " ")..tostring(level),17,15)
  642.         writeAt(tostring(speed),18,18)
  643.       end
  644.     elseif myEvent[1] == "peripheral_detach" and mon then
  645.       if myEvent[2] == mon.side then exitGame("I've lost my monitor - your turtle didn't mine it, I hope?") end
  646.     end
  647.   end
  648. end
  649.  
  650. ---------------------------------------------
  651. ------------Primary Game Function------------
  652. ---------------------------------------------
  653.  
  654. local function game()
  655.   local falling, knocked, score, curBlock, nextSpeed, startSpeed, startLevel, loaded, rotation = true, false, 0, 1, 9999, speed, level, false, 0
  656.   local nextBlock = math.random(bit.band(gamemode,1)==1 and 7 or 13)
  657.   local x, y, blockTimer, lines, held, flicker, flickerTimer
  658.  
  659.   os.queueEvent("musicPlay",musicFile)
  660.   os.queueEvent(playMusic and "musicResume" or "musicPause")
  661.  
  662.   for i=1,depth do grid[i] = {} end
  663.  
  664.   -- Resume an old game?
  665.   if saves[gamemode] then
  666.     writeAt(" Load old ",2,9)
  667.     writeAt("   game   ",2,10)
  668.     writeAt("[Y]es [N]o",2,11)
  669.    
  670.     while true do
  671.       myEvent = {os.pullEvent()}
  672.          
  673.       if pressedKey(keys.y) or clickedAt(3,11) then
  674.         running = true
  675.         break
  676.       elseif pressedKey(keys.n) or clickedAt(9,11) then
  677.         running = false
  678.         break
  679.       elseif myEvent[1] == "monitor_resize" and mon then
  680.         if myEvent[2] == mon.side then
  681.           enforceScreenSize()
  682.           redrawGame(score)
  683.            
  684.           writeAt(" Load old ",2,9)
  685.           writeAt("   game   ",2,10)
  686.           writeAt("[Y]es [N]o",2,11)
  687.         end
  688.       elseif myEvent[1] == "peripheral_detach" and mon then
  689.         if myEvent[2] == mon.side then exitGame("I've lost my monitor - your turtle didn't mine it, I hope?") end
  690.       elseif myEvent[1] == "terminate" then
  691.         exitGame()      
  692.       end
  693.     end
  694.    
  695.     if running then
  696.       x          = saves[gamemode]["x"]
  697.       y          = saves[gamemode]["y"]
  698.       rotation   = saves[gamemode]["rotation"]
  699.       curBlock   = saves[gamemode]["curBlock"]
  700.       nextBlock  = saves[gamemode]["nextBlock"]
  701.       score      = saves[gamemode]["score"]
  702.       speed      = saves[gamemode]["speed"]
  703.       nextSpeed  = saves[gamemode]["nextSpeed"]
  704.       startSpeed = saves[gamemode]["startSpeed"]
  705.       level      = saves[gamemode]["level"]
  706.       startLevel = saves[gamemode]["startLevel"]
  707.       grid       = saves[gamemode]["grid"]
  708.       saves[gamemode] = nil
  709.       loaded = true
  710.     end
  711.   end
  712.  
  713.   running = true
  714.  
  715.   -- Clear off the menu.
  716.   term.setBackgroundColor(colours.black)
  717.   for i=1,7 do
  718.     term.setCursorPos(screenx+2,screeny+i+6)
  719.     term.write(string.rep(" ",10))
  720.   end  
  721.  
  722.   if loaded then
  723.     term.setTextColor(term.isColor() and colours.lightGrey or colours.black)
  724.     for yy=1,depth do
  725.       term.setCursorPos(screenx+2,screeny+yy)
  726.       for xx=1,10 do if grid[depth+1-yy][xx] ~= nil then
  727.         term.setBackgroundColor(term.isColor() and bit.blshift(1,grid[depth+1-yy][xx]) or colours.white)
  728.         term.write("L")
  729.       else
  730.         term.setBackgroundColor(colours.black)
  731.         term.write(" ")
  732.       end end
  733.     end  
  734.   else fillGrid() end
  735.  
  736.   writeAt("[P]ause",pocket and 21 or 22,4)
  737.   if note then writeAt("[M]usic",22,6) end
  738.   writeAt("Next",14,1)
  739.   writeAt("Score",14,8)
  740.   writeAt(string.rep(" ",6-#tostring(score))..tostring(score),13,9)
  741.   writeAt((level>9 and "" or " ")..tostring(level),17,15)
  742.   writeAt(tostring(speed),18,18)
  743.  
  744.   -- Primary game loop.  
  745.   while running do
  746.     if rotation < 4 then  -- The last type of block "may" persist after line checks.
  747.       if loaded then
  748.         loaded = false
  749.       else
  750.         curBlock = nextBlock
  751.      
  752.         -- Change the "next block" display.
  753.         drawBlock(nextBlock,0,(#block[nextBlock][1] < 3 and 15 or 14),(#block[nextBlock] < 3 and 3 or 2), false, false)
  754.         nextBlock = math.random(bit.band(gamemode,1)==1 and 7 or 13)
  755.        
  756.         -- Trick block will be next!
  757.         if bit.band(gamemode-1,2)==2 and math.random(20) == 20 then nextBlock = 13 + math.random(3) end
  758.      
  759.         -- Prepare a new falling block.
  760.         x, y, rotation = (#block[curBlock][1] < 4 and 5 or 4), 1, 0
  761.       end
  762.      
  763.       drawBlock(nextBlock,0,(#block[nextBlock][1] < 3 and 15 or 14),(#block[nextBlock] < 3 and 3 or 2), true, nextBlock>13)
  764.       drawBlock(curBlock,rotation,x,y,true,false)
  765.          
  766.       -- Can play continue?
  767.       if checkBlock(curBlock,rotation,x,y) then
  768.         falling, knocked, flicker, blockTimer, flickerTimer = true, false, false, os.startTimer((10-speed)/10), os.startTimer(0.3)
  769.       else running = false end
  770.     else
  771.       falling, rotation, flickerTimer = true, 0, os.startTimer(0)
  772.       drawBlock(curBlock,rotation,x,y,true,flicker)
  773.     end
  774.    
  775.     -- Stuff that happens while a block falls.
  776.     while falling do
  777.       myEvent = {os.pullEvent()}
  778.      
  779.       -- Is the user still holding down "down" after the last block fell?
  780.       if held then
  781.         if not ((myEvent[1] == "timer" and myEvent[2] ~= blockTimer)
  782.           or pressedKey(keys.down,keys.s) or myEvent[1] == "char")
  783.           then held = false end
  784.       end
  785.      
  786.       -- Trick blocks flash.
  787.       if myEvent[1] == "timer" and myEvent[2] == flickerTimer and curBlock > 13 then
  788.         flicker = not flicker
  789.         flickerTimer = os.startTimer(0.3)
  790.         drawBlock(curBlock,rotation,x,y,true,flicker)
  791.  
  792.       elseif curBlock > 14 and (clickedAt(x,x+1+(bit.band(rotation,1)==1 and #block[curBlock] or #block[curBlock][1]),y-1,y+(bit.band(rotation,1)==1 and #block[curBlock][1] or #block[curBlock])) or pressedKey(keys.up,keys.w,keys.space,keys.enter)) then
  793.        
  794.         -- Erasure!
  795.         if curBlock == 15 then
  796.           for i=depth-1-y,1,-1 do if grid[i][x] ~= nil then
  797.             term.setCursorPos(screenx+1+x,screeny+depth+1-i)
  798.             term.setBackgroundColor(colours.black)
  799.             term.write(" ")
  800.             grid[i][x] = nil
  801.             break
  802.           end end
  803.          
  804.           if bit.band(gamemode-1,4)==4 then
  805.             -- Possible to fulfil the quota here without creating a line.
  806.             lines = 0
  807.             for yy=1,depth do for xx=1,10 do if grid[xx][yy] == 14 then
  808.               yy=depth
  809.               xx=10
  810.               lines = 1
  811.             end end end
  812.          
  813.             if lines ~= 1 then
  814.               score = score + 10000
  815.               level = level + 1
  816.               rotation = 0
  817.               falling = false
  818.               gameover()
  819.               fillGrid()
  820.              
  821.               writeAt(string.rep(" ",6-#tostring(score))..tostring(score),13,9)
  822.        
  823.               if score > 99999 then running = false end
  824.        
  825.               -- Check for a score record.        
  826.               if score > highScore[gamemode] then
  827.                 highScore[gamemode] = score
  828.                 writeAt(string.rep(" ",6-#tostring(highScore[gamemode]))..tostring(highScore[gamemode]),13,12)
  829.               end
  830.        
  831.               writeAt((level>9 and "" or " ")..tostring(level),17,15)
  832.              
  833.               -- Increment speed.
  834.               nextSpeed = nextSpeed + 10000
  835.               speed = (speed==9) and 9 or (speed+1)
  836.               writeAt(tostring(speed),18,18)
  837.             end
  838.           end
  839.          
  840.         -- Fill!
  841.         elseif curBlock == 16 and grid[depth-2-y][x] == nil then
  842.           rotation = 1
  843.           for i=1,depth-3-y do if grid[i][x] ~= nil then rotation = i+1 end end
  844.           term.setCursorPos(screenx+1+x,screeny+depth+1-rotation)
  845.           term.setBackgroundColor(colours.white)
  846.           term.setTextColor(term.isColor() and colours.lightGrey or colours.black)
  847.           term.write("L")
  848.           grid[rotation][x] = 32
  849.           falling, rotation = false, 20 + rotation  -- Rotation used here as a flag to indicate we're still actually falling.
  850.         end
  851.        
  852.       -- Block rotation.
  853.       elseif (clickedAt(x,x+1+(bit.band(rotation,1)==1 and #block[curBlock] or #block[curBlock][1]),y-1,y+(bit.band(rotation,1)==1 and #block[curBlock][1] or #block[curBlock])) or pressedKey(keys.up,keys.w) or (myEvent[1] == "mouse_scroll" and myEvent[2] == -1)) and checkBlock(curBlock,rotation==3 and 0 or (rotation+1),x,y) then
  854.         drawBlock(curBlock,rotation,x,y,false,flicker)
  855.         rotation = (rotation==3) and 0 or (rotation+1)
  856.         drawBlock(curBlock,rotation,x,y,true,flicker)  
  857.          
  858.       elseif (myEvent[1] == "timer" and myEvent[2] == blockTimer) or clickedAt(1,12,depth-2,depth+1) or (pressedKey(keys.down,keys.s) and not held) or (myEvent[1] == "mouse_scroll" and myEvent[2] == 1) then
  859.        
  860.         -- Move the block down if we can.
  861.         if checkBlock(curBlock,rotation,x,y+1) then
  862.           drawBlock(curBlock,rotation,x,y,false,flicker)
  863.           y = y + 1
  864.           drawBlock(curBlock,rotation,x,y,true,flicker)
  865.           knocked = false
  866.          
  867.         -- Brick's stopped moving down, add it to the grid.
  868.         elseif knocked then
  869.           if curBlock < 15 then
  870.             for yy=1,#block[curBlock] do for xx=1,#block[curBlock][1] do if block[curBlock][yy][xx] == 1 then
  871.               if rotation == 0 then
  872.                 grid[depth+2-y-yy][x+xx-1] = (curBlock == 14 and 15 or curBlock)
  873.               elseif rotation == 1 then
  874.                 grid[depth+2-y-xx][x+#block[curBlock]-yy] = (curBlock == 14 and 15 or curBlock)
  875.               elseif rotation == 2 then
  876.                 grid[depth+1-y-#block[curBlock]+yy][x+#block[curBlock][1]-xx] = (curBlock == 14 and 15 or curBlock)
  877.               elseif rotation == 3 then
  878.                 grid[depth+1-y-#block[curBlock][1]+xx][x+yy-1] = (curBlock == 14 and 15 or curBlock)
  879.               end
  880.             end end end
  881.           end
  882.          
  883.           if curBlock > 13 then drawBlock(curBlock,rotation,x,y,curBlock==14,false) end
  884.           falling = false
  885.           held = true -- This is used to stop the NEXT block being pelted downwards if you were holding a "down" button.
  886.          
  887.         -- Brick has "knocked" other bricks, but hasn't yet locked in place.
  888.         else knocked = true end
  889.        
  890.         if falling then blockTimer = os.startTimer((10-speed)/10) end
  891.      
  892.       -- Pause the game.
  893.       elseif (myEvent[1] == "monitor_resize" and mon) or pressedKey(keys.p) or (touchedMe() and myEvent[3] > screenx+21 and myEvent[4] == screeny+4) then
  894.         -- Something altered my monitor!
  895.         if myEvent[1] == "monitor_resize" and mon then
  896.           if myEvent[2] == mon.side then
  897.             enforceScreenSize()
  898.             redrawGame(score)
  899.             drawBlock(curBlock,rotation,x,y,true,flicker)
  900.             drawBlock(nextBlock,0,(#block[nextBlock][1] < 3 and 15 or 14),(#block[nextBlock] < 3 and 3 or 2), true, nextBlock>13)
  901.           end
  902.         end  
  903.        
  904.         writeAt("  PAUSED  ",2,10)
  905.         blockTimer = os.startTimer(1)
  906.        
  907.         while true do
  908.           myEvent = {os.pullEvent()}
  909.  
  910.           if myEvent[1] == "key" or touchedMe() then
  911.             break
  912.           elseif myEvent[1] == "monitor_resize" and mon then
  913.             if myEvent[2] == mon.side then
  914.               enforceScreenSize()
  915.               redrawGame(score)
  916.               drawBlock(curBlock,rotation,x,y,true,flicker)
  917.               drawBlock(nextBlock,0,(#block[nextBlock][1] < 3 and 15 or 14),(#block[nextBlock] < 3 and 3 or 2), true, nextBlock>13)
  918.            
  919.               falling = not falling
  920.               writeAt(falling and "  PAUSED  " or string.rep(" ",10),2,10)
  921.               blockTimer = os.startTimer(1)
  922.             end
  923.           elseif myEvent[1] == "timer" and myEvent[2] == blockTimer then
  924.             falling = not falling
  925.             writeAt(falling and "  PAUSED  " or string.rep(" ",10),2,10)
  926.             blockTimer = os.startTimer(1)
  927.           elseif myEvent[1] == "peripheral_detach" and mon then
  928.            if myEvent[2] == mon.side then exitGame("I've lost my monitor - your turtle didn't mine it, I hope?") end
  929.           elseif myEvent[1] == "terminate" then
  930.             exitGame()
  931.           elseif myEvent[1] == "musicFinished" then
  932.             os.queueEvent("musicPlay",musicFile)
  933.           end
  934.         end
  935.                  
  936.         term.setTextColor(term.isColor() and colours.lightGrey or colours.black)
  937.         term.setCursorPos(screenx+2,screeny+10)
  938.         for xx=1,10 do if grid[10][xx] ~= nil then
  939.           term.setBackgroundColor(term.isColor() and bit.blshift(1,grid[10][xx]) or colours.white)
  940.           term.write("L")
  941.         else
  942.           term.setBackgroundColor(colours.black)
  943.           term.write(" ")
  944.         end end
  945.         drawBlock(curBlock,rotation,x,y,true,flicker)
  946.         blockTimer, flickerTimer, falling = os.startTimer((10-speed)/10), os.startTimer(0.3), true
  947.        
  948.       -- Toggle music playback.
  949.       elseif pressedKey(keys.m) or (touchedMe() and myEvent[3] > screenx+21 and myEvent[4] == screeny+6) then
  950.         playMusic = not playMusic
  951.         os.queueEvent(playMusic and "musicResume" or "musicPause")
  952.        
  953.       -- Repeat music.
  954.       elseif myEvent[1] == "musicFinished" then
  955.         os.queueEvent("musicPlay",musicFile)
  956.      
  957.       -- Display the help screen.
  958.       elseif ((touchedMe() and myEvent[3] < screenx and myEvent[4] == screeny + 2) or pressedKey(keys.h)) and not pocket then
  959.         help()
  960.         redrawGame(score)
  961.         drawBlock(curBlock,rotation,x,y,true,flicker)
  962.         drawBlock(nextBlock,0,(#block[nextBlock][1] < 3 and 15 or 14),(#block[nextBlock] < 3 and 3 or 2), true, nextBlock>13)
  963.         blockTimer, flickerTimer = os.startTimer((10-speed)/10), os.startTimer(0.3)
  964.      
  965.       -- User is attempting to end game.
  966.       elseif pressedKey(keys.x,keys.q) or (touchedMe() and myEvent[3] > screenx+21 and myEvent[4] == screeny+2) then
  967.         if canSave then
  968.           writeAt(" Save the ",2,9)
  969.           writeAt("   game   ",2,10)
  970.           writeAt("[Y]es [N]o",2,11)
  971.  
  972.           while true do
  973.             myEvent = {os.pullEvent()}
  974.            
  975.             if pressedKey(keys.y) or clickedAt(3,11) then
  976.               running = true
  977.               break
  978.             elseif pressedKey(keys.n) or clickedAt(9,11) then
  979.               running = false
  980.               break
  981.             elseif myEvent[1] == "monitor_resize" and mon then
  982.               if myEvent[2] == mon.side then
  983.                 enforceScreenSize()
  984.                 redrawGame(score)
  985.                 drawBlock(curBlock,rotation,x,y,true,flicker)
  986.                 drawBlock(nextBlock,0,(#block[nextBlock][1] < 3 and 15 or 14),(#block[nextBlock] < 3 and 3 or 2), true, nextBlock>13)
  987.            
  988.                 writeAt(" Save the ",2,9)
  989.                 writeAt("   game   ",2,10)
  990.                 writeAt("[Y]es [N]o",2,11)
  991.               end
  992.             elseif myEvent[1] == "peripheral_detach" and mon then
  993.               if myEvent[2] == mon.side then exitGame("I've lost my monitor - your turtle didn't mine it, I hope?") end
  994.             elseif myEvent[1] == "terminate" then
  995.               exitGame()
  996.             end
  997.           end
  998.         else running = false end
  999.  
  1000.         -- User wants to save current progress.
  1001.         if running then
  1002.           saves[gamemode] = {}
  1003.           saves[gamemode]["x"] = x
  1004.           saves[gamemode]["y"] = y
  1005.           saves[gamemode]["rotation"] = rotation
  1006.           saves[gamemode]["curBlock"] = curBlock
  1007.           saves[gamemode]["nextBlock"] = nextBlock
  1008.           saves[gamemode]["score"] = score
  1009.           saves[gamemode]["speed"] = speed
  1010.           saves[gamemode]["nextSpeed"] = nextSpeed
  1011.           saves[gamemode]["startSpeed"] = startSpeed
  1012.           saves[gamemode]["level"] = level
  1013.           saves[gamemode]["startLevel"] = startLevel
  1014.           saves[gamemode]["grid"] = {}
  1015.           for yy=1,depth do
  1016.             saves[gamemode]["grid"][yy] = {}
  1017.             for a,b in pairs(grid[yy]) do saves[gamemode]["grid"][yy][a] = b end
  1018.           end
  1019.         end
  1020.        
  1021.         falling, running, rotation = false, false, 10  -- Rotation used here as a flag to bypass the "game over" animation.
  1022.      
  1023.       -- User is trying to out-right quit the game.
  1024.       elseif myEvent[1] == "terminate" then
  1025.         exitGame()
  1026.      
  1027.       -- Move block left.
  1028.       elseif (clickedAt(1,7,0,depth-1) or pressedKey(keys.left,keys.a)) and checkBlock(curBlock,rotation,x-1,y) then
  1029.         drawBlock(curBlock,rotation,x,y,false,flicker)
  1030.         x = x - 1
  1031.         drawBlock(curBlock,rotation,x,y,true,flicker)
  1032.        
  1033.       -- Move block right.
  1034.       elseif (clickedAt(6,12,0,depth-1) or pressedKey(keys.right,keys.d)) and checkBlock(curBlock,rotation,x+1,y) then
  1035.         drawBlock(curBlock,rotation,x,y,false,flicker)
  1036.         x = x + 1
  1037.         drawBlock(curBlock,rotation,x,y,true,flicker)
  1038.        
  1039.       -- Rotating the other way.
  1040.       elseif ((touchedMe() and (myEvent[3] < screenx+1 or myEvent[3] > screenx+12)) or pressedKey(keys.space,keys.enter)) and checkBlock(curBlock,rotation==0 and 3 or (rotation-1),x,y) and curBlock < 14 then
  1041.         drawBlock(curBlock,rotation,x,y,false,flicker)
  1042.         rotation = (rotation==0) and 3 or (rotation-1)
  1043.         drawBlock(curBlock,rotation,x,y,true,flicker)        
  1044.      
  1045.       elseif myEvent[1] == "peripheral_detach" and mon then
  1046.         if myEvent[2] == mon.side then exitGame("I've lost my monitor - your turtle didn't mine it, I hope?") end
  1047.       end  
  1048.     end
  1049.    
  1050.     -- Look for complete lines.
  1051.     if running then
  1052.       lines = {}
  1053.       for yy=((rotation>3) and (40-rotation) or y),((rotation>3) and (40-rotation) or y+(bit.band(rotation,1)==1 and #block[curBlock][1] or #block[curBlock])-1) do
  1054.         falling = true
  1055.        
  1056.         for xx=1,10 do if yy < depth+1 then
  1057.           if grid[depth+1-yy][xx] == nil then
  1058.             falling = false
  1059.             break
  1060.           end
  1061.         else falling = false end end
  1062.        
  1063.         if falling then
  1064.           lines[#lines+1] = yy
  1065.           table.remove(grid,depth+1-yy)
  1066.           grid[depth] = {}
  1067.         end
  1068.       end
  1069.      
  1070.       if #lines > 0 then
  1071.        
  1072.         -- Blink the complete lines a few times.
  1073.         falling = true
  1074.         term.setBackgroundColor(term.isColor() and colours.black or colours.white)
  1075.         term.setTextColor(term.isColor() and colours.lightGrey or colours.black)
  1076.         for i=1,6 do
  1077.           if not term.isColor() then term.setBackgroundColor(falling and colours.white or colours.black) end
  1078.           for j=1,#lines do
  1079.             term.setCursorPos(screenx+2,screeny+lines[j])
  1080.             term.write(string.rep(falling and "L" or " ",10))
  1081.           end
  1082.           falling = not falling
  1083.          
  1084.           blockTimer = os.startTimer(0.1)
  1085.           while myEvent[2] ~= blockTimer do myEvent = {os.pullEvent("timer")} end
  1086.         end
  1087.        
  1088.         -- Collapse the on-screen grid.
  1089.         for yy=1,lines[#lines] do
  1090.           term.setCursorPos(screenx+2,screeny+yy)
  1091.           for xx=1,10 do if grid[depth+1-yy][xx] ~= nil then
  1092.             term.setBackgroundColor(term.isColor() and bit.blshift(1,grid[depth+1-yy][xx]) or colours.white)
  1093.             term.write("L")
  1094.           else
  1095.             term.setBackgroundColor(colours.black)
  1096.             term.write(" ")
  1097.           end end
  1098.         end
  1099.        
  1100.         if bit.band(gamemode-1,4)==4 then
  1101.           -- Quota reached?
  1102.           for yy=1,depth do for xx=1,10 do if grid[xx][yy] == 14 then
  1103.             yy=depth
  1104.             xx=10
  1105.             lines = 1
  1106.           end end end
  1107.          
  1108.           if lines ~= 1 then
  1109.             score = score + 10000
  1110.             level = level + 1
  1111.             rotation = 0
  1112.             falling = false
  1113.             gameover()
  1114.             fillGrid()
  1115.           end
  1116.         else
  1117.           -- Increment score in the usual way.
  1118.           if #lines == 1 then
  1119.             score = score + 100
  1120.           elseif #lines == 2 then
  1121.             score = score + 300
  1122.           elseif #lines == 3 then
  1123.             score = score + 700
  1124.           elseif #lines == 4 then
  1125.             score = score + 1500
  1126.           end
  1127.         end
  1128.        
  1129.         writeAt(string.rep(" ",6-#tostring(score))..tostring(score),13,9)
  1130.        
  1131.         if score > 99999 then running = false end
  1132.        
  1133.         -- Check for a score record.        
  1134.         if score > highScore[gamemode] then
  1135.           highScore[gamemode] = score
  1136.           writeAt(string.rep(" ",6-#tostring(highScore[gamemode]))..tostring(highScore[gamemode]),13,12)
  1137.         end
  1138.        
  1139.         writeAt((level>9 and "" or " ")..tostring(level),17,15)
  1140.        
  1141.         -- Increment speed?
  1142.         if score > nextSpeed then
  1143.           nextSpeed = nextSpeed + 10000
  1144.           speed = (speed==9) and 9 or (speed+1)
  1145.           writeAt(tostring(speed),18,18)
  1146.         end
  1147.        
  1148.         blockTimer = os.startTimer((10-speed)/10)
  1149.       end
  1150.     end
  1151.   end
  1152.  
  1153.   -- Game over.
  1154.   if rotation < 4 then
  1155.     writeAt(score < 100000 and "GAME  OVER" or "!!WINNER!!",2,10)
  1156.    
  1157.     -- Assign bonus points.
  1158.     score = score + (100*startSpeed) + startLevel
  1159.     writeAt(string.rep(" ",6-#tostring(score))..tostring(score),13,9)
  1160.    
  1161.     -- Check for a score record.        
  1162.     if score > highScore[gamemode] then
  1163.       highScore[gamemode] = score
  1164.       writeAt(string.rep(" ",6-#tostring(highScore[gamemode]))..tostring(highScore[gamemode]),13,12)
  1165.     end
  1166.  
  1167.     gameover()
  1168.   end
  1169.  
  1170.   speed = startSpeed
  1171.   level = startLevel
  1172. end
  1173.  
  1174. ---------------------------------------------
  1175. ------------         Init        ------------
  1176. ---------------------------------------------
  1177.  
  1178. -- Override the event-puller.
  1179. OGeventPuller = os.pullEvent
  1180. os.pullEvent = os.pullEventRaw
  1181.  
  1182. -- Load the INI file.
  1183. if fs.exists(shell.resolve(".").."\\bbtetris.ini") then
  1184.   local readIn, readTable  
  1185.   myEvent = fs.open(shell.resolve(".").."\\bbtetris.ini", "r")
  1186.   readIn = myEvent.readLine()  
  1187.  
  1188.   while readIn do
  1189.     readTable = {}
  1190.     readIn = readIn:gsub("="," "):lower()
  1191.     for i in readIn:gmatch("%S+") do readTable[#readTable+1] = i end    
  1192.    
  1193.     if readTable[1] == "nointro" and readTable[2] then
  1194.       if readTable[2] == "yes" or readTable[2] == "y" or readTable[2] == "true" or readTable[2] == "on" or readTable[2] == "1" then skipIntro = true end
  1195.     elseif readTable[1] == "nomonitor" and readTable[2] then
  1196.       if readTable[2] == "yes" or readTable[2] == "y" or readTable[2] == "true" or readTable[2] == "on" or readTable[2] == "1" then skipMonitor = true end
  1197.     elseif readTable[1] == "defaultmonitorside" and readTable[2] then
  1198.       defaultMonitor = readTable[2]
  1199.     elseif readTable[1] == "defaultspeed" and tonumber(readTable[2]) then
  1200.       if tonumber(readTable[2]) > -1 and tonumber(readTable[2]) < 10 then speed = tonumber(readTable[2]) end
  1201.     elseif readTable[1] == "defaultlevel" and tonumber(readTable[2]) then
  1202.       if tonumber(readTable[2]) > -1 and tonumber(readTable[2]) < 13 then level = tonumber(readTable[2]) end
  1203.     elseif readTable[1] == "defaultmode" and tonumber(readTable[2]) then
  1204.       if tonumber(readTable[2]) > 0 and tonumber(readTable[2]) < 9 then gamemode = tonumber(readTable[2]) end
  1205.     elseif readTable[1] == "nomusic" and readTable[2] then
  1206.       if readTable[2] == "yes" or readTable[2] == "y" or readTable[2] == "true" or readTable[2] == "on" or readTable[2] == "1" then playMusic = false end
  1207.     end
  1208.      
  1209.     readIn = myEvent.readLine()
  1210.   end
  1211.  
  1212.   myEvent.close()
  1213. else
  1214.   myEvent = fs.open(shell.resolve(".").."\\bbtetris.ini", "w")
  1215.  
  1216.   if myEvent then
  1217.     myEvent.writeLine("NoIntro = ")
  1218.     myEvent.writeLine("NoMonitor = ")
  1219.     myEvent.writeLine("NoMusic = ")
  1220.     myEvent.writeLine("DefaultMonitorSide = ")
  1221.     myEvent.writeLine("DefaultSpeed = ")
  1222.     myEvent.writeLine("DefaultLevel = ")
  1223.     myEvent.writeLine("DefaultMode = ")
  1224.     myEvent.close()
  1225.   else canSave = false end
  1226. end
  1227.  
  1228. -- Load saved data.
  1229. if fs.exists(shell.resolve(".").."\\bbtetris.dat") then
  1230.   myEvent = fs.open(shell.resolve(".").."\\bbtetris.dat", "r")
  1231.   highScore = myEvent.readLine()
  1232.   highScore = textutils.unserialize(myEvent.readLine())
  1233.   saves = textutils.unserialize(myEvent.readLine())
  1234.   myEvent.close()
  1235. end
  1236.  
  1237. if type(highScore) == "table" then
  1238.   for i=#highScore+1,8 do highScore[i] = 0 end
  1239. else
  1240.   highScore = {}
  1241.   for i=1,8 do highScore[i] = 0 end
  1242. end
  1243.  
  1244. -- Look for a monitor to use.
  1245. if not skipMonitor then
  1246.   if defaultMonitor then
  1247.     if peripheral.getType(defaultMonitor) == "monitor" and peripheral.call(defaultMonitor, "isColour") then
  1248.       term.clear()
  1249.       term.setCursorPos(1,1)
  1250.       print("Game in progress on attached display...")
  1251.            
  1252.       mon = peripheral.wrap(defaultMonitor)
  1253.       mon.side = defaultMonitor
  1254.       if not term.restore then mon.restoreTo = term.current() end
  1255.       term.redirect(mon)
  1256.       enforceScreenSize()
  1257.     else
  1258.       exitGame("The \"monitor\" at location \""..defaultMonitor.."\" (specified in \"bbtetris.ini\") is invalid. Please fix that.")
  1259.     end
  1260.   else
  1261.     local sides = peripheral.getNames()
  1262.  
  1263.     for i=1,#sides do if peripheral.getType(sides[i]) == "monitor" and peripheral.call(sides[i], "isColour") then
  1264.       print("")
  1265.       print("I see a colour monitor attached - do you wish to use it (y/n)?")
  1266.           term.clear()
  1267.           term.setCursorPos(1,1)
  1268.           print("Game in progress on attached display...")
  1269.      
  1270.           mon = peripheral.wrap(sides[i])
  1271.           mon.side = sides[i]
  1272.           if not term.restore then mon.restoreTo = term.current() end
  1273.           term.redirect(mon)
  1274.           enforceScreenSize()
  1275.       break
  1276.     end end
  1277.   end
  1278. end
  1279.  
  1280. -- Ensure the display is suitable.
  1281. screenx,screeny = term.getSize()
  1282. pocket, depth = screenx < 50, screeny > 19 and 20 or 19
  1283. if (screenx < 26 or screeny < 19) and not pocket then error("\nA minimum display resolution of 26 columns by 19 rows is required.\n\nIf you're trying to run me on eg a turtle, please try a regular computer.\n",0) end
  1284. screenx,screeny = math.floor(screenx/2)-10, pocket and 0 or math.floor(screeny/2)-9
  1285.  
  1286. -- Load the music player, if possible.
  1287. if peripheral.find and peripheral.find("iron_note") and fs.exists("/moarp/note") and fs.exists(musicFile) then os.loadAPI("/moarp/note") end
  1288.  
  1289. -- Show the splash screen.
  1290. if not skipIntro then intro() end
  1291.  
  1292. ---------------------------------------------
  1293. ------------  Main Program Loop  ------------
  1294. ---------------------------------------------
  1295.  
  1296. while true do
  1297.   drawBorder()
  1298.   menu()
  1299.   if note then parallel.waitForAny(game, note.songEngine) else game() end
  1300. end
Advertisement
Add Comment
Please, Sign In to add comment