· 6 years ago · Mar 14, 2020, 02:56 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 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.1, -- 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 local waitTime
692 if cPlayer.lines == 0 then
693 mino = makeNewMino("eatmyass", player.board, 12, 3 + game.boardOverflow)
694 waitTime = 130
695 elseif cPlayer.lines <= 5 then
696 mino = makeNewMino("yousuck", player.board, 12, 3 + game.boardOverflow)
697 waitTime = 100
698 elseif cPlayer.lines == 69 or cPlayer.lines == 690 then
699 mino = makeNewMino("nice", player.board, 12, 3 + game.boardOverflow)
700 waitTime = 80
701 else
702 mino = makeNewMino("gameover", player.board, 12, 3 + game.boardOverflow)
703 waitTime = 90
704 end
705 local color = 0
706 for i = 1, waitTime do
707 mino.x = mino.x - 1
708 mino.draw()
709 sendInfo("send_info", false)
710 renderBoard(player.board, 0, 0, true)
711 for i = 1, 20 do
712 color = color + 0.01
713 term.setPaletteColor(4096, math.sin(color) / 2 + 0.5, math.sin(color) / 2, math.sin(color) / 2)
714 end
715 sleep(0.1)
716 end
717 return
718end
719
720-- calculates the amount of garbage to send
721local calculateGarbage = function(lines, combo, backToBack, didTspin)
722 local output = 0
723 local clearTbl = {}
724 if didTspin then
725 clearTbl = {
726 2,
727 4,
728 6,
729 8,
730 10,
731 12,
732 14,
733 }
734 else
735 clearTbl = {
736 0,
737 1,
738 2,
739 4,
740 6,
741 8,
742 10
743 }
744 end
745 return (clearTbl[lines] or 0) + backToBack + math.max(0, combo - 2)
746end
747
748-- actually give a player some garbage
749local doleOutGarbage = function(player, cPlayer, amount)
750 local board = player.board
751 local gx = math.random(1, board.xSize)
752 local repeatProbability = 75 -- percent probability that garbage will leave the same hole open
753 for i = 1, amount do
754 table.remove(player.board, 1)
755 player.board[board.ySize] = {}
756 for x = 1, board.xSize do
757 if x ~= gx then
758 player.board[board.ySize][x] = {true, "8", 0, 0}
759 else
760 player.board[board.ySize][x] = {false, board.BGcolor, 0, 0}
761 end
762 end
763 if math.random(0, 100) > repeatProbability then
764 gx = math.random(1, board.xSize)
765 end
766 end
767 cPlayer.garbage = 0
768end
769
770-- initiates a game as a specific player (takes a number)
771local startGame = function(playerNumber)
772
773 local mino, ghostMino
774 local dropTimer, inputTimer, lockTimer, tickTimer, comboTimer
775 local evt, board, player, cPlayer, control
776 local finished -- whether or not a mino is done being placed
777 local clearedLines = {} -- used when calculating cleared lines
778
779 if game.net.isHost then
780
781 player = game.p[playerNumber]
782 cPlayer = game.pp[playerNumber]
783 board = player.board
784 control = player.control
785
786 local draw = function(isSolid)
787 local canChangeSpecial = true
788 for k,v in pairs(game.p) do
789 if v.flashingSpecial then
790 canChangeSpecial = false
791 break
792 end
793 end
794 if canChangeSpecial then
795 term.setPaletteColor(4096, mino.ghostColor)
796 end
797 ghostMino.x = mino.x
798 ghostMino.y = mino.y
799 ghostMino.move(0, board.ySize, true)
800 ghostMino.draw(false)
801 mino.draw(isSolid, ageSpaces)
802 sendInfo("send_info", false, playerNumber)
803 renderBoard(board, 0, 0, true)
804 end
805
806 local currentMinoType
807 local takeFromQueue = true
808
809 local interpretInput = function()
810 finished = false
811 game.cancelTimer(inputTimer)
812 inputTimer = game.startTimer(game.inputDelay)
813
814 if control.quit == 1 then
815 finished = true
816 game.running = false
817 sendInfo("quit_game", false)
818 return
819 end
820
821 if game.paused then
822 if control.pause == 1 then
823 game.paused = false
824 end
825 else
826 if control.pause == 1 then
827 game.paused = true
828 end
829 if not cPlayer.frozen then
830 if control.moveLeft == 1 or (control.moveLeft or 0) > 1 + game.moveHoldDelay then
831 if mino.move(-1, 0) then
832 game.cancelTimer(lockTimer or 0)
833 mino.waitingForLock = false
834 draw()
835 end
836 end
837 if control.moveRight == 1 or (control.moveRight or 0) >= 1 + game.moveHoldDelay then
838 if mino.move(1, 0) then
839 game.cancelTimer(lockTimer or 0)
840 mino.waitingForLock = false
841 draw()
842 end
843 end
844 if control.moveDown then
845 game.cancelTimer(lockTimer or 0)
846 mino.waitingForLock = false
847 if mino.move(0, 1) then
848 draw()
849 else
850 if mino.waitingForLock then
851 game.alterTimer(lockTimer, -0.1)
852 else
853 mino.lockBreaks = mino.lockBreaks - 1
854 lockTimer = game.startTimer(math.max(0.2 / cPlayer.fallSteps, 0.5))
855 mino.waitingForLock = true
856 end
857 end
858 end
859 if control.rotateLeft == 1 then
860 if mino.rotate(-1) then
861 ghostMino.y = mino.y
862 ghostMino.rotate(-1)
863 game.cancelTimer(lockTimer or 0)
864 mino.waitingForLock = false
865 draw()
866 end
867 end
868 if control.rotateRight == 1 then
869 if mino.rotate(1) then
870 ghostMino.y = mino.y
871 ghostMino.rotate(1)
872 game.cancelTimer(lockTimer or 0)
873 mino.waitingForLock = false
874 draw()
875 end
876 end
877 if control.hold == 1 then
878 if cPlayer.canHold then
879 if cPlayer.hold == 0 then
880 takeFromQueue = true
881 else
882 takeFromQueue = false
883 end
884 cPlayer.hold, currentMinoType = currentMinoType, cPlayer.hold
885 cPlayer.canHold = false
886 player.holdBoard = clearBoard(player.holdBoard, nil, nil, nil, nil, nil, nil, true)
887 makeNewMino(
888 cPlayer.hold,
889 player.holdBoard,
890 #minos[cPlayer.hold].shape[1] == 2 and 1 or 0,
891 0
892 ).draw()
893 sendInfo("send_info", false, playerNumber)
894 renderBoard(player.holdBoard, 0, 0, false)
895 finished = true
896 end
897 end
898 if control.fastDrop == 1 then
899 mino.move(0, board.ySize, true)
900 draw(true)
901 cPlayer.canHold = true
902 finished = true
903 end
904 end
905 end
906 for k,v in pairs(player.control) do
907 player.control[k] = v + game.inputDelay
908 end
909 end
910
911 renderBoard(player.holdBoard, 0, 0, true)
912
913 while game.running do
914
915 cPlayer.level = math.ceil((1 + cPlayer.lines) / 10)
916 cPlayer.fallSteps = 0.075 * (1.33 ^ cPlayer.level)
917
918 if takeFromQueue then
919 currentMinoType = cPlayer.queue[1]
920 end
921
922 mino = makeNewMino(
923 currentMinoType,
924 board,
925 math.floor(board.xSize / 2) - 2,
926 game.boardOverflow
927 )
928 cPlayer.mino = mino
929
930 ghostMino = makeNewMino(
931 currentMinoType,
932 board,
933 math.floor(board.xSize / 2) - 2,
934 game.boardOverflow,
935 "c"
936 )
937 cPlayer.ghostMino = ghostMino
938
939 if takeFromQueue then
940 table.remove(cPlayer.queue, 1)
941 table.insert(cPlayer.queue, pseudoRandom(player.randomPieces))
942 end
943
944 -- draw queue
945 player.queueBoard = clearBoard(player.queueBoard, nil, nil, nil, nil, nil, nil, true)
946 for i = 1, math.min(#cPlayer.queue, 4) do
947 local m = makeNewMino(
948 cPlayer.queue[i],
949 player.queueBoard,
950 #minos[cPlayer.queue[i]].shape[1] == 2 and 1 or 0,
951 1 + (3 * (i - 1)) + (i > 1 and 2 or 0)
952 )
953 m.draw()
954 end
955 sendInfo("send_info", false, playerNumber)
956 renderBoard(player.queueBoard, 0, 0, false)
957
958 -- draw held piece
959 if cPlayer.hold ~= 0 then
960 local m = makeNewMino(
961 cPlayer.hold,
962 player.holdBoard,
963 #minos[cPlayer.hold].shape[1] == 2 and 1 or 0,
964 0
965 )
966 end
967
968 takeFromQueue = true
969
970 drawScore(player, cPlayer)
971 drawLevel(player, cPlayer)
972
973 term.setCursorPos(13 + player.xmod, 13 + player.ymod)
974 term.write("HOLD")
975
976 -- check to see if you've topped out
977 if mino.checkCollision() then
978 for k,v in pairs(game.pp) do
979 game.pp[k].frozen = true
980 end
981 sendInfo("game_over", false)
982 gameOver(player, cPlayer)
983 sendInfo("quit_game", false)
984 return
985 end
986
987 draw()
988
989
990 game.cancelTimer(dropTimer)
991 game.cancelTimer(lockTimer)
992
993 dropTimer = game.startTimer(0)
994 inputTimer = game.startTimer(game.inputDelay)
995 game.cancelTimer(lockTimer or 0)
996
997 tickTimer = os.startTimer(game.inputDelay)
998
999 -- drop a piece
1000 while game.running do
1001
1002 evt = {os.pullEvent()}
1003
1004 control = game.p[playerNumber].control
1005
1006 -- tick down internal game timer system
1007 if evt[1] == "timer" then
1008 if evt[2] == tickTimer then
1009 --local delKeys = {}
1010 for k,v in pairs(game.timers) do
1011 game.timers[k] = v - 0.05
1012 if v <= 0 then
1013 os.queueEvent("gameTimer", k)
1014 game.timers[k] = nil
1015 end
1016 end
1017 tickTimer = os.startTimer(game.inputDelay)
1018 elseif evt[2] == comboTimer then
1019 cPlayer.drawCombo = false
1020 drawScore(player, cPlayer)
1021 end
1022 end
1023
1024 if player.paused then
1025 if evt[1] == "gameTimer" then
1026 if control.pause == 1 then
1027 game.paused = false
1028 end
1029 end
1030 else
1031 if evt[1] == "key" and evt[3] == false then
1032
1033 interpretInput()
1034 if finished then
1035 break
1036 end
1037
1038 elseif evt[1] == "gameTimer" then
1039
1040 if evt[2] == inputTimer then
1041
1042 interpretInput()
1043 if finished then
1044 break
1045 end
1046
1047 elseif evt[2] == dropTimer then
1048 dropTimer = game.startTimer(0)
1049 if not game.paused then
1050 if not cPlayer.frozen then
1051 if mino.checkCollision(0, 1) then
1052 if mino.lockBreaks == 0 then
1053 draw(true)
1054 cPlayer.canHold = true
1055 break
1056 elseif not mino.waitingForLock then
1057 mino.lockBreaks = mino.lockBreaks - 1
1058 lockTimer = game.startTimer(math.max(0.2 / cPlayer.fallSteps, 0.25))
1059 mino.waitingForLock = true
1060 end
1061 else
1062 mino.move(0, cPlayer.fallSteps, true)
1063 draw()
1064 end
1065 end
1066 end
1067 elseif evt[2] == lockTimer then
1068 if not game.paused then
1069 cPlayer.canHold = true
1070 draw(true)
1071 break
1072 end
1073 end
1074 end
1075 end
1076 end
1077
1078 clearedLines = {}
1079 for y = 1, board.ySize do
1080 if checkIfLineCleared(board, y) then
1081 table.insert(clearedLines, y)
1082 end
1083 end
1084 if #clearedLines == 0 then
1085 if cPlayer.canHold then
1086 cPlayer.combo = 0
1087 end
1088 else
1089 cPlayer.combo = cPlayer.combo + 1
1090 cPlayer.lines = cPlayer.lines + #clearedLines
1091 cPlayer.drawCombo = true
1092 os.cancelTimer(comboTimer or 0)
1093 comboTimer = os.startTimer(2)
1094 if cPlayer.lastLinesCleared == #clearedLines and #clearedLines >= 3 then
1095 player.backToBack = player.backToBack + 1
1096 else
1097 player.backToBack = 0
1098 end
1099
1100 drawComboMessage(player, cPlayer, #clearedLines)
1101
1102 cPlayer.lastLinesCleared = #clearedLines
1103
1104 -- give the other fucktard(s) some garbage
1105 cPlayer.garbage = cPlayer.garbage - calculateGarbage(#clearedLines, cPlayer.combo, player.backToBack, mino.didTspin) -- calculate T-spin later
1106 if cPlayer.garbage < 0 then
1107 for e, enemy in pairs(game.pp) do
1108 if e ~= playerNumber then
1109 enemy.garbage = enemy.garbage - cPlayer.garbage
1110 end
1111 end
1112 end
1113 cPlayer.garbage = math.max(0, cPlayer.garbage)
1114
1115 for l = 1, #clearedLines do
1116 for x = 1, board.xSize do
1117 board[clearedLines[l]][x][2] = "c"
1118 end
1119 end
1120 -- make the other network player see the flash
1121 sendInfo("flash_special", false)
1122 player.flashingSpecial = true
1123 renderBoard(board, 0, 0, true)
1124 for i = 1, 0, -0.12 do
1125 term.setPaletteColor(4096, i,i,i)
1126 sleep(0.05)
1127 end
1128 for i = #clearedLines, 1, -1 do
1129 table.remove(board, clearedLines[i])
1130 end
1131 for i = 1, #clearedLines do
1132 table.insert(board, 1, false)
1133 end
1134 board = clearBoard(board)
1135 player.flashingSpecial = false
1136 end
1137
1138 -- take some garbage for yourself
1139
1140 if cPlayer.garbage > 0 then
1141 doleOutGarbage(player, cPlayer, cPlayer.garbage)
1142 end
1143 end
1144 else
1145 -- if you're a client, take in all that board info and just fukkin draw it
1146
1147 inputTimer = os.startTimer(game.inputDelay)
1148
1149 local timeoutTimer = os.startTimer(3)
1150
1151 while game.running do
1152 evt = {os.pullEvent()}
1153 if evt[1] == "new_player_info" then
1154 player = game.p[game.you]
1155 for k,v in pairs(game.p) do
1156 renderBoard(v.board, 0, 0, false)
1157 renderBoard(v.holdBoard, 0, 0, false)
1158 renderBoard(v.queueBoard, 0, 0, false)
1159 drawScore(v, game.pp[k])
1160 drawLevel(v, game.pp[k])
1161 term.setCursorPos(13 + v.xmod, 13 + v.ymod)
1162 term.write("HOLD")
1163 end
1164 os.cancelTimer(timeoutTimer or 0)
1165 timeoutTimer = os.startTimer(3)
1166 elseif evt[1] == "timer" then
1167 if evt[2] == inputTimer then
1168 os.cancelTimer(inputTimer or 0)
1169 inputTimer = os.startTimer(game.inputDelay)
1170 if player then
1171 for k,v in pairs(player.control) do
1172 player.control[k] = v + game.inputDelay
1173 end
1174 end
1175 elseif evt[2] == timeoutTimer then
1176 return
1177 end
1178 end
1179 end
1180
1181 end
1182end
1183
1184-- records all key input
1185local getInput = function()
1186 local evt
1187 while true do
1188 evt = {os.pullEvent()}
1189 if evt[1] == "key" and evt[3] == false then
1190 keysDown[evt[2]] = 1
1191 elseif evt[1] == "key_up" then
1192 keysDown[evt[2]] = nil
1193 end
1194 if (evt[1] == "key" and evt[3] == false) or (evt[1] == "key_up") then
1195 if game.revControl[evt[2]] then
1196 game.p[game.you].control[game.revControl[evt[2]]] = keysDown[evt[2]]
1197 if not game.net.isHost then
1198 sendInfo("send_info", false)
1199 end
1200 end
1201 end
1202 end
1203end
1204
1205local cTime
1206local networking = function()
1207 local evt, side, channel, repchannel, msg, distance
1208 local currentPlayers = 1
1209 while true do
1210 if game.net.useSkynet then
1211 evt, channel, msg = os.pullEvent("skynet_message")
1212 else
1213 evt, side, channel, repchannel, msg, distance = os.pullEvent("modem_message")
1214 end
1215 if channel == game.net.channel and type(msg) == "table" then
1216 if game.net.waitingForGame then
1217 if type(msg.time) == "number" and msg.command == "find_game" then
1218 if msg.instanceID ~= game.instanceID then
1219 if msg.time < cTime then
1220 game.net.isHost = false
1221 game.you = 2
1222 game.net.gameID = msg.gameID
1223 else
1224 game.net.isHost = true
1225 end
1226
1227 transmit({
1228 gameID = game.net.gameID,
1229 time = cTime,
1230 command = "find_game",
1231 instanceID = game.instanceID
1232 })
1233 game.net.waitingForGame = false
1234 os.queueEvent("new_game", game.net.gameID)
1235 return game.net.gameID
1236 end
1237 end
1238 else
1239 if msg.gameID == game.net.gameID then
1240
1241 if game.net.isHost then
1242 if type(msg.control) == "table" then
1243 if type(msg.you) == "number" and msg.you ~= game.you then
1244 game.p[msg.you].control = msg.control
1245 for y = 1, game.p[msg.you].board.ySize do
1246 for x = 1, game.p[msg.you].board.xSize do
1247 ageSpace(game.p[msg.you].board, x, y)
1248 end
1249 end
1250 end
1251 end
1252 else
1253 if type(msg.you) == "number" and msg.you ~= game.you then
1254 if type(msg.p) == "table" then
1255 if msg.pNum then
1256 for k,v in pairs(msg.p) do
1257 if k ~= "control" then
1258 game.p[msg.pNum][k] = v
1259 end
1260 end
1261 else
1262 game.p = msg.p
1263 end
1264 if msg.specialColor then
1265 term.setPaletteColor(tColors.special, table.unpack(msg.specialColor))
1266 end
1267 os.queueEvent("new_player_info", msg.p)
1268 end
1269 if msg.command == "quit_game" then
1270 return
1271 end
1272 if msg.command == "flash_special" then
1273 for i = 1, 0, -0.12 do
1274 term.setPaletteColor(4096, i,i,i)
1275 renderBoard(game.p[msg.you].board, 0, 0, true)
1276 sleep(0.05)
1277 end
1278 end
1279 end
1280 end
1281
1282 end
1283 end
1284 end
1285 end
1286end
1287
1288local cwrite = function(text, y, xdiff, wordPosCheck)
1289 wordPosCheck = wordPosCheck or #text
1290 term.setCursorPos(math.floor(scr_x / 2 - math.floor(0.5 + #text + (xdiff or 0)) / 2), y or (scr_y - 2))
1291 term.write(text)
1292 return (scr_x / 2) - (#text / 2) + wordPosCheck
1293end
1294
1295local setUpModem = function()
1296 if game.net.useSkynet then
1297 if fs.exists(game.net.skynetPath) then
1298 game.net.skynet = dofile(game.net.skynetPath)
1299 term.clear()
1300 cwrite("Connecting to Skynet...", scr_y / 2)
1301 game.net.skynet.open(game.net.channel)
1302 return true
1303 else
1304 term.clear()
1305 cwrite("Downloading Skynet...", scr_y / 2)
1306 local prog = http.get(game.net.skynetURL)
1307 if prog then
1308 local file = fs.open(game.net.skynetPath, "w")
1309 file.write(prog.readAll())
1310 file.close()
1311 skynet = dofile(game.net.skynetPath)
1312 cwrite("Connecting to Skynet...", 1 + scr_y / 2)
1313 skynet.open(game.net.channel)
1314 return true
1315 else
1316 return false, "Could not download Skynet."
1317 end
1318 end
1319 else
1320 -- unload / close skynet
1321 if game.net.skynet then
1322 if game.net.skynet.socket then
1323 game.net.skynet.socket.close()
1324 end
1325 game.net.skynet = nil
1326 end
1327 game.net.modem = peripheral.find("modem")
1328 if (not game.net.modem) and ccemux then
1329 ccemux.attach("top", "wireless_modem")
1330 game.net.modem = peripheral.find("modem")
1331 end
1332 if game.net.modem then
1333 game.net.modem.open(game.net.channel)
1334 return true
1335 else
1336 return false, "No modem was found."
1337 end
1338 end
1339end
1340
1341local pleaseWait = function()
1342 local periods = 1
1343 local maxPeriods = 5
1344 term.setBackgroundColor(tColors.black)
1345 term.setTextColor(tColors.gray)
1346 term.clear()
1347
1348 local tID = os.startTimer(0.2)
1349 local evt, txt
1350 if game.net.useSkynet then
1351 txt = "Waiting for Skynet game"
1352 else
1353 txt = "Waiting for modem game"
1354 end
1355
1356 while true do
1357 cwrite("(Press 'Q' to cancel)", 2)
1358 cwrite(txt, scr_y - 2, maxPeriods)
1359 term.write(("."):rep(periods))
1360 evt = {os.pullEvent()}
1361 if evt[1] == "timer" and evt[2] == tID then
1362 tID = os.startTimer(0.5)
1363 periods = (periods % maxPeriods) + 1
1364 term.clearLine()
1365 elseif evt[1] == "key" and evt[2] == keys.q then
1366 return
1367 end
1368 end
1369end
1370
1371local titleScreen = function() -- mondo placeholder
1372 term.setTextColor(tColors.white)
1373 term.setBackgroundColor(tColors.black)
1374 term.clear()
1375 cwrite("LDris", 3)
1376 cwrite("by LDDestroier", 5)
1377 cwrite("Press 1 to play a game.", 7)
1378 if game.net.useSkynet then
1379 cwrite("Press 2 to play an HTTP game.", 8)
1380 cwrite("Press S to disable Skynet.", 9)
1381 else
1382 cwrite("Press 2 to play a modem game.", 8)
1383 cwrite("Press S to enable Skynet.", 9)
1384 end
1385 cwrite("Press H to see controls.", 10)
1386 cwrite("Press Q to quit.", 11)
1387 local evt
1388 while true do
1389 evt = {os.pullEvent()}
1390 if evt[1] == "key" then
1391 if evt[2] == keys.one then
1392 return "1P"
1393 elseif evt[2] == keys.two then
1394 return "2P"
1395 elseif evt[2] == keys.s then
1396 return "skynet"
1397 elseif evt[2] == keys.h then
1398 return "help"
1399 elseif evt[2] == keys.q then
1400 return "quit"
1401 end
1402 end
1403 end
1404end
1405
1406local screenError = function(...)
1407 local lines = {...}
1408 term.setBackgroundColor(tColors.black)
1409 term.setTextColor(tColors.white)
1410 term.clear()
1411 for i = 1, #lines do
1412 cwrite(lines[i], 2 + i)
1413 end
1414 cwrite("Press any key to continue.", #lines + 4)
1415 sleep(0)
1416 repeat until os.pullEvent("key")
1417end
1418
1419-- a lot of these menus and whatnot are very primitive. I can improve that later
1420local showHelp = function()
1421 term.setBackgroundColor(tColors.black)
1422 term.setTextColor(tColors.white)
1423 term.clear()
1424 cwrite("CONTROLS (defaults):", 2)
1425 term.setCursorPos(1, 4)
1426 print(" Move Piece: LEFT and RIGHT")
1427 print(" Hard Drop: UP")
1428 print(" Fast Drop: DOWN")
1429 print(" Rotate Piece: Z and X")
1430 print(" Hold Piece: L.SHIFT")
1431 print(" Quit: Q")
1432 print("\n Press any key to continue.")
1433 sleep(0)
1434 repeat until os.pullEvent("key")
1435end
1436
1437local main = function()
1438
1439 local rVal -- please wait result
1440 local modeVal -- title screen mode result
1441 local funcs
1442
1443 setUpModem()
1444
1445 while true do
1446
1447 game.you = 1
1448 game.net.isHost = true
1449 finished = false
1450 game.running = true
1451
1452 while true do
1453 modeVal = titleScreen()
1454
1455 if modeVal == "1P" then
1456 game.net.active = false
1457 game.amountOfPlayers = 1
1458 game.gameDelay = 0.05
1459 break
1460 elseif modeVal == "2P" then
1461 if setUpModem() then
1462 if (game.net.skynet and game.net.useSkynet) then
1463 game.gameDelay = 0.1
1464 else
1465 game.gameDelay = 0.05
1466 end
1467 game.net.active = true
1468 game.amountOfPlayers = 2
1469 break
1470 else
1471 screenError("A modem is required for multiplayer.")
1472 finished = true
1473 end
1474 elseif modeVal == "skynet" then
1475 if http.websocket then
1476 game.net.useSkynet = not game.net.useSkynet
1477 setUpModem()
1478 else
1479 screenError(
1480 "Skynet requires websocket support.",
1481 "Use CCEmuX, CC:Tweaked,",
1482 "or CraftOS-PC 2.2 or higher to play."
1483 )
1484 finished = true
1485 end
1486 elseif modeVal == "help" then
1487 showHelp()
1488 elseif modeVal == "quit" then
1489 return false
1490 end
1491 end
1492
1493 if game.net.active then
1494
1495 game.net.waitingForGame = true
1496
1497 cTime = getTime()
1498 transmit({
1499 gameID = game.net.gameID,
1500 time = cTime,
1501 command = "find_game",
1502 instanceID = game.instanceID
1503 })
1504 if game.net.useSkynet then
1505 rVal = parallel.waitForAny( networking, pleaseWait, game.net.skynet.listen )
1506 else
1507 rVal = parallel.waitForAny( networking, pleaseWait )
1508 end
1509 sleep(0.1)
1510
1511 if rVal == 1 then
1512
1513 funcs = {
1514 getInput,
1515 }
1516
1517 if game.net.active then
1518 table.insert(funcs, networking)
1519 if game.net.useSkynet and game.net.skynet then
1520 table.insert(funcs, game.net.skynet.listen)
1521 end
1522 end
1523
1524 initializePlayers(game.amountOfPlayers or 1)
1525
1526 if game.net.isHost then
1527 for k,v in pairs(game.p) do
1528 funcs[#funcs + 1] = function()
1529 return startGame(k)
1530 end
1531 end
1532 else
1533 funcs[#funcs + 1] = startGame
1534 end
1535
1536 else
1537 finished = true
1538 end
1539
1540 else
1541
1542 funcs = {getInput}
1543 initializePlayers(game.amountOfPlayers or 1)
1544
1545 for k,v in pairs(game.p) do
1546 funcs[#funcs + 1] = function()
1547 return startGame(k)
1548 end
1549 end
1550
1551 end
1552
1553 if not finished then
1554
1555 term.setBackgroundColor(tColors.gray)
1556 term.clear()
1557
1558 parallel.waitForAny(table.unpack(funcs))
1559
1560 end
1561
1562 end
1563
1564end
1565
1566main()
1567
1568-- reset palette to back from whence it came
1569for k,v in pairs(colors) do
1570 if type(v) == "number" then
1571 term.setPaletteColor(v, term.nativePaletteColor(v))
1572 end
1573end
1574
1575term.setBackgroundColor(colors.black)
1576term.setTextColor(colors.white)
1577
1578if game.net.skynet then
1579 if game.net.skynet.socket then
1580 game.net.skynet.socket.close()
1581 end
1582end
1583
1584for i = 1, 5 do
1585 term.scroll(1)
1586 if i == 3 then
1587 term.setCursorPos(1, scr_y)
1588 term.write("Thanks for playing!")
1589 end
1590 sleep(0.05)
1591end
1592term.setCursorPos(1, scr_y)