View difference between Paste ID: 1gVJ7PLQ and nKQZwgtv
SHOW: | | - or go back to the newest paste.
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-
      while true do
1265+
1266-
        myEvent = {os.pullEvent("char")}
1266+
1267-
   
1267+
1268-
        if myEvent[2]:lower() == "y" then
1268+
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-
          break
1278+
1279-
        elseif myEvent[2]:lower() == "n" then
1279+
1280-
          break
1280+
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