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