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