· 4 years ago · Jun 22, 2021, 10:40 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
269-- converts blit colors to colors api, and back
270local to_colors, to_blit = {
271 [' '] = 0,
272 ['0'] = 1,
273 ['1'] = 2,
274 ['2'] = 4,
275 ['3'] = 8,
276 ['4'] = 16,
277 ['5'] = 32,
278 ['6'] = 64,
279 ['7'] = 128,
280 ['8'] = 256,
281 ['9'] = 512,
282 ['a'] = 1024,
283 ['b'] = 2048,
284 ['c'] = 4096,
285 ['d'] = 8192,
286 ['e'] = 16384,
287 ['f'] = 32768,
288}, {}
289for k,v in pairs(to_colors) do
290 to_blit[v] = k
291end
292
293-- checks if (x, y) is a valid space on the board
294local doesSpaceExist = function(board, x, y)
295 return (x >= 1 and x <= board.xSize) and (y >= 1 and y <= board.ySize)
296end
297
298-- checks if (x, y) is being occupied by a tetramino (or if it's off-board)
299local isSpaceSolid = function(board, _x, _y)
300 local x, y = math.floor(_x), math.floor(_y)
301 if doesSpaceExist(board, x, y) then
302 return board[y][x][1]
303 else
304 return true
305 end
306end
307
308-- ticks down a space's timers, which can cause it to become non-solid or background-colored
309local ageSpace = function(board, _x, _y)
310 local x, y = math.floor(_x), math.floor(_y)
311 if doesSpaceExist(board, x, y) then
312 -- make space non-solid if timer elapses
313 if board[y][x][3] ~= 0 then
314 board[y][x][3] = board[y][x][3] - 1
315 if board[y][x][3] == 0 then
316 board[y][x][1] = false
317 end
318 end
319 -- color space board.BGcolor if timer elapses
320 if board[y][x][4] ~= 0 then
321 board[y][x][4] = board[y][x][4] - 1
322 if board[y][x][4] == 0 then
323 board[y][x][2] = board.BGcolor
324 end
325 end
326 end
327end
328
329-- generates a "mino" object, which can be drawn and manipulated on a board
330local makeNewMino = function(minoType, board, x, y, replaceColor)
331 local mino = tableCopy(minos[minoType])
332 if replaceColor then
333 for yy = 1, #mino.shape do
334 mino.shape[yy] = mino.shape[yy]:gsub("[^ ]", replaceColor)
335 end
336 end
337 -- what color the ghost mino will be
338 mino.ghostColor = 0x353535
339
340 mino.x = x
341 mino.y = y
342 mino.didWallKick = false
343 mino.color = minos[minoType].mainColor or "c"
344 mino.didTspin = false -- if the player has done a T-spin with this piece
345 mino.lockBreaks = 16 -- anti-infinite measure
346 mino.waitingForLock = false
347 mino.board = board
348 mino.minoType = minoType
349 -- checks to see if the mino is currently clipping with a solid board space (with the offset values)
350 mino.checkCollision = function(xOffset, yOffset, doNotCountBorder)
351 local cx, cy
352 for y = 1, #mino.shape do
353 for x = 1, #mino.shape[y] do
354 cx = mino.x + x + (xOffset or 0)
355 cy = mino.y + y + (yOffset or 0)
356 if mino.shape[y]:sub(x,x) ~= " " then
357 if isSpaceSolid(mino.board, cx, cy) and not (doNotCountBorder and not doesSpaceExist(board, cx, cy)) then
358 return true
359 end
360 end
361 end
362 end
363 return false
364 end
365 -- rotates a mino, and kicks it off a wall if need be
366 mino.rotate = function(direction)
367 local output = {}
368 local oldShape = tableCopy(mino.shape)
369 local origX, origY = mino.x, mino.y
370 for y = 1, #mino.shape do
371 output[y] = {}
372 for x = 1, #mino.shape[y] do
373 if direction == 1 then
374 output[y][x] = mino.shape[#mino.shape - (x - 1)]:sub(y,y)
375 elseif direction == -1 then
376 output[y][x] = mino.shape[x]:sub(-y, -y)
377 else
378 error("invalid rotation direction (must be 1 or -1)")
379 end
380 end
381 output[y] = table.concat(output[y])
382 end
383 mino.shape = output
384 -- check T-spin
385 local checkTspin = function(mino)
386 if (mino.checkCollision(-1, 0, false) and mino.checkCollision(1, 0, false) and mino.checkCollision(0, -1, false)) then
387 if mino.didWallKick then
388 mino.didTspin = 1
389 else
390 mino.didTspin = 2
391 end
392 else
393 mino.didTspin = false
394 end
395 return mino.didTspin
396 end
397 -- try to kick off wall/floor
398 if mino.checkCollision(0, 0) then
399 -- try T-spin triple rotation
400 if not mino.checkCollision(-direction, 2) then
401 mino.y = mino.y + 2
402 mino.x = mino.x - direction
403 mino.didWallKick = true
404 checkTspin(mino)
405 return true
406 end
407 -- kick off ceiling
408 for y = 1, #mino.shape do
409 for x = -1, 1 do
410 if not mino.checkCollision(x, y) then
411 mino.x = mino.x + x
412 mino.y = mino.y + y
413 mino.didWallKick = true
414
415 --checkTspin(mino)
416 --return true
417 end
418 end
419 end
420 -- kick off floor
421 for y = 1, math.floor(#mino.shape / 2) do
422 if not mino.checkCollision(0, -y) then
423 mino.y = mino.y - y
424 mino.didWallKick = true
425 checkTspin(mino)
426 return true
427 elseif not mino.checkCollision(-1, -y) then
428 mino.y = mino.y - y
429 mino.x = mino.x - 1
430 mino.didWallKick = true
431 checkTspin(mino)
432 return true
433 elseif not mino.checkCollision(1, -y) then
434 mino.y = mino.y - y
435 mino.x = mino.x + 1
436 mino.didWallKick = true
437 checkTspin(mino)
438 return true
439 end
440 end
441 -- kick off right wall
442 for x = 0, -math.floor(#mino.shape[1] / 2), -1 do
443 if not mino.checkCollision(x, 0) then
444 mino.x = mino.x + x
445 checkTspin(mino)
446 return true
447 end
448 -- try diagonal-down
449 if not mino.checkCollision(x, 1) then
450 mino.x = mino.x + x
451 mino.y = mino.y + 1
452 sendInfo("send_info", false)
453 checkTspin(mino)
454 return true
455 end
456 end
457 -- kick off left wall
458 for x = 0, math.floor(#mino.shape[1] / 2) do
459 if not mino.checkCollision(x, 0) then
460 mino.x = mino.x + x
461 mino.didWallKick = true
462 checkTspin(mino)
463 return true
464 end
465 -- try diagonal-down
466 if not mino.checkCollision(x, 1) then
467 mino.x = mino.x + x
468 mino.y = mino.y + 1
469 sendInfo("send_info", false)
470 mino.didWallKick = true
471 checkTspin(mino)
472 return true
473 end
474 end
475 mino.shape = oldShape
476 return false
477 else
478 checkTspin(mino)
479 return true
480 end
481 end
482 -- draws a mino onto a board; you'll still need to render the board, though
483 mino.draw = function(isSolid, colorReplace)
484 for y = 1, #mino.shape do
485 for x = 1, #mino.shape[y] do
486 if mino.shape[y]:sub(x,x) ~= " " then
487 if doesSpaceExist(mino.board, x + math.floor(mino.x), y + math.floor(mino.y)) then
488 mino.board[y + math.floor(mino.y)][x + math.floor(mino.x)] = {
489 isSolid or false,
490 colorReplace or mino.shape[y]:sub(x,x),
491 isSolid and 0 or 0,
492 isSolid and 0 or 1
493 }
494 end
495 end
496 end
497 end
498 end
499 -- moves a mino, making sure not to clip with solid board spaces
500 mino.move = function(x, y, doSlam)
501 if not mino.checkCollision(x, y) then
502 if not (x == 0 and y == 0) then
503 mino.x = mino.x + x
504 mino.y = mino.y + y
505 mino.didTspin = false
506 mino.didWallKick = false
507 end
508 return true
509 elseif doSlam then
510 for sx = 0, x, math.abs(x) / x do
511 if mino.checkCollision(sx, 0) then
512 mino.x = mino.x + sx - math.abs(x) / x
513 break
514 end
515 if sx ~= 0 then
516 mino.didWallKick = false
517 mino.didTspin = false
518 end
519 end
520 for sy = 0, math.ceil(y), math.abs(y) / y do
521 if mino.checkCollision(0, sy) then
522 mino.y = mino.y + sy - math.abs(y) / y
523 break
524 end
525 if sy ~= 0 then
526 mino.didWallKick = false
527 end
528 end
529 else
530 return false
531 end
532 end
533
534 return mino
535end
536
537-- generates a random number, excluding those listed in the _psExclude table
538local pseudoRandom = function(randomPieces)
539 if game.config.scrubMode then
540 return 1
541 else
542 if #randomPieces == 0 then
543 for i = 1, #minos do
544 randomPieces[i] = i
545 end
546 end
547 local rand = math.random(1, #randomPieces)
548 local num = randomPieces[rand]
549 table.remove(randomPieces, rand)
550 return num
551 end
552end
553
554-- initialize players
555local initializePlayers = function(amountOfPlayers)
556 local newPlayer = function(xmod, ymod)
557 return {
558 xmod = xmod,
559 ymod = ymod,
560 control = {},
561 board = clearBoard({}, 2 + xmod, 2 + ymod, 10, nil, "f"),
562 holdBoard = clearBoard({}, 13 + xmod, 14 + ymod, 4, 3, "f", 0),
563 queueBoard = clearBoard({}, 13 + xmod, 2 + ymod, 4, 14, "f", 0),
564 randomPieces = {}, -- list of all minos for pseudo-random selection
565 flashingSpecial = false, -- if true, then this player is flashing the special color
566 }, {
567 frozen = false, -- if true, literally cannot move or act
568 hold = 0, -- current piece being held
569 canHold = true, -- whether or not player can hold (can't hold twice in a row)
570 queue = {}, -- current queue of minos to use
571 garbage = 0, -- amount of garbage you'll get after the next drop
572 lines = 0, -- amount of lines cleared, "points"
573 combo = 0, -- amount of consequative line clears
574 drawCombo = false, -- draw the combo message
575 lastLinesClear = 0, -- previous amount of simultaneous line clears (does not reset if miss)
576 level = 1, -- level determines speed of mino drop
577 fallSteps = 0.1, -- amount of spaces the mino will draw each drop
578 }
579 end
580
581 game.p = {}
582 game.pp = {}
583
584 for i = 1, (amountOfPlayers or 1) do
585 game.p[i], game.pp[i] = newPlayer((scr_x/2 - 8 * amountOfPlayers) + (i - 1) * 18, 0)
586 end
587
588 -- generates the initial queue of minos per player
589 for p = 1, #game.pp do
590 for i = 1, #minos do
591 game.pp[p].queue[i] = pseudoRandom(game.p[p].randomPieces)
592 end
593 end
594end
595
596-- actually renders a board to the screen
597local renderBoard = function(board, bx, by, doAgeSpaces, blankColor)
598 local char, line
599 local tY = board.y + (by or 0)
600 for y = (board.topCull or 0) + 1, board.ySize, 3 do
601 line = {("\143"):rep(board.xSize),"",""}
602 term.setCursorPos(board.x + (bx or 0), tY)
603 for x = 1, board.xSize do
604 line[2] = line[2] .. (blankColor or board[y][x][2])
605 if board[y + 1] then
606 line[3] = line[3] .. (blankColor or board[y + 1][x][2])
607 else
608 line[3] = line[3] .. board.BGcolor
609 end
610 end
611 term.blit(line[1], line[2], line[3])
612 line = {("\131"):rep(board.xSize),"",""}
613 term.setCursorPos(board.x + (bx or 0), tY + 1)
614 for x = 1, board.xSize do
615 if board[y + 2] then
616 line[2] = line[2] .. (blankColor or board[y + 1][x][2])
617 line[3] = line[3] .. (blankColor or board[y + 2][x][2])
618 elseif board[y + 1] then
619 line[2] = line[2] .. (blankColor or board[y + 1][x][2])
620 line[3] = line[3] .. board.BGcolor
621 else
622 line[2] = line[2] .. board.BGcolor
623 line[3] = line[3] .. board.BGcolor
624 end
625 end
626 term.blit(line[1], line[2], line[3])
627 tY = tY + 2
628 end
629 if doAgeSpaces then
630 for y = 1, board.ySize do
631 for x = 1, board.xSize do
632 ageSpace(board, x, y)
633 end
634 end
635 end
636end
637
638-- checks if you've done the one thing in tetris that you need to be doing
639local checkIfLineCleared = function(board, y)
640 for x = 1, board.xSize do
641 if not board[y][x][1] then
642 return false
643 end
644 end
645 return true
646end
647
648-- draws the score of a player, and clears the space where the combo text is drawn
649local drawScore = function(player, cPlayer)
650 if not cPlayer.drawCombo then
651 term.setCursorPos(2 + player.xmod, 18 + player.ymod)
652 term.setTextColor(tColors.white)
653 term.write((" "):rep(14))
654 term.setCursorPos(2 + player.xmod, 18 + player.ymod)
655 term.write("Lines: " .. cPlayer.lines)
656 term.setCursorPos(2 + player.xmod, 19 + player.ymod)
657 term.write((" "):rep(14))
658 end
659end
660
661local drawLevel = function(player, cPlayer)
662 term.setCursorPos(13 + player.xmod, 17 + player.ymod)
663 term.write("Lv" .. cPlayer.level .. " ")
664end
665
666-- draws the player's simultaneous line clear after clearing one or more lines
667-- also tells the player's combo, which is nice
668local drawComboMessage = function(player, cPlayer, lines, didTspin, didDoAllClear)
669 local msgs = {
670 [0] = "",
671 "SINGLE",
672 "DOUBLE",
673 "TRIPLE",
674 "TETRIS"
675 }
676 if not msgs[lines] then
677 return
678 end
679 term.setCursorPos(player.xmod + 2, player.ymod + 18)
680 term.setTextColor(tColors.white)
681 term.write((" "):rep(16))
682 term.setCursorPos(player.xmod + 2, player.ymod + 18)
683 if didDoAllClear then
684 term.write("ALL CLEAR!! ")
685 else
686 if didTspin then
687 if didTspin == 1 then
688 term.write("T-SPIN MINI ")
689 elseif didTspin == 2 then
690 term.write("T-SPIN ")
691 end
692 else
693 if lines == cPlayer.lastLinesCleared then
694 if lines == 3 then
695 term.write("OH BABY A ")
696 else
697 term.write("ANOTHER ")
698 end
699 end
700 end
701 term.write(msgs[lines])
702 end
703 if cPlayer.combo >= 2 and lines > 0 then
704 term.setCursorPos(player.xmod + 2, player.ymod + 19)
705 term.setTextColor(tColors.white)
706 term.write((" "):rep(16))
707 term.setCursorPos(player.xmod + 2, player.ymod + 19)
708 if lines == 4 and cPlayer.combo == 3 then
709 term.write("Nice Job!")
710 elseif lines == 4 and cPlayer.combo > 3 then
711 term.write("Good")
712 else
713 term.write(cPlayer.combo .. "x COMBO")
714 end
715 end
716
717end
718
719local gameOver = function(player, cPlayer)
720 local mino
721 local waitTime
722 mino = makeNewMino("gameover", player.board, 12, 3 + game.boardOverflow)
723 waitTime = 70
724 local color = 0
725 local evt
726 local sTimer
727 local cTimer = os.startTimer(1)
728 local canSkip = false
729 for i = 1, waitTime do
730 mino.x = mino.x - 1
731 mino.draw()
732 sendInfo("send_info", false)
733 renderBoard(player.board, 0, 0, true)
734 for i = 1, 20 do
735 color = color + 0.01
736 game.setPaletteColor(4096, math.sin(color) / 2 + 0.5, math.sin(color) / 2, math.sin(color) / 2)
737 end
738
739 sTimer = os.startTimer(0.05)
740 while true do
741 evt = {os.pullEvent()}
742 if evt[1] == "timer" then
743 if evt[2] == sTimer then
744 break
745 elseif evt[2] == cTimer then
746 canSkip = true
747 end
748 elseif evt[1] == "key" then
749 if canSkip then
750 return
751 end
752 end
753 end
754 end
755 return
756end
757
758-- used when darkening just-placed pieces
759local normalPalettes = {}
760local darkerPalettes = {}
761for i = 1, 16 do
762 normalPalettes[("0123456789abcdef"):sub(i,i)] = {term.getPaletteColor(2 ^ (i - 1))}
763end
764local genDarkerPalettes = function(mult)
765 for i = 1, 16 do
766 darkerPalettes[("0123456789abcdef"):sub(i,i)] = {
767 normalPalettes[("0123456789abcdef"):sub(i,i)][1] * (mult or 0.6),
768 normalPalettes[("0123456789abcdef"):sub(i,i)][2] * (mult or 0.6),
769 normalPalettes[("0123456789abcdef"):sub(i,i)][3] * (mult or 0.6),
770 }
771 end
772end
773genDarkerPalettes()
774
775-- calculates the amount of garbage to send
776local calculateGarbage = function(lines, combo, backToBack, didTspin)
777 local output = 0
778 local clearTbl = {}
779 if didTspin then
780 clearTbl = {
781 2,
782 4,
783 6,
784 8,
785 10,
786 12,
787 14,
788 }
789 else
790 clearTbl = {
791 0,
792 1,
793 2,
794 4,
795 6,
796 8,
797 10
798 }
799 end
800 return (clearTbl[lines] or 0) + backToBack + math.max(0, combo - 2)
801end
802
803-- actually give a player some garbage
804local doleOutGarbage = function(player, cPlayer, amount)
805 local board = player.board
806 local gx = math.random(1, board.xSize)
807 local repeatProbability = 75 -- percent probability that garbage will leave the same hole open
808 for i = 1, amount do
809 table.remove(player.board, 1)
810 player.board[board.ySize] = {}
811 for x = 1, board.xSize do
812 if x ~= gx then
813 player.board[board.ySize][x] = {true, "8", 0, 0}
814 else
815 player.board[board.ySize][x] = {false, board.BGcolor, 0, 0}
816 end
817 end
818 if math.random(0, 100) > repeatProbability then
819 gx = math.random(1, board.xSize)
820 end
821 board.minoAmount = board.minoAmount + board.xSize - 1
822 end
823 cPlayer.garbage = 0
824end
825
826local ldPalettes = function(light)
827 if light then
828 for k,v in pairs(normalPalettes) do
829 if k == "c" then
830 --term.setPaletteColor(to_colors[k], 0xf0f0f0)
831 else
832 game.setPaletteColor(to_colors[k], table.unpack(v))
833 end
834 end
835 else
836 for i = 1, 0.3, -0.1 do
837 genDarkerPalettes(i)
838 for k,v in pairs(darkerPalettes) do
839 if k == "c" then
840 --game.setPaletteColor(to_colors[k], 0xf0f0f0)
841 else
842 game.setPaletteColor(to_colors[k], table.unpack(v))
843 end
844 end
845 sleep(0.05)
846 end
847 end
848 genDarkerPalettes()
849end
850
851-- initiates a game as a specific player (takes a number)
852local startGame = function(playerNumber)
853
854 local mino, ghostMino
855 local dropTimer, inputTimer, lockTimer, tickTimer, comboTimer
856 local evt, board, player, cPlayer, control
857 local finished -- whether or not a mino is done being placed
858 local clearedLines = {} -- used when calculating cleared lines
859
860 if game.net.isHost then
861
862 player = game.p[playerNumber]
863 cPlayer = game.pp[playerNumber]
864 board = player.board
865 control = player.control
866
867 local draw = function(isSolid, doSendInfo)
868 local canChangeSpecial = true
869 for k,v in pairs(game.p) do
870 if v.flashingSpecial then
871 canChangeSpecial = false
872 break
873 end
874 end
875 if canChangeSpecial then
876 game.setPaletteColor(4096, mino.ghostColor)
877 end
878 ghostMino.x = mino.x
879 ghostMino.y = mino.y
880 ghostMino.move(0, board.ySize, true)
881 ghostMino.draw(false)
882 mino.draw(isSolid, ageSpaces)
883 if doSendInfo then
884 sendInfo("send_info", false, playerNumber)
885 end
886 renderBoard(board, 0, 0, true)
887 renderBoard(player.queueBoard, 0, 0, false)
888 end
889
890 local currentMinoType
891 local takeFromQueue = true
892
893 local interpretInput = function()
894 finished = false
895 game.cancelTimer(inputTimer)
896 inputTimer = game.startTimer(game.inputDelay)
897
898 if control.quit == 1 then
899 finished = true
900 game.running = false
901 game.paused = false
902 ldPalettes(true)
903 sendInfo("quit_game", false)
904 return
905 end
906
907 if game.paused then
908 if control.pause == 1 then
909 ldPalettes(true)
910 game.paused = false
911 end
912 else
913 if control.pause == 1 then
914 ldPalettes(false)
915 game.paused = true
916 end
917 if not cPlayer.frozen then
918 if control.moveLeft == 1 or (control.moveLeft or 0) > 1 + game.moveHoldDelay then
919 if mino.move(-1, 0) then
920 game.cancelTimer(lockTimer or 0)
921 mino.waitingForLock = false
922 draw(nil, true)
923 end
924 end
925 if control.moveRight == 1 or (control.moveRight or 0) >= 1 + game.moveHoldDelay then
926 if mino.move(1, 0) then
927 game.cancelTimer(lockTimer or 0)
928 mino.waitingForLock = false
929 draw(nil, true)
930 end
931 end
932 if control.softDrop then
933 game.cancelTimer(lockTimer or 0)
934 mino.waitingForLock = false
935 local oldY = mino.y
936 mino.move(0, game.softDropSpeed, true)
937 if mino.y ~= oldY then
938 draw(nil, true)
939 else
940 if game.config.TGMlock then
941 draw(true, true)
942 cPlayer.canHold = true
943 finished = true
944 board.minoAmount = board.minoAmount + mino.size
945 else
946 if mino.waitingForLock then
947 game.alterTimer(lockTimer, -0.1)
948 else
949 mino.lockBreaks = mino.lockBreaks - 1
950 lockTimer = game.startTimer(math.min(math.max(0.2 / cPlayer.fallSteps, game.minimumLockTimer), game.maximumLockTimer))
951 mino.waitingForLock = true
952 end
953 end
954 end
955 end
956 if control.rotateLeft == 1 then
957 if mino.rotate(-1) then
958 ghostMino.y = mino.y
959 ghostMino.rotate(-1)
960 game.cancelTimer(lockTimer or 0)
961 mino.waitingForLock = false
962 draw(nil, true)
963 end
964 end
965 if control.rotateRight == 1 then
966 if mino.rotate(1) then
967 ghostMino.y = mino.y
968 ghostMino.rotate(1)
969 game.cancelTimer(lockTimer or 0)
970 mino.waitingForLock = false
971 draw(nil, true)
972 end
973 end
974 if control.hold == 1 then
975 if cPlayer.canHold then
976 if cPlayer.hold == 0 then
977 takeFromQueue = true
978 else
979 takeFromQueue = false
980 end
981 cPlayer.hold, currentMinoType = currentMinoType, cPlayer.hold
982 cPlayer.canHold = false
983 player.holdBoard = clearBoard(player.holdBoard, nil, nil, nil, nil, nil, nil, true)
984 makeNewMino(
985 cPlayer.hold,
986 player.holdBoard,
987 #minos[cPlayer.hold].shape[1] == 2 and 1 or 0,
988 0
989 ).draw(false)
990 sendInfo("send_info", false, playerNumber)
991 renderBoard(player.holdBoard, 0, 0, false)
992 finished = true
993 cPlayer.didHold = true
994 end
995 end
996 if control.hardDrop == 1 then
997 mino.move(0, board.ySize, true)
998 if game.config.TGMlock then
999 draw(false)
1000 else
1001 draw(true, true)
1002 cPlayer.canHold = true
1003 finished = true
1004 board.minoAmount = board.minoAmount + mino.size
1005 end
1006 elseif control.sonicDrop == 1 then
1007 mino.move(0, board.ySize, true)
1008 draw(false)
1009 end
1010 end
1011 end
1012 for k,v in pairs(player.control) do
1013 player.control[k] = v + game.inputDelay
1014 end
1015 end
1016
1017 renderBoard(player.holdBoard, 0, 0, true)
1018
1019 while game.running do
1020
1021 cPlayer.level = math.ceil((1 + cPlayer.lines) / 10)
1022 cPlayer.fallSteps = 0.075 * (1.33 ^ cPlayer.level)
1023 cPlayer.didHold = false
1024
1025 if takeFromQueue then
1026 currentMinoType = cPlayer.queue[1]
1027 end
1028
1029 mino = makeNewMino(
1030 currentMinoType,
1031 board,
1032 math.floor(board.xSize / 2) - 2,
1033 game.boardOverflow - 1
1034 )
1035 if mino.checkCollision() then
1036 mino.y = mino.y - 1
1037 end
1038 cPlayer.mino = mino
1039
1040 ghostMino = makeNewMino(
1041 currentMinoType,
1042 board,
1043 math.floor(board.xSize / 2) - 2,
1044 game.boardOverflow - 1,
1045 "c"
1046 )
1047 cPlayer.ghostMino = ghostMino
1048
1049 if takeFromQueue then
1050 table.remove(cPlayer.queue, 1)
1051 table.insert(cPlayer.queue, pseudoRandom(player.randomPieces))
1052 end
1053
1054 -- draw queue
1055 player.queueBoard = clearBoard(player.queueBoard, nil, nil, nil, nil, nil, nil, true)
1056 for i = 1, math.min(#cPlayer.queue, 4) do
1057 local m = makeNewMino(
1058 cPlayer.queue[i],
1059 player.queueBoard,
1060 #minos[cPlayer.queue[i]].shape[1] == 2 and 1 or 0,
1061 1 + (3 * (i - 1)) + (i > 1 and 2 or 0)
1062 )
1063 m.draw()
1064 end
1065
1066 -- draw held piece
1067 if cPlayer.hold ~= 0 then
1068 local m = makeNewMino(
1069 cPlayer.hold,
1070 player.holdBoard,
1071 #minos[cPlayer.hold].shape[1] == 2 and 1 or 0,
1072 0
1073 )
1074 end
1075
1076 takeFromQueue = true
1077
1078 drawScore(player, cPlayer)
1079 drawLevel(player, cPlayer)
1080
1081 term.setCursorPos(13 + player.xmod, 13 + player.ymod)
1082 term.write("HOLD")
1083
1084 -- check to see if you've topped out
1085 if mino.checkCollision() then
1086 for k,v in pairs(game.pp) do
1087 game.pp[k].frozen = true
1088 end
1089 sendInfo("game_over", false)
1090 gameOver(player, cPlayer)
1091 sendInfo("quit_game", false)
1092 return
1093 end
1094
1095 mino.draw()
1096 ghostMino.move(0, board.ySize, true)
1097 ghostMino.draw()
1098 sendInfo("send_info", false, playerNumber)
1099
1100 draw()
1101
1102
1103 game.cancelTimer(dropTimer)
1104 game.cancelTimer(lockTimer)
1105
1106 dropTimer = game.startTimer(0)
1107 inputTimer = game.startTimer(game.inputDelay)
1108 game.cancelTimer(lockTimer or 0)
1109
1110 tickTimer = os.startTimer(game.inputDelay)
1111
1112 -- drop a piece
1113 while game.running do
1114
1115 evt = {os.pullEvent()}
1116
1117 control = game.p[playerNumber].control
1118
1119 -- tick down internal game timer system
1120 if evt[1] == "timer" then
1121 if evt[2] == tickTimer then
1122 --local delKeys = {}
1123 for k,v in pairs(game.timers) do
1124 game.timers[k] = v - 0.05
1125 if v <= 0 then
1126 os.queueEvent("gameTimer", k)
1127 game.timers[k] = nil
1128 end
1129 end
1130 tickTimer = os.startTimer(game.inputDelay)
1131 elseif evt[2] == comboTimer then
1132 cPlayer.drawCombo = false
1133 drawScore(player, cPlayer)
1134 end
1135 end
1136
1137 if player.paused then
1138 if evt[1] == "gameTimer" then
1139 if control.pause == 1 then
1140 game.paused = false
1141 end
1142 end
1143 else
1144 if evt[1] == "key" and evt[3] == false then
1145
1146 interpretInput()
1147 if finished then
1148 break
1149 end
1150
1151 elseif evt[1] == "gameTimer" then
1152
1153 if evt[2] == inputTimer then
1154
1155 interpretInput()
1156 if finished then
1157 break
1158 end
1159
1160 elseif evt[2] == dropTimer then
1161 dropTimer = game.startTimer(0)
1162 if not game.paused then
1163 if not cPlayer.frozen then
1164 mino.oldY = mino.y
1165 if mino.checkCollision(0, 1) then
1166 if mino.lockBreaks == 0 then
1167 draw(true)
1168 cPlayer.canHold = true
1169 break
1170 elseif not mino.waitingForLock then
1171 mino.lockBreaks = mino.lockBreaks - 1
1172 lockTimer = game.startTimer(math.min(math.max(0.2 / cPlayer.fallSteps, game.minimumLockTimer), game.maximumLockTimer))
1173 mino.waitingForLock = true
1174 end
1175 else
1176 mino.move(0, cPlayer.fallSteps, true)
1177 if math.floor(mino.oldY) ~= math.floor(mino.y) then
1178 draw(false, true)
1179 --sendInfo("send_info", false)
1180 end
1181 draw()
1182 end
1183 end
1184 end
1185 elseif evt[2] == lockTimer then
1186 if not game.paused then
1187 cPlayer.canHold = true
1188 draw(true)
1189 break
1190 end
1191 end
1192 end
1193 end
1194 end
1195
1196 clearedLines = {}
1197 for y = 1, board.ySize do
1198 if checkIfLineCleared(board, y) then
1199 table.insert(clearedLines, y)
1200 board.minoAmount = board.minoAmount - board.xSize
1201 end
1202 end
1203 if #clearedLines == 0 then
1204 if cPlayer.canHold then
1205 cPlayer.combo = 0
1206 end
1207 if mino.didTspin then
1208 cPlayer.drawCombo = true
1209 os.cancelTimer(comboTimer or 0)
1210 comboTimer = os.startTimer(2)
1211 drawComboMessage(player, cPlayer, #clearedLines, mino.didTspin)
1212 end
1213 else
1214 cPlayer.combo = cPlayer.combo + 1
1215 cPlayer.lines = cPlayer.lines + #clearedLines
1216 cPlayer.drawCombo = true
1217 os.cancelTimer(comboTimer or 0)
1218 comboTimer = os.startTimer(2)
1219 if cPlayer.lastLinesCleared == #clearedLines and #clearedLines >= 3 then
1220 player.backToBack = player.backToBack + 1
1221 else
1222 player.backToBack = 0
1223 end
1224
1225 -- check for all clear
1226 local didDoAllClear = board.minoAmount == 0
1227
1228 drawComboMessage(player, cPlayer, #clearedLines, mino.didTspin, didDoAllClear)
1229
1230 cPlayer.lastLinesCleared = #clearedLines
1231
1232 cPlayer.garbage = (didDoAllClear and -10 or 0) + cPlayer.garbage - calculateGarbage(#clearedLines, cPlayer.combo, player.backToBack, mino.didTspin) -- calculate T-spin later
1233 if cPlayer.garbage < 0 then
1234 for e, enemy in pairs(game.pp) do
1235 if e ~= playerNumber then
1236 enemy.garbage = enemy.garbage - cPlayer.garbage
1237 end
1238 end
1239 end
1240 cPlayer.garbage = math.max(0, cPlayer.garbage)
1241
1242 for l = 1, #clearedLines do
1243 for x = 1, board.xSize do
1244 board[clearedLines[l]][x][2] = "c"
1245 end
1246 end
1247 -- make the other network player see the flash
1248 sendInfo("flash_special", false)
1249 player.flashingSpecial = true
1250 renderBoard(board, 0, 0, true)
1251 for i = 1, 0, -0.12 do
1252 game.setPaletteColor(4096, i,i,i)
1253 sleep(0.05)
1254 end
1255 for i = #clearedLines, 1, -1 do
1256 table.remove(board, clearedLines[i])
1257 end
1258 for i = 1, #clearedLines do
1259 table.insert(board, 1, false)
1260 end
1261 board = clearBoard(board)
1262 player.flashingSpecial = false
1263 end
1264
1265 -- take some garbage for yourself
1266
1267 if cPlayer.garbage > 0 then
1268 doleOutGarbage(player, cPlayer, cPlayer.garbage)
1269 end
1270
1271 if #clearedLines == 0 and game.running and not cPlayer.didHold then
1272 mino.draw(false, "c")
1273 game.setPaletteColor(colors.brown, table.unpack(darkerPalettes[mino.color]))
1274 renderBoard(board, 0, 0, true)
1275 sleep(game.appearanceDelay)
1276 mino.draw(true)
1277 --board.minoAmount = board.minoAmount + mino.size
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
1343
1344local cwrite = function(text, y, xdiff, wordPosCheck)
1345 wordPosCheck = wordPosCheck or #text
1346 term.setCursorPos(math.floor(scr_x / 2 - math.floor(0.5 + #text + (xdiff or 0)) / 2), y or (scr_y - 2))
1347 term.write(text)
1348 return (scr_x / 2) - (#text / 2) + wordPosCheck
1349end
1350
1351local titleScreen = function() -- mondo placeholder
1352 return "1P"
1353end
1354
1355local screenError = function(...)
1356 local lines = {...}
1357 term.setBackgroundColor(tColors.black)
1358 term.setTextColor(tColors.white)
1359 term.clear()
1360 for i = 1, #lines do
1361 cwrite(lines[i], 2 + i)
1362 end
1363 cwrite("Press any key to continue.", #lines + 4)
1364 sleep(0)
1365 repeat until os.pullEvent("key")
1366end
1367
1368-- a lot of these menus and whatnot are very primitive. I can improve that later
1369local showHelp = function()
1370 term.setBackgroundColor(tColors.black)
1371 term.setTextColor(tColors.white)
1372 term.clear()
1373 cwrite("CONTROLS (defaults):", 2)
1374 term.setCursorPos(1, 4)
1375 print(" Move Piece: LEFT and RIGHT")
1376 print(" Hard Drop: UP")
1377 print(" Soft Drop: DOWN")
1378 print(" Sonic Drop: SPACE")
1379 print(" Rotate Piece: Z and X")
1380 print(" Hold Piece: L.SHIFT")
1381 print(" Quit: Q")
1382 print("\n Press any key to continue.")
1383 sleep(0)
1384 repeat until os.pullEvent("key")
1385end
1386
1387local main = function()
1388
1389 local rVal -- please wait result
1390 local modeVal -- title screen mode result
1391 local funcs
1392
1393 setUpModem()
1394
1395 while true do
1396
1397 game.you = 1
1398 game.net.isHost = true
1399 finished = false
1400 game.running = true
1401
1402 game.net.active = false
1403 game.amountOfPlayers = 1
1404 game.gameDelay = 0.05
1405
1406 funcs = {getInput}
1407 initializePlayers(game.amountOfPlayers or 1)
1408
1409 for k,v in pairs(game.p) do
1410 funcs[#funcs + 1] = function()
1411 return startGame(k)
1412 end
1413 end
1414
1415 if not finished then
1416
1417 term.setBackgroundColor(tColors.gray)
1418 term.clear()
1419
1420 parallel.waitForAny(table.unpack(funcs))
1421
1422 end
1423
1424 end
1425
1426end
1427
1428main()
1429
1430-- reset palette to back from whence it came
1431for k,v in pairs(colors) do
1432 if type(v) == "number" then
1433 term.setPaletteColor(v, term.nativePaletteColor(v))
1434 end
1435end
1436
1437term.setBackgroundColor(colors.black)
1438term.setTextColor(colors.white)
1439
1440if game.net.skynet then
1441 if game.net.skynet.socket then
1442 game.net.skynet.socket.close()
1443 end
1444end
1445
1446for i = 1, 5 do
1447 term.scroll(1)
1448 if i == 3 then
1449 term.setCursorPos(1, scr_y)
1450 term.write("Thanks for playing!")
1451 end
1452 sleep(0.05)
1453end
1454term.setCursorPos(1, scr_y)