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