· 5 years ago · Jun 19, 2020, 03:38 AM
1--
2-- ## ##### ###### ###### ######
3-- ## ## ## ## ## ## ## ###
4-- ## ## ## ## ## ## ###
5-- ## ## ## ###### ## ######
6-- ## ## ## ## ## ## ###
7-- ## ## ## ## ## ## ### ##
8-- ##### ##### ## ## ###### ######
9--
10-- ComputerCraft port of Tetris
11-- by LDDestroier
12--
13-- Supports wall kicking, holding, fast-dropping,
14-- and ghost pieces.
15--
16-- TO-DO:
17-- + Add random color pulsation (for effect!)
18-- + Add a proper title screen. The current one is pathetic.
19
20local sentInfos = 0
21
22local scr_x, scr_y = term.getSize()
23local keysDown = {}
24local game = {
25 p = {}, -- stores player information
26 pp = {}, -- stores other player information that doesn't need to be sent during netplay
27 you = 1, -- current player slot
28 instanceID = math.random(1, 2^31-1), -- random per-instance value to ensure skynet doesn't poopie
29 amountOfPlayers = 2, -- amount of players for the current game
30 running = true, -- if set to false, will quit the game
31 moveHoldDelay = 0.1, -- amount of time to hold left or right for it to keep moving that way
32 boardOverflow = 12, -- amount of space above the board that it can overflow
33 paused = false, -- whether or not game is paused
34 canPause = true, -- if false, cannot pause game (such as in online multiplayer)
35 inputDelay = 0.05, -- amount of time between each input
36 gameDelay = 0.05, -- amount of time between game ticks
37 minimumLockTimer = 0.4, -- shortest amount of time before a piece locks upon touching the ground
38 appearanceDelay = 0.15, -- amount of time to wait after placing a piece
39 config = {
40 TGMlock = false, -- replicate the piece locking from Tetris: The Grand Master
41 scrubMode = false, -- gives you nothing but I-pieces
42 },
43 control = { -- client's control scheme
44 moveLeft = keys.left, -- shift left
45 moveRight = keys.right, -- shift right
46 moveDown = keys.down, -- shift downwards
47 rotateLeft = keys.z, -- rotate counter-clockwise
48 rotateRight = keys.x, -- rotate clockwise
49 fastDrop = keys.up, -- instantly drop and place piece
50 hold = keys.leftShift, -- slot piece into hold buffer
51 quit = keys.q, -- fuck off
52 pause = keys.p
53 },
54 revControl = {}, -- a mirror of "control", but with the keys and values inverted
55 net = { -- all network-related values
56 isHost = true, -- the host holds all the cards
57 gameID = math.random(1, 2^31-1), -- per-game ID to prevent interference from other games
58 channel = 1294, -- modem or skynet channel
59 active = true, -- whether or not you're using modems at all
60 waitingForGame = true, -- if you're waiting for another player to start the game
61 modem = peripheral.find("modem"), -- modem transmit object
62 useSkynet = false, -- if true, uses Skynet instead of modems
63 skynet = nil, -- skynet transmit object
64 skynetURL = "https://github.com/LDDestroier/CC/raw/master/API/skynet.lua", -- exactly what it looks like
65 skynetPath = "/skynet.lua" -- location for Skynet API
66 },
67 timers = {},
68 timerNo = 1,
69}
70
71for k,v in pairs(game.control) do
72 game.revControl[v] = k
73end
74
75game.setPaletteColor = function(...)
76 if game.amountOfPlayers <= 1 then
77 term.setPaletteColor(...)
78 end
79end
80
81local getTime = function()
82 if os.epoch then
83 return os.epoch("utc")
84 else
85 return 24 * os.day() + os.time()
86 end
87end
88
89game.startTimer = function(duration)
90 game.timers[game.timerNo] = duration
91 game.timerNo = game.timerNo + 1
92 return game.timerNo - 1
93end
94
95game.cancelTimer = function(tID)
96 game.timers[tID or 0] = nil
97end
98
99game.alterTimer = function(tID, mod)
100 if game.timers[tID] then
101 game.timers[tID] = game.timers[tID] + mod
102 end
103end
104
105local tableCopy
106tableCopy = function(tbl)
107 local output = {}
108 for k,v in pairs(tbl) do
109 if type(v) == "table" then
110 output[k] = tableCopy(v)
111 else
112 output[k] = v
113 end
114 end
115 return output
116end
117
118-- sets up brown as colors.special, for palette swapping magic(k)
119local tColors = tableCopy(colors)
120
121tColors.white = 1
122tColors.brown = nil -- brown is now white
123tColors.special = 4096
124term.setPaletteColor(tColors.special, 0x353535)
125term.setPaletteColor(tColors.white, 0xf0f0f0)
126
127-- initializes and fixes up a board
128-- boards are 2D objects that can display perfectly square graphics
129local clearBoard = function(board, xpos, ypos, newXsize, newYsize, newBGcolor, topCull, clearContent)
130 board = board or {}
131 board.x = board.x or xpos or 1
132 board.y = board.y or ypos or 1
133 board.xSize = board.xSize or newXsize or 10
134 board.ySize = board.ySize or newYsize or 24 + game.boardOverflow
135 board.topCull = board.topCull or topCull or game.boardOverflow
136 board.BGcolor = board.BGcolor or newBGcolor or "f"
137 for y = 1, board.ySize do
138 board[y] = board[y] or {}
139 for x = 1, board.xSize do
140 -- explanation on each space:
141 -- {
142 -- boolean; if true, the space is solid
143 -- string; the hex color of the space
144 -- number; the countdown until the space is made non-solid (inactive if 0)
145 -- number; the countdown until the space is colored to board.BGcolor (inactive if 0)
146 -- }
147 if clearContent then
148 board[y][x] = {false, board.BGcolor, 0, 0}
149 else
150 board[y][x] = board[y][x] or {false, board.BGcolor, 0, 0}
151 end
152 end
153 end
154 return board
155end
156
157-- tetramino information
158-- don't tamper with this or I'll beat your ass so hard that war veterans would blush
159local minos = {
160 [1] = { -- I-piece
161 canRotate = true,
162 canTspin = false,
163 mainColor = "3",
164 shape = {
165 " ",
166 "3333",
167 " ",
168 " ",
169 }
170 },
171 [2] = { -- L-piece
172 canRotate = true,
173 canTspin = false,
174 mainColor = "1",
175 shape = {
176 " 1",
177 "111",
178 " ",
179 }
180 },
181 [3] = { -- J-piece
182 canRotate = true,
183 canTspin = false,
184 mainColor = "b",
185 shape = {
186 "b ",
187 "bbb",
188 " ",
189 }
190 },
191 [4] = { -- O-piece
192 canRotate = true,
193 canTspin = false,
194 mainColor = "4",
195 shape = {
196 "44",
197 "44",
198 }
199 },
200 [5] = { -- T-piece
201 canRotate = true,
202 canTspin = true,
203 mainColor = "a",
204 shape = {
205 " a ",
206 "aaa",
207 " ",
208 }
209 },
210 [6] = { -- Z-piece
211 canRotate = true,
212 canTspin = false,
213 mainColor = "e",
214 shape = {
215 "ee ",
216 " ee",
217 " ",
218 }
219 },
220 [7] = { -- S-piece
221 canRotate = true,
222 canTspin = false,
223 mainColor = "5",
224 shape = {
225 " 55",
226 "55 ",
227 " ",
228 }
229 },
230 ["gameover"] = { -- special "mino" for game over
231 canRotate = false,
232 shape = {
233 " ccc ccc c c ccccc ccc c c ccccc cccc",
234 "c c c cc cc c c c c c c c c",
235 "c c c cc cc c c c c c c c c",
236 "c cc ccccc c c c cccc c c c c cccc cccc",
237 "c c c c c c c c c c c c c c",
238 "c c c c c c c c c c c c c c",
239 "c c c c c c c c c c c c c c",
240 " ccc c c c c ccccc ccc c ccccc c c",
241 }
242 },
243 ["yousuck"] = {
244 canRotate = false,
245 shape = {
246 "c c ccc c c ccc c c ccc c c",
247 "c c c c c c c c c c c c c c ",
248 "c c c c c c c c c c c c ",
249 " c c c c c c ccc c c c cc ",
250 " c c c c c c c c c c c ",
251 " c c c c c c c c c c c ",
252 " c c c c c c c c c c c c c",
253 " c ccc ccc ccc ccc ccc c c",
254 }
255 },
256 ["eatmyass"] = {
257 canRotate = false,
258 shape = {
259 "ccccc ccc ccccc c c c c ccc ccc ccc ",
260 "c c c c cc cc c c c c c c c c",
261 "c c c c c c c c c c c c c c ",
262 "cccc ccccc c c c c c ccccc ccc ccc ",
263 "c c c c c c c c c c c",
264 "c c c c c c c c c c c",
265 "c c c c c c c c c c c c c",
266 "ccccc c c c c c c c c ccc ccc ",
267 }
268 },
269 ["nice"] = { -- nice
270 canRotate = false,
271 shape = {
272 " c ",
273 " ",
274 "c ccc c cccc cccc ",
275 "c c c c c c c c ",
276 "cc c c c c c ",
277 "c c c c cccccc ",
278 "c c c c c ",
279 "c c c c c ",
280 "c c c c c c c ",
281 "c c c cccc cccc c",
282 }
283 }
284}
285
286local images = {
287 -- to do...add images...
288}
289
290-- converts blit colors to colors api, and back
291local to_colors, to_blit = {
292 [' '] = 0,
293 ['0'] = 1,
294 ['1'] = 2,
295 ['2'] = 4,
296 ['3'] = 8,
297 ['4'] = 16,
298 ['5'] = 32,
299 ['6'] = 64,
300 ['7'] = 128,
301 ['8'] = 256,
302 ['9'] = 512,
303 ['a'] = 1024,
304 ['b'] = 2048,
305 ['c'] = 4096,
306 ['d'] = 8192,
307 ['e'] = 16384,
308 ['f'] = 32768,
309}, {}
310for k,v in pairs(to_colors) do
311 to_blit[v] = k
312end
313
314-- checks if (x, y) is a valid space on the board
315local doesSpaceExist = function(board, x, y)
316 return (x >= 1 and x <= board.xSize) and (y >= 1 and y <= board.ySize)
317end
318
319-- checks if (x, y) is being occupied by a tetramino (or if it's off-board)
320local isSpaceSolid = function(board, _x, _y)
321 local x, y = math.floor(_x), math.floor(_y)
322 if doesSpaceExist(board, x, y) then
323 return board[y][x][1]
324 else
325 return true
326 end
327end
328
329-- ticks down a space's timers, which can cause it to become non-solid or background-colored
330local ageSpace = function(board, _x, _y)
331 local x, y = math.floor(_x), math.floor(_y)
332 if doesSpaceExist(board, x, y) then
333 -- make space non-solid if timer elapses
334 if board[y][x][3] ~= 0 then
335 board[y][x][3] = board[y][x][3] - 1
336 if board[y][x][3] == 0 then
337 board[y][x][1] = false
338 end
339 end
340 -- color space board.BGcolor if timer elapses
341 if board[y][x][4] ~= 0 then
342 board[y][x][4] = board[y][x][4] - 1
343 if board[y][x][4] == 0 then
344 board[y][x][2] = board.BGcolor
345 end
346 end
347 end
348end
349
350local transmit = function(msg)
351 if game.net.active then
352 if game.net.useSkynet then
353 game.net.skynet.send(game.net.channel, msg)
354 else
355 game.net.modem.transmit(game.net.channel, game.net.channel, msg)
356 end
357 end
358end
359
360local sendInfo = function(command, doSendTime, playerNumber)
361 sentInfos = sentInfos + 1
362 if game.net.isHost then
363 transmit({
364 command = command,
365 gameID = game.net.gameID,
366 time = doSendTime and getTime(),
367 p = playerNumber and game.p[playerNumber] or game.p,
368 pNum = playerNumber,
369 you = game.you,
370 specialColor = {term.getPaletteColor(tColors.special)}
371 })
372 else
373 transmit({
374 command = command,
375 gameID = game.net.gameID,
376 time = doSendTime and getTime(),
377 pNum = playerNumber,
378 you = game.you,
379 control = game.p[game.you].control
380 })
381 end
382end
383
384-- generates a "mino" object, which can be drawn and manipulated on a board
385local makeNewMino = function(minoType, board, x, y, replaceColor)
386 local mino = tableCopy(minos[minoType])
387 if replaceColor then
388 for yy = 1, #mino.shape do
389 mino.shape[yy] = mino.shape[yy]:gsub("[^ ]", replaceColor)
390 end
391 end
392 -- what color the ghost mino will be
393 mino.ghostColor = 0x353535
394
395 mino.x = x
396 mino.y = y
397 mino.color = minos[minoType].mainColor or "c"
398 mino.didTspin = false -- if the player has done a T-spin with this piece
399 mino.lockBreaks = 16 -- anti-infinite measure
400 mino.waitingForLock = false
401 mino.board = board
402 mino.minoType = minoType
403 -- checks to see if the mino is currently clipping with a solid board space (with the offset values)
404 mino.checkCollision = function(xOffset, yOffset)
405 local cx, cy
406 for y = 1, #mino.shape do
407 for x = 1, #mino.shape[y] do
408 cx = mino.x + x + (xOffset or 0)
409 cy = mino.y + y + (yOffset or 0)
410 if mino.shape[y]:sub(x,x) ~= " " then
411 if isSpaceSolid(mino.board, cx, cy) then
412 return true
413 end
414 end
415 end
416 end
417 return false
418 end
419 -- rotates a mino, and kicks it off a wall if need be
420 mino.rotate = function(direction)
421 local output = {}
422 local oldShape = tableCopy(mino.shape)
423 local origX, origY = mino.x, mino.y
424 for y = 1, #mino.shape do
425 output[y] = {}
426 for x = 1, #mino.shape[y] do
427 if direction == 1 then
428 output[y][x] = mino.shape[#mino.shape - (x - 1)]:sub(y,y)
429 elseif direction == -1 then
430 output[y][x] = mino.shape[x]:sub(-y, -y)
431 else
432 error("invalid rotation direction (must be 1 or -1)")
433 end
434 end
435 output[y] = table.concat(output[y])
436 end
437 mino.shape = output
438 -- check T-spin
439 local checkTspin = function(mino)
440 if (mino.checkCollision(-1, 0) and mino.checkCollision(1, 0) and mino.checkCollision(0, -1)) then
441 mino.didTspin = true
442 return true
443 else
444 return false
445 end
446 end
447 -- try to kick off wall/floor
448 if mino.checkCollision(0, 0) then
449 -- try T-spin triple rotation
450 if not mino.checkCollision(-direction, 2) then
451 mino.y = mino.y + 2
452 mino.x = mino.x - direction
453 checkTspin(mino)
454 return true
455 end
456 -- kick off ceiling
457 for y = 1, #mino.shape do
458 for x = -1, 1 do
459 if not mino.checkCollision(x, y) then
460 mino.x = mino.x + x
461 mino.y = mino.y + y
462 --checkTspin(mino)
463 --return true
464 end
465 end
466 end
467 -- kick off floor
468 for y = 1, math.floor(#mino.shape / 2) do
469 if not mino.checkCollision(0, -y) then
470 mino.y = mino.y - y
471 checkTspin(mino)
472 return true
473 elseif not mino.checkCollision(-1, -y) then
474 mino.y = mino.y - y
475 mino.x = mino.x - 1
476 checkTspin(mino)
477 return true
478 elseif not mino.checkCollision(1, -y) then
479 mino.y = mino.y - y
480 mino.x = mino.x + 1
481 checkTspin(mino)
482 return true
483 end
484 end
485 -- kick off right wall
486 for x = 0, -math.floor(#mino.shape[1] / 2), -1 do
487 if not mino.checkCollision(x, 0) then
488 mino.x = mino.x + x
489 checkTspin(mino)
490 return true
491 end
492 -- try diagonal-down
493 if not mino.checkCollision(x, 1) then
494 mino.x = mino.x + x
495 mino.y = mino.y + 1
496 sendInfo("send_info", false)
497 checkTspin(mino)
498 return true
499 end
500 end
501 -- kick off left wall
502 for x = 0, math.floor(#mino.shape[1] / 2) do
503 if not mino.checkCollision(x, 0) then
504 mino.x = mino.x + x
505 checkTspin(mino)
506 return true
507 end
508 -- try diagonal-down
509 if not mino.checkCollision(x, 1) then
510 mino.x = mino.x + x
511 mino.y = mino.y + 1
512 sendInfo("send_info", false)
513 checkTspin(mino)
514 return true
515 end
516 end
517 mino.shape = oldShape
518 return false
519 else
520 return true
521 end
522 end
523 -- draws a mino onto a board; you'll still need to render the board, though
524 mino.draw = function(isSolid, colorReplace)
525 for y = 1, #mino.shape do
526 for x = 1, #mino.shape[y] do
527 if mino.shape[y]:sub(x,x) ~= " " then
528 if doesSpaceExist(mino.board, x + math.floor(mino.x), y + math.floor(mino.y)) then
529 mino.board[y + math.floor(mino.y)][x + math.floor(mino.x)] = {
530 isSolid or false,
531 colorReplace or mino.shape[y]:sub(x,x),
532 isSolid and 0 or 0,
533 isSolid and 0 or 1
534 }
535 end
536 end
537 end
538 end
539 end
540 -- moves a mino, making sure not to clip with solid board spaces
541 mino.move = function(x, y, doSlam)
542 if not mino.checkCollision(x, y) then
543 mino.x = mino.x + x
544 mino.y = mino.y + y
545 mino.didTspin = false
546 return true
547 elseif doSlam then
548 for sx = 0, x, math.abs(x) / x do
549 if mino.checkCollision(sx, 0) then
550 mino.x = mino.x + sx - math.abs(x) / x
551 break
552 end
553 if sx ~= 0 then
554 mino.didTspin = false
555 end
556 end
557 for sy = 0, math.ceil(y), math.abs(y) / y do
558 if mino.checkCollision(0, sy) then
559 mino.y = mino.y + sy - math.abs(y) / y
560 break
561 end
562 if sy ~= 0 then
563 mino.didTspin = false
564 end
565 end
566 else
567 return false
568 end
569 end
570
571 return mino
572end
573
574-- generates a random number, excluding those listed in the _psExclude table
575local pseudoRandom = function(randomPieces)
576 if game.config.scrubMode then
577 return 1
578 else
579 if #randomPieces == 0 then
580 for i = 1, #minos do
581 randomPieces[i] = i
582 end
583 end
584 local rand = math.random(1, #randomPieces)
585 local num = randomPieces[rand]
586 table.remove(randomPieces, rand)
587 return num
588 end
589end
590
591-- initialize players
592local initializePlayers = function(amountOfPlayers)
593 local newPlayer = function(xmod, ymod)
594 return {
595 xmod = xmod,
596 ymod = ymod,
597 control = {},
598 board = clearBoard({}, 2 + xmod, 2 + ymod, 10, nil, "f"),
599 holdBoard = clearBoard({}, 13 + xmod, 14 + ymod, 4, 3, "f", 0),
600 queueBoard = clearBoard({}, 13 + xmod, 2 + ymod, 4, 14, "f", 0),
601 randomPieces = {}, -- list of all minos for pseudo-random selection
602 flashingSpecial = false, -- if true, then this player is flashing the special color
603 }, {
604 frozen = false, -- if true, literally cannot move or act
605 hold = 0, -- current piece being held
606 canHold = true, -- whether or not player can hold (can't hold twice in a row)
607 queue = {}, -- current queue of minos to use
608 garbage = 0, -- amount of garbage you'll get after the next drop
609 lines = 0, -- amount of lines cleared, "points"
610 combo = 0, -- amount of consequative line clears
611 drawCombo = false, -- draw the combo message
612 lastLinesClear = 0, -- previous amount of simultaneous line clears (does not reset if miss)
613 level = 1, -- level determines speed of mino drop
614 fallSteps = 0.1, -- amount of spaces the mino will draw each drop
615 }
616 end
617
618 game.p = {}
619 game.pp = {}
620
621 for i = 1, (amountOfPlayers or 1) do
622 game.p[i], game.pp[i] = newPlayer((scr_x/2 - 8 * amountOfPlayers) + (i - 1) * 18, 0)
623 end
624
625 -- generates the initial queue of minos per player
626 for p = 1, #game.pp do
627 for i = 1, #minos do
628 game.pp[p].queue[i] = pseudoRandom(game.p[p].randomPieces)
629 end
630 end
631end
632
633-- actually renders a board to the screen
634local renderBoard = function(board, bx, by, doAgeSpaces, blankColor)
635 local char, line
636 local tY = board.y + (by or 0)
637 for y = (board.topCull or 0) + 1, board.ySize, 3 do
638 line = {("\143"):rep(board.xSize),"",""}
639 term.setCursorPos(board.x + (bx or 0), tY)
640 for x = 1, board.xSize do
641 line[2] = line[2] .. (blankColor or board[y][x][2])
642 if board[y + 1] then
643 line[3] = line[3] .. (blankColor or board[y + 1][x][2])
644 else
645 line[3] = line[3] .. board.BGcolor
646 end
647 end
648 term.blit(line[1], line[2], line[3])
649 line = {("\131"):rep(board.xSize),"",""}
650 term.setCursorPos(board.x + (bx or 0), tY + 1)
651 for x = 1, board.xSize do
652 if board[y + 2] then
653 line[2] = line[2] .. (blankColor or board[y + 1][x][2])
654 line[3] = line[3] .. (blankColor or board[y + 2][x][2])
655 elseif board[y + 1] then
656 line[2] = line[2] .. (blankColor or board[y + 1][x][2])
657 line[3] = line[3] .. board.BGcolor
658 else
659 line[2] = line[2] .. board.BGcolor
660 line[3] = line[3] .. board.BGcolor
661 end
662 end
663 term.blit(line[1], line[2], line[3])
664 tY = tY + 2
665 end
666 if doAgeSpaces then
667 for y = 1, board.ySize do
668 for x = 1, board.xSize do
669 ageSpace(board, x, y)
670 end
671 end
672 end
673end
674
675-- checks if you've done the one thing in tetris that you need to be doing
676local checkIfLineCleared = function(board, y)
677 for x = 1, board.xSize do
678 if not board[y][x][1] then
679 return false
680 end
681 end
682 return true
683end
684
685-- draws the score of a player, and clears the space where the combo text is drawn
686local drawScore = function(player, cPlayer)
687 if not cPlayer.drawCombo then
688 term.setCursorPos(2 + player.xmod, 18 + player.ymod)
689 term.setTextColor(tColors.white)
690 term.write((" "):rep(14))
691 term.setCursorPos(2 + player.xmod, 18 + player.ymod)
692 term.write("Lines: " .. cPlayer.lines)
693 term.setCursorPos(2 + player.xmod, 19 + player.ymod)
694 term.write((" "):rep(14))
695 end
696end
697
698local drawLevel = function(player, cPlayer)
699 term.setCursorPos(13 + player.xmod, 17 + player.ymod)
700 term.write("Lv" .. cPlayer.level .. " ")
701end
702
703-- draws the player's simultaneous line clear after clearing one or more lines
704-- also tells the player's combo, which is nice
705local drawComboMessage = function(player, cPlayer, lines, didTspin)
706 local msgs = {
707 "SINGLE",
708 "DOUBLE",
709 "TRIPLE",
710 "TETRIS"
711 }
712 if not msgs[lines] then
713 return
714 end
715 term.setCursorPos(player.xmod + 2, player.ymod + 18)
716 term.setTextColor(tColors.white)
717 term.write((" "):rep(16))
718 term.setCursorPos(player.xmod + 2, player.ymod + 18)
719 if didTspin then
720 term.write("T-SPIN ")
721 else
722 if lines == cPlayer.lastLinesCleared then
723 if lines == 3 then
724 term.write("OH BABY A ")
725 else
726 term.write("ANOTHER ")
727 end
728 end
729 end
730 term.write(msgs[lines])
731 if cPlayer.combo >= 2 then
732 term.setCursorPos(player.xmod + 2, player.ymod + 19)
733 term.setTextColor(tColors.white)
734 term.write((" "):rep(16))
735 term.setCursorPos(player.xmod + 2, player.ymod + 19)
736 if lines == 4 and cPlayer.combo == 3 then
737 term.write("HOLY SHIT!")
738 elseif lines == 4 and cPlayer.combo > 3 then
739 term.write("ALRIGHT JACKASS")
740 else
741 term.write(cPlayer.combo .. "x COMBO")
742 end
743 end
744
745end
746
747-- god damn it you've fucked up
748local gameOver = function(player, cPlayer)
749 local mino
750 local waitTime
751 if cPlayer.lines == 0 then
752 mino = makeNewMino("eatmyass", player.board, 12, 3 + game.boardOverflow)
753 waitTime = 70
754 elseif cPlayer.lines <= 5 then
755 mino = makeNewMino("yousuck", player.board, 12, 3 + game.boardOverflow)
756 waitTime = 60
757 elseif cPlayer.lines == 69 or cPlayer.lines == 690 then
758 mino = makeNewMino("nice", player.board, 12, 3 + game.boardOverflow)
759 waitTime = 50
760 else
761 mino = makeNewMino("gameover", player.board, 12, 3 + game.boardOverflow)
762 waitTime = 70
763 end
764 local color = 0
765 for i = 1, waitTime do
766 mino.x = mino.x - 1
767 mino.draw()
768 sendInfo("send_info", false)
769 renderBoard(player.board, 0, 0, true)
770 for i = 1, 20 do
771 color = color + 0.01
772 game.setPaletteColor(4096, math.sin(color) / 2 + 0.5, math.sin(color) / 2, math.sin(color) / 2)
773 end
774 sleep(0.1)
775 end
776 return
777end
778
779-- used when darkening just-placed pieces
780local normalPalettes = {}
781local darkerPalettes = {}
782for i = 1, 16 do
783 normalPalettes[("0123456789abcdef"):sub(i,i)] = {term.getPaletteColor(2 ^ (i - 1))}
784end
785local genDarkerPalettes = function(mult)
786 for i = 1, 16 do
787 darkerPalettes[("0123456789abcdef"):sub(i,i)] = {
788 normalPalettes[("0123456789abcdef"):sub(i,i)][1] * (mult or 0.6),
789 normalPalettes[("0123456789abcdef"):sub(i,i)][2] * (mult or 0.6),
790 normalPalettes[("0123456789abcdef"):sub(i,i)][3] * (mult or 0.6),
791 }
792 end
793end
794genDarkerPalettes()
795
796-- calculates the amount of garbage to send
797local calculateGarbage = function(lines, combo, backToBack, didTspin)
798 local output = 0
799 local clearTbl = {}
800 if didTspin then
801 clearTbl = {
802 2,
803 4,
804 6,
805 8,
806 10,
807 12,
808 14,
809 }
810 else
811 clearTbl = {
812 0,
813 1,
814 2,
815 4,
816 6,
817 8,
818 10
819 }
820 end
821 return (clearTbl[lines] or 0) + backToBack + math.max(0, combo - 2)
822end
823
824-- actually give a player some garbage
825local doleOutGarbage = function(player, cPlayer, amount)
826 local board = player.board
827 local gx = math.random(1, board.xSize)
828 local repeatProbability = 75 -- percent probability that garbage will leave the same hole open
829 for i = 1, amount do
830 table.remove(player.board, 1)
831 player.board[board.ySize] = {}
832 for x = 1, board.xSize do
833 if x ~= gx then
834 player.board[board.ySize][x] = {true, "8", 0, 0}
835 else
836 player.board[board.ySize][x] = {false, board.BGcolor, 0, 0}
837 end
838 end
839 if math.random(0, 100) > repeatProbability then
840 gx = math.random(1, board.xSize)
841 end
842 end
843 cPlayer.garbage = 0
844end
845
846local ldPalettes = function(light)
847 if light then
848 for k,v in pairs(normalPalettes) do
849 if k == "c" then
850 --term.setPaletteColor(to_colors[k], 0xf0f0f0)
851 else
852 game.setPaletteColor(to_colors[k], table.unpack(v))
853 end
854 end
855 else
856 for i = 1, 0.3, -0.1 do
857 genDarkerPalettes(i)
858 for k,v in pairs(darkerPalettes) do
859 if k == "c" then
860 --game.setPaletteColor(to_colors[k], 0xf0f0f0)
861 else
862 game.setPaletteColor(to_colors[k], table.unpack(v))
863 end
864 end
865 sleep(0.05)
866 end
867 end
868 genDarkerPalettes()
869end
870
871-- initiates a game as a specific player (takes a number)
872local startGame = function(playerNumber)
873
874 local mino, ghostMino
875 local dropTimer, inputTimer, lockTimer, tickTimer, comboTimer
876 local evt, board, player, cPlayer, control
877 local finished -- whether or not a mino is done being placed
878 local clearedLines = {} -- used when calculating cleared lines
879
880 if game.net.isHost then
881
882 player = game.p[playerNumber]
883 cPlayer = game.pp[playerNumber]
884 board = player.board
885 control = player.control
886
887 local draw = function(isSolid, doSendInfo)
888 local canChangeSpecial = true
889 for k,v in pairs(game.p) do
890 if v.flashingSpecial then
891 canChangeSpecial = false
892 break
893 end
894 end
895 if canChangeSpecial then
896 game.setPaletteColor(4096, mino.ghostColor)
897 end
898 ghostMino.x = mino.x
899 ghostMino.y = mino.y
900 ghostMino.move(0, board.ySize, true)
901 ghostMino.draw(false)
902 mino.draw(isSolid, ageSpaces)
903 if doSendInfo then
904 sendInfo("send_info", false, playerNumber)
905 end
906 renderBoard(board, 0, 0, true)
907 renderBoard(player.queueBoard, 0, 0, false)
908 end
909
910 local currentMinoType
911 local takeFromQueue = true
912
913 local interpretInput = function()
914 finished = false
915 game.cancelTimer(inputTimer)
916 inputTimer = game.startTimer(game.inputDelay)
917
918 if control.quit == 1 then
919 finished = true
920 game.running = false
921 game.paused = false
922 ldPalettes(true)
923 sendInfo("quit_game", false)
924 return
925 end
926
927 if game.paused then
928 if control.pause == 1 then
929 ldPalettes(true)
930 game.paused = false
931 end
932 else
933 if control.pause == 1 then
934 ldPalettes(false)
935 game.paused = true
936 end
937 if not cPlayer.frozen then
938 if control.moveLeft == 1 or (control.moveLeft or 0) > 1 + game.moveHoldDelay then
939 if mino.move(-1, 0) then
940 game.cancelTimer(lockTimer or 0)
941 mino.waitingForLock = false
942 draw(nil, true)
943 end
944 end
945 if control.moveRight == 1 or (control.moveRight or 0) >= 1 + game.moveHoldDelay then
946 if mino.move(1, 0) then
947 game.cancelTimer(lockTimer or 0)
948 mino.waitingForLock = false
949 draw(nil, true)
950 end
951 end
952 if control.moveDown then
953 game.cancelTimer(lockTimer or 0)
954 mino.waitingForLock = false
955 if mino.move(0, 1) then
956 draw(nil, true)
957 else
958 if game.config.TGMlock then
959 draw(true, true)
960 cPlayer.canHold = true
961 finished = true
962 else
963 if mino.waitingForLock then
964 game.alterTimer(lockTimer, -0.1)
965 else
966 mino.lockBreaks = mino.lockBreaks - 1
967 lockTimer = game.startTimer(math.max(0.2 / cPlayer.fallSteps, game.minimumLockTimer))
968 mino.waitingForLock = true
969 end
970 end
971 end
972 end
973 if control.rotateLeft == 1 then
974 if mino.rotate(-1) then
975 ghostMino.y = mino.y
976 ghostMino.rotate(-1)
977 game.cancelTimer(lockTimer or 0)
978 mino.waitingForLock = false
979 draw(nil, true)
980 end
981 end
982 if control.rotateRight == 1 then
983 if mino.rotate(1) then
984 ghostMino.y = mino.y
985 ghostMino.rotate(1)
986 game.cancelTimer(lockTimer or 0)
987 mino.waitingForLock = false
988 draw(nil, true)
989 end
990 end
991 if control.hold == 1 then
992 if cPlayer.canHold then
993 if cPlayer.hold == 0 then
994 takeFromQueue = true
995 else
996 takeFromQueue = false
997 end
998 cPlayer.hold, currentMinoType = currentMinoType, cPlayer.hold
999 cPlayer.canHold = false
1000 player.holdBoard = clearBoard(player.holdBoard, nil, nil, nil, nil, nil, nil, true)
1001 makeNewMino(
1002 cPlayer.hold,
1003 player.holdBoard,
1004 #minos[cPlayer.hold].shape[1] == 2 and 1 or 0,
1005 0
1006 ).draw(false)
1007 sendInfo("send_info", false, playerNumber)
1008 renderBoard(player.holdBoard, 0, 0, false)
1009 finished = true
1010 cPlayer.didHold = true
1011 end
1012 end
1013 if control.fastDrop == 1 then
1014 mino.move(0, board.ySize, true)
1015 if game.config.TGMlock then
1016 draw(false)
1017 else
1018 draw(true, true)
1019 cPlayer.canHold = true
1020 finished = true
1021 end
1022 end
1023 end
1024 end
1025 for k,v in pairs(player.control) do
1026 player.control[k] = v + game.inputDelay
1027 end
1028 end
1029
1030 renderBoard(player.holdBoard, 0, 0, true)
1031
1032 while game.running do
1033
1034 cPlayer.level = math.ceil((1 + cPlayer.lines) / 10)
1035 cPlayer.fallSteps = 0.075 * (1.33 ^ cPlayer.level)
1036 cPlayer.didHold = false
1037
1038 if takeFromQueue then
1039 currentMinoType = cPlayer.queue[1]
1040 end
1041
1042 mino = makeNewMino(
1043 currentMinoType,
1044 board,
1045 math.floor(board.xSize / 2) - 2,
1046 game.boardOverflow
1047 )
1048 cPlayer.mino = mino
1049
1050 ghostMino = makeNewMino(
1051 currentMinoType,
1052 board,
1053 math.floor(board.xSize / 2) - 2,
1054 game.boardOverflow,
1055 "c"
1056 )
1057 cPlayer.ghostMino = ghostMino
1058
1059 if takeFromQueue then
1060 table.remove(cPlayer.queue, 1)
1061 table.insert(cPlayer.queue, pseudoRandom(player.randomPieces))
1062 end
1063
1064 -- draw queue
1065 player.queueBoard = clearBoard(player.queueBoard, nil, nil, nil, nil, nil, nil, true)
1066 for i = 1, math.min(#cPlayer.queue, 4) do
1067 local m = makeNewMino(
1068 cPlayer.queue[i],
1069 player.queueBoard,
1070 #minos[cPlayer.queue[i]].shape[1] == 2 and 1 or 0,
1071 1 + (3 * (i - 1)) + (i > 1 and 2 or 0)
1072 )
1073 m.draw()
1074 end
1075
1076 -- draw held piece
1077 if cPlayer.hold ~= 0 then
1078 local m = makeNewMino(
1079 cPlayer.hold,
1080 player.holdBoard,
1081 #minos[cPlayer.hold].shape[1] == 2 and 1 or 0,
1082 0
1083 )
1084 end
1085
1086 takeFromQueue = true
1087
1088 drawScore(player, cPlayer)
1089 drawLevel(player, cPlayer)
1090
1091 term.setCursorPos(13 + player.xmod, 13 + player.ymod)
1092 term.write("HOLD")
1093
1094 -- check to see if you've topped out
1095 if mino.checkCollision() then
1096 for k,v in pairs(game.pp) do
1097 game.pp[k].frozen = true
1098 end
1099 sendInfo("game_over", false)
1100 gameOver(player, cPlayer)
1101 sendInfo("quit_game", false)
1102 return
1103 end
1104
1105 mino.draw()
1106 ghostMino.move(0, board.ySize, true)
1107 ghostMino.draw()
1108 sendInfo("send_info", false, playerNumber)
1109
1110 draw()
1111
1112
1113 game.cancelTimer(dropTimer)
1114 game.cancelTimer(lockTimer)
1115
1116 dropTimer = game.startTimer(0)
1117 inputTimer = game.startTimer(game.inputDelay)
1118 game.cancelTimer(lockTimer or 0)
1119
1120 tickTimer = os.startTimer(game.inputDelay)
1121
1122 -- drop a piece
1123 while game.running do
1124
1125 evt = {os.pullEvent()}
1126
1127 control = game.p[playerNumber].control
1128
1129 -- tick down internal game timer system
1130 if evt[1] == "timer" then
1131 if evt[2] == tickTimer then
1132 --local delKeys = {}
1133 for k,v in pairs(game.timers) do
1134 game.timers[k] = v - 0.05
1135 if v <= 0 then
1136 os.queueEvent("gameTimer", k)
1137 game.timers[k] = nil
1138 end
1139 end
1140 tickTimer = os.startTimer(game.inputDelay)
1141 elseif evt[2] == comboTimer then
1142 cPlayer.drawCombo = false
1143 drawScore(player, cPlayer)
1144 end
1145 end
1146
1147 if player.paused then
1148 if evt[1] == "gameTimer" then
1149 if control.pause == 1 then
1150 game.paused = false
1151 end
1152 end
1153 else
1154 if evt[1] == "key" and evt[3] == false then
1155
1156 interpretInput()
1157 if finished then
1158 break
1159 end
1160
1161 elseif evt[1] == "gameTimer" then
1162
1163 if evt[2] == inputTimer then
1164
1165 interpretInput()
1166 if finished then
1167 break
1168 end
1169
1170 elseif evt[2] == dropTimer then
1171 dropTimer = game.startTimer(0)
1172 if not game.paused then
1173 if not cPlayer.frozen then
1174 mino.oldY = mino.y
1175 if mino.checkCollision(0, 1) then
1176 if mino.lockBreaks == 0 then
1177 draw(true)
1178 cPlayer.canHold = true
1179 break
1180 elseif not mino.waitingForLock then
1181 mino.lockBreaks = mino.lockBreaks - 1
1182 lockTimer = game.startTimer(math.max(0.2 / cPlayer.fallSteps, game.minimumLockTimer))
1183 mino.waitingForLock = true
1184 end
1185 else
1186 mino.move(0, cPlayer.fallSteps, true)
1187 if math.floor(mino.oldY) ~= math.floor(mino.y) then
1188 draw(false, true)
1189 --sendInfo("send_info", false)
1190 end
1191 draw()
1192 end
1193 end
1194 end
1195 elseif evt[2] == lockTimer then
1196 if not game.paused then
1197 cPlayer.canHold = true
1198 draw(true)
1199 break
1200 end
1201 end
1202 end
1203 end
1204 end
1205
1206 clearedLines = {}
1207 for y = 1, board.ySize do
1208 if checkIfLineCleared(board, y) then
1209 table.insert(clearedLines, y)
1210 end
1211 end
1212 if #clearedLines == 0 then
1213 if cPlayer.canHold then
1214 cPlayer.combo = 0
1215 end
1216 else
1217 cPlayer.combo = cPlayer.combo + 1
1218 cPlayer.lines = cPlayer.lines + #clearedLines
1219 cPlayer.drawCombo = true
1220 os.cancelTimer(comboTimer or 0)
1221 comboTimer = os.startTimer(2)
1222 if cPlayer.lastLinesCleared == #clearedLines and #clearedLines >= 3 then
1223 player.backToBack = player.backToBack + 1
1224 else
1225 player.backToBack = 0
1226 end
1227
1228 drawComboMessage(player, cPlayer, #clearedLines, mino.didTspin)
1229
1230 cPlayer.lastLinesCleared = #clearedLines
1231
1232 -- give the other fucktard(s) some garbage
1233 cPlayer.garbage = cPlayer.garbage - calculateGarbage(#clearedLines, cPlayer.combo, player.backToBack, mino.didTspin) -- calculate T-spin later
1234 if cPlayer.garbage < 0 then
1235 for e, enemy in pairs(game.pp) do
1236 if e ~= playerNumber then
1237 enemy.garbage = enemy.garbage - cPlayer.garbage
1238 end
1239 end
1240 end
1241 cPlayer.garbage = math.max(0, cPlayer.garbage)
1242
1243 for l = 1, #clearedLines do
1244 for x = 1, board.xSize do
1245 board[clearedLines[l]][x][2] = "c"
1246 end
1247 end
1248 -- make the other network player see the flash
1249 sendInfo("flash_special", false)
1250 player.flashingSpecial = true
1251 renderBoard(board, 0, 0, true)
1252 for i = 1, 0, -0.12 do
1253 game.setPaletteColor(4096, i,i,i)
1254 sleep(0.05)
1255 end
1256 for i = #clearedLines, 1, -1 do
1257 table.remove(board, clearedLines[i])
1258 end
1259 for i = 1, #clearedLines do
1260 table.insert(board, 1, false)
1261 end
1262 board = clearBoard(board)
1263 player.flashingSpecial = false
1264 end
1265
1266 -- take some garbage for yourself
1267
1268 if cPlayer.garbage > 0 then
1269 doleOutGarbage(player, cPlayer, cPlayer.garbage)
1270 end
1271
1272 if #clearedLines == 0 and game.running and not cPlayer.didHold then
1273 mino.draw(false, "c")
1274 game.setPaletteColor(colors.brown, table.unpack(darkerPalettes[mino.color]))
1275 renderBoard(board, 0, 0, true)
1276 sleep(game.appearanceDelay)
1277 mino.draw(true)
1278 renderBoard(board, 0, 0, false)
1279 end
1280 end
1281 else
1282 -- if you're a client, take in all that board info and just fukkin draw it
1283
1284 inputTimer = os.startTimer(game.inputDelay)
1285
1286 local timeoutTimer = os.startTimer(3)
1287
1288 while game.running do
1289 evt = {os.pullEvent()}
1290 if evt[1] == "new_player_info" then
1291 player = game.p[game.you]
1292 for k,v in pairs(game.p) do
1293 renderBoard(v.board, 0, 0, false)
1294 renderBoard(v.holdBoard, 0, 0, false)
1295 renderBoard(v.queueBoard, 0, 0, false)
1296 drawScore(v, game.pp[k])
1297 drawLevel(v, game.pp[k])
1298 term.setCursorPos(13 + v.xmod, 13 + v.ymod)
1299 term.write("HOLD")
1300 end
1301 os.cancelTimer(timeoutTimer or 0)
1302 timeoutTimer = os.startTimer(3)
1303 elseif evt[1] == "timer" then
1304 if evt[2] == inputTimer then
1305 os.cancelTimer(inputTimer or 0)
1306 inputTimer = os.startTimer(game.inputDelay)
1307 if player then
1308 for k,v in pairs(player.control) do
1309 player.control[k] = v + game.inputDelay
1310 end
1311 end
1312 elseif evt[2] == timeoutTimer then
1313 return
1314 end
1315 end
1316 end
1317
1318 end
1319end
1320
1321-- records all key input
1322local getInput = function()
1323 local evt
1324 while true do
1325 evt = {os.pullEvent()}
1326 if evt[1] == "key" and evt[3] == false then
1327 keysDown[evt[2]] = 1
1328 elseif evt[1] == "key_up" then
1329 keysDown[evt[2]] = nil
1330 end
1331 if (evt[1] == "key" and evt[3] == false) or (evt[1] == "key_up") then
1332 if game.revControl[evt[2]] then
1333 game.p[game.you].control[game.revControl[evt[2]]] = keysDown[evt[2]]
1334 if not game.net.isHost then
1335 sendInfo("send_info", false)
1336 end
1337 end
1338 end
1339 end
1340end
1341
1342local cTime
1343local networking = function()
1344 local evt, side, channel, repchannel, msg, distance
1345 local currentPlayers = 1
1346 while true do
1347 if game.net.useSkynet then
1348 evt, channel, msg = os.pullEvent("skynet_message")
1349 else
1350 evt, side, channel, repchannel, msg, distance = os.pullEvent("modem_message")
1351 end
1352 if channel == game.net.channel and type(msg) == "table" then
1353 if game.net.waitingForGame then
1354 if type(msg.time) == "number" and msg.command == "find_game" then
1355 if msg.instanceID ~= game.instanceID then
1356 if msg.time < cTime then
1357 game.net.isHost = false
1358 game.you = 2
1359 game.net.gameID = msg.gameID
1360 else
1361 game.net.isHost = true
1362 end
1363
1364 transmit({
1365 gameID = game.net.gameID,
1366 time = cTime,
1367 command = "find_game",
1368 instanceID = game.instanceID
1369 })
1370 game.net.waitingForGame = false
1371 os.queueEvent("new_game", game.net.gameID)
1372 return game.net.gameID
1373 end
1374 end
1375 else
1376 if msg.gameID == game.net.gameID then
1377
1378 if game.net.isHost then
1379 if type(msg.control) == "table" then
1380 if type(msg.you) == "number" and msg.you ~= game.you then
1381 game.p[msg.you].control = msg.control
1382 for y = 1, game.p[msg.you].board.ySize do
1383 for x = 1, game.p[msg.you].board.xSize do
1384 ageSpace(game.p[msg.you].board, x, y)
1385 end
1386 end
1387 end
1388 end
1389 else
1390 if type(msg.you) == "number" and msg.you ~= game.you then
1391 if type(msg.p) == "table" then
1392 if msg.pNum then
1393 for k,v in pairs(msg.p) do
1394 if k ~= "control" then
1395 game.p[msg.pNum][k] = v
1396 end
1397 end
1398 else
1399 game.p = msg.p
1400 end
1401 if msg.specialColor then
1402 game.setPaletteColor(tColors.special, table.unpack(msg.specialColor))
1403 end
1404 os.queueEvent("new_player_info", msg.p)
1405 end
1406 if msg.command == "quit_game" then
1407 return
1408 end
1409 if msg.command == "flash_special" then
1410 for i = 1, 0, -0.12 do
1411 game.setPaletteColor(4096, i,i,i)
1412 renderBoard(game.p[msg.you].board, 0, 0, true)
1413 sleep(0.05)
1414 end
1415 end
1416 end
1417 end
1418
1419 end
1420 end
1421 end
1422 end
1423end
1424
1425local cwrite = function(text, y, xdiff, wordPosCheck)
1426 wordPosCheck = wordPosCheck or #text
1427 term.setCursorPos(math.floor(scr_x / 2 - math.floor(0.5 + #text + (xdiff or 0)) / 2), y or (scr_y - 2))
1428 term.write(text)
1429 return (scr_x / 2) - (#text / 2) + wordPosCheck
1430end
1431
1432local setUpModem = function()
1433 if game.net.useSkynet then
1434 if fs.exists(game.net.skynetPath) then
1435 game.net.skynet = dofile(game.net.skynetPath)
1436 term.clear()
1437 cwrite("Connecting to Skynet...", scr_y / 2)
1438 game.net.skynet.open(game.net.channel)
1439 return true
1440 else
1441 term.clear()
1442 cwrite("Downloading Skynet...", scr_y / 2)
1443 local prog = http.get(game.net.skynetURL)
1444 if prog then
1445 local file = fs.open(game.net.skynetPath, "w")
1446 file.write(prog.readAll())
1447 file.close()
1448 skynet = dofile(game.net.skynetPath)
1449 cwrite("Connecting to Skynet...", 1 + scr_y / 2)
1450 skynet.open(game.net.channel)
1451 return true
1452 else
1453 return false, "Could not download Skynet."
1454 end
1455 end
1456 else
1457 -- unload / close skynet
1458 if game.net.skynet then
1459 if game.net.skynet.socket then
1460 game.net.skynet.socket.close()
1461 end
1462 game.net.skynet = nil
1463 end
1464 game.net.modem = peripheral.find("modem")
1465 if (not game.net.modem) and ccemux then
1466 ccemux.attach("top", "wireless_modem")
1467 game.net.modem = peripheral.find("modem")
1468 end
1469 if game.net.modem then
1470 game.net.modem.open(game.net.channel)
1471 return true
1472 else
1473 return false, "No modem was found."
1474 end
1475 end
1476end
1477
1478local pleaseWait = function()
1479 local periods = 1
1480 local maxPeriods = 5
1481 term.setBackgroundColor(tColors.black)
1482 term.setTextColor(tColors.gray)
1483 term.clear()
1484
1485 local tID = os.startTimer(0.2)
1486 local evt, txt
1487 if game.net.useSkynet then
1488 txt = "Waiting for Skynet game"
1489 else
1490 txt = "Waiting for modem game"
1491 end
1492
1493 while true do
1494 cwrite("(Press 'Q' to cancel)", 2)
1495 cwrite(txt, scr_y - 2, maxPeriods)
1496 term.write(("."):rep(periods))
1497 evt = {os.pullEvent()}
1498 if evt[1] == "timer" and evt[2] == tID then
1499 tID = os.startTimer(0.5)
1500 periods = (periods % maxPeriods) + 1
1501 term.clearLine()
1502 elseif evt[1] == "key" and evt[2] == keys.q then
1503 return
1504 end
1505 end
1506end
1507
1508local titleScreen = function() -- mondo placeholder
1509 term.setTextColor(tColors.white)
1510 term.setBackgroundColor(tColors.black)
1511 term.clear()
1512 cwrite("LDris", 3)
1513 cwrite("by LDDestroier", 5)
1514 cwrite("Press 1 to play a game.", 7)
1515 if game.net.useSkynet then
1516 cwrite("Press 2 to play an HTTP game.", 8)
1517 cwrite("Press S to disable Skynet.", 9)
1518 else
1519 cwrite("Press 2 to play a modem game.", 8)
1520 cwrite("Press S to enable Skynet.", 9)
1521 end
1522 cwrite("Press H to see controls.", 10)
1523 cwrite("Press Q to quit.", 11)
1524 local evt
1525 while true do
1526 evt = {os.pullEvent()}
1527 if evt[1] == "key" then
1528 if evt[2] == keys.one then
1529 return "1P"
1530 elseif evt[2] == keys.two then
1531 return "2P"
1532 elseif evt[2] == keys.s then
1533 return "skynet"
1534 elseif evt[2] == keys.h then
1535 return "help"
1536 elseif evt[2] == keys.q then
1537 return "quit"
1538 end
1539 end
1540 end
1541end
1542
1543local screenError = function(...)
1544 local lines = {...}
1545 term.setBackgroundColor(tColors.black)
1546 term.setTextColor(tColors.white)
1547 term.clear()
1548 for i = 1, #lines do
1549 cwrite(lines[i], 2 + i)
1550 end
1551 cwrite("Press any key to continue.", #lines + 4)
1552 sleep(0)
1553 repeat until os.pullEvent("key")
1554end
1555
1556-- a lot of these menus and whatnot are very primitive. I can improve that later
1557local showHelp = function()
1558 term.setBackgroundColor(tColors.black)
1559 term.setTextColor(tColors.white)
1560 term.clear()
1561 cwrite("CONTROLS (defaults):", 2)
1562 term.setCursorPos(1, 4)
1563 print(" Move Piece: LEFT and RIGHT")
1564 print(" Hard Drop: UP")
1565 print(" Fast Drop: DOWN")
1566 print(" Rotate Piece: Z and X")
1567 print(" Hold Piece: L.SHIFT")
1568 print(" Quit: Q")
1569 print("\n Press any key to continue.")
1570 sleep(0)
1571 repeat until os.pullEvent("key")
1572end
1573
1574local main = function()
1575
1576 local rVal -- please wait result
1577 local modeVal -- title screen mode result
1578 local funcs
1579
1580 setUpModem()
1581
1582 while true do
1583
1584 game.you = 1
1585 game.net.isHost = true
1586 finished = false
1587 game.running = true
1588
1589 while true do
1590 modeVal = titleScreen()
1591
1592 if modeVal == "1P" then
1593 game.net.active = false
1594 game.amountOfPlayers = 1
1595 game.gameDelay = 0.05
1596 break
1597 elseif modeVal == "2P" then
1598 if setUpModem() then
1599 if (game.net.skynet and game.net.useSkynet) then
1600 game.gameDelay = 0.1
1601 else
1602 game.gameDelay = 0.05
1603 end
1604 game.net.active = true
1605 game.amountOfPlayers = 2
1606 break
1607 else
1608 screenError("A modem is required for multiplayer.")
1609 finished = true
1610 end
1611 elseif modeVal == "skynet" then
1612 if http.websocket then
1613 game.net.useSkynet = not game.net.useSkynet
1614 setUpModem()
1615 else
1616 screenError(
1617 "Skynet requires websocket support.",
1618 "Use CCEmuX, CC:Tweaked,",
1619 "or CraftOS-PC 2.2 or higher to play."
1620 )
1621 finished = true
1622 end
1623 elseif modeVal == "help" then
1624 showHelp()
1625 elseif modeVal == "quit" then
1626 return false
1627 end
1628 end
1629
1630 if game.net.active then
1631
1632 game.net.waitingForGame = true
1633
1634 cTime = getTime()
1635 transmit({
1636 gameID = game.net.gameID,
1637 time = cTime,
1638 command = "find_game",
1639 instanceID = game.instanceID
1640 })
1641 if game.net.useSkynet then
1642 rVal = parallel.waitForAny( networking, pleaseWait, game.net.skynet.listen )
1643 else
1644 rVal = parallel.waitForAny( networking, pleaseWait )
1645 end
1646 sleep(0.1)
1647
1648 if rVal == 1 then
1649
1650 funcs = {
1651 getInput,
1652 }
1653
1654 if game.net.active then
1655 table.insert(funcs, networking)
1656 if game.net.useSkynet and game.net.skynet then
1657 table.insert(funcs, game.net.skynet.listen)
1658 end
1659 end
1660
1661 initializePlayers(game.amountOfPlayers or 1)
1662
1663 if game.net.isHost then
1664 for k,v in pairs(game.p) do
1665 funcs[#funcs + 1] = function()
1666 return startGame(k)
1667 end
1668 end
1669 else
1670 funcs[#funcs + 1] = startGame
1671 end
1672
1673 else
1674 finished = true
1675 end
1676
1677 else
1678
1679 funcs = {getInput}
1680 initializePlayers(game.amountOfPlayers or 1)
1681
1682 for k,v in pairs(game.p) do
1683 funcs[#funcs + 1] = function()
1684 return startGame(k)
1685 end
1686 end
1687
1688 end
1689
1690 if not finished then
1691
1692 term.setBackgroundColor(tColors.gray)
1693 term.clear()
1694
1695 parallel.waitForAny(table.unpack(funcs))
1696
1697 end
1698
1699 end
1700
1701end
1702
1703main()
1704
1705-- reset palette to back from whence it came
1706for k,v in pairs(colors) do
1707 if type(v) == "number" then
1708 term.setPaletteColor(v, term.nativePaletteColor(v))
1709 end
1710end
1711
1712term.setBackgroundColor(colors.black)
1713term.setTextColor(colors.white)
1714
1715if game.net.skynet then
1716 if game.net.skynet.socket then
1717 game.net.skynet.socket.close()
1718 end
1719end
1720
1721for i = 1, 5 do
1722 term.scroll(1)
1723 if i == 3 then
1724 term.setCursorPos(1, scr_y)
1725 term.write("Thanks for playing!")
1726 end
1727 sleep(0.05)
1728end
1729term.setCursorPos(1, scr_y)