Advertisement
BombBloke

BBTetris (ComputerCraft)

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