From 028f229a0f2801733f3d97cf4b935c9311f3c1ae Mon Sep 17 00:00:00 2001 From: LDDestroier Date: Thu, 21 Nov 2019 01:56:52 -0500 Subject: [PATCH] Added multiplayer, title menu, skynet... Very yes --- ldris.lua | 1105 ++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 760 insertions(+), 345 deletions(-) diff --git a/ldris.lua b/ldris.lua index 2ab9889..8d03991 100644 --- a/ldris.lua +++ b/ldris.lua @@ -14,13 +14,15 @@ -- and ghost pieces. -- -- TO-DO: --- + Add multiplayer --- + Add random color pulsation (for effect!) +-- + Add slight random color pulsation (for effect!) local scr_x, scr_y = term.getSize() +local keysDown = {} local game = { p = {}, -- stores player information + pp = {}, -- stores other player information that doesn't need to be sent during netplay you = 1, -- current player slot + nou = 2, -- current enemy slot amountOfPlayers = 2, -- amount of players for the current game running = true, -- if set to false, will quit the game moveHoldDelay = 0.2, -- amount of time to hold left or right for it to keep moving that way @@ -32,20 +34,45 @@ local game = { TGMlock = true, -- replicate the piece locking from Tetris: The Grand Master scrubMode = false, -- gives you nothing but I-pieces }, - control = { - moveLeft = keys.left, - moveRight = keys.right, - moveDown = keys.down, - rotateLeft = keys.z, - rotateRight = keys.x, - fastDrop = keys.up, - hold = keys.leftShift, - quit = keys.q + control = { -- client's control scheme + moveLeft = keys.left, -- shift left + moveRight = keys.right, -- shift right + moveDown = keys.down, -- shift downwards + rotateLeft = keys.z, -- rotate counter-clockwise + rotateRight = keys.x, -- rotate clockwise + fastDrop = keys.up, -- instantly drop and place piece + hold = keys.leftShift, -- slot piece into hold buffer + quit = keys.q -- fuck off + }, + revControl = {}, -- a mirror of "control", but with the keys and values inverted + net = { -- all network-related values + isHost = true, -- the host holds all the cards + gameID = math.random(1, 2^31-1), -- per-game ID to prevent interference from other games + channel = 1294, -- modem or skynet channel + active = true, -- whether or not you're using modems at all + waitingForGame = true, -- if you're waiting for another player to start the game + modem = peripheral.find("modem"), -- modem transmit object + useSkynet = false, -- if true, uses Skynet instead of modems + skynet = nil, -- skynet transmit object + skynetURL = "https://github.com/LDDestroier/CC/raw/master/API/skynet.lua", -- exactly what it looks like + skynetPath = "/skynet.lua" -- location for Skynet API }, timers = {}, timerNo = 1 } +for k,v in pairs(game.control) do + game.revControl[v] = k +end + +local getTime = function() + if os.epoch then + return os.epoch("utc") + else + return 24 * os.day() + os.time() + end +end + game.startTimer = function(duration) game.timers[game.timerNo] = duration game.timerNo = game.timerNo + 1 @@ -86,7 +113,7 @@ term.setPaletteColor(tColors.white, 0xf0f0f0) -- initializes and fixes up a board -- boards are 2D objects that can display perfectly square graphics -local clearBoard = function(board, xpos, ypos, newXsize, newYsize, newBGcolor, topCull) +local clearBoard = function(board, xpos, ypos, newXsize, newYsize, newBGcolor, topCull, clearContent) board = board or {} board.x = board.x or xpos or 1 board.y = board.y or ypos or 1 @@ -104,7 +131,11 @@ local clearBoard = function(board, xpos, ypos, newXsize, newYsize, newBGcolor, t -- number; the countdown until the space is made non-solid (inactive if 0) -- number; the countdown until the space is colored to board.BGcolor (inactive if 0) -- } - board[y][x] = board[y][x] or {false, board.BGcolor, 0, 0} + if clearContent then + board[y][x] = {false, board.BGcolor, 0, 0} + else + board[y][x] = board[y][x] or {false, board.BGcolor, 0, 0} + end end end return board @@ -296,6 +327,34 @@ local ageSpace = function(board, _x, _y) end end +local transmit = function(msg) + if game.net.useSkynet then + game.net.skynet.send(game.net.channel, msg) + else + game.net.modem.transmit(game.net.channel, game.net.channel, msg) + end +end + +local sendInfo = function(command, doSendTime, playerNumber) + if game.net.isHost then + transmit({ + command = command, + gameID = game.net.gameID, + time = doSendTime and getTime(), + p = playerNumber and game.p[playerNumber] or game.p, + pNum = playerNumber, + specialColor = {term.getPaletteColor(tColors.special)} + }) + else + transmit({ + command = command, + gameID = game.net.gameID, + time = doSendTime and getTime(), + control = game.p[game.you].control + }) + end +end + -- generates a "mino" object, which can be drawn and manipulated on a board local makeNewMino = function(minoType, board, x, y, replaceColor) local mino = tableCopy(minos[minoType]) @@ -469,11 +528,14 @@ local initializePlayers = function(amountOfPlayers) return { xmod = xmod, ymod = ymod, - keysDown = {}, + control = {}, board = clearBoard({}, 2 + xmod, 2 + ymod, 10, nil, "f"), holdBoard = clearBoard({}, 13 + xmod, 14 + ymod, 4, 3, "f", 0), queueBoard = clearBoard({}, 13 + xmod, 2 + ymod, 4, 14, "f", 0), - randomPieces = {}, -- list of all minos for pseudo-random selection + randomPieces = {}, -- list of all minos for pseudo-random selection + flashingSpecial = false, -- if true, then this player is flashing the special color + }, { + frozen = false, -- if true, literally cannot move or act hold = 0, -- current piece being held canHold = true, -- whether or not player can hold (can't hold twice in a row) queue = {}, -- current queue of minos to use @@ -487,14 +549,17 @@ local initializePlayers = function(amountOfPlayers) } end + game.p = {} + game.pp = {} + for i = 1, (amountOfPlayers or 1) do - game.p[i] = newPlayer((i - 1) * 16, 0) + game.p[i], game.pp[i] = newPlayer((i - 1) * 16, 0) end -- generates the initial queue of minos per player - for p = 1, #game.p do + for p = 1, #game.pp do for i = 1, #minos do - game.p[p].queue[i] = pseudoRandom(game.p[p].randomPieces) + game.pp[p].queue[i] = pseudoRandom(game.p[p].randomPieces) end end end @@ -552,27 +617,26 @@ local checkIfLineCleared = function(board, y) end -- draws the score of a player, and clears the space where the combo text is drawn -local drawScore = function(player) - if not player.drawCombo then +local drawScore = function(player, cPlayer) + if not cPlayer.drawCombo then term.setCursorPos(2 + player.xmod, 18 + player.ymod) term.setTextColor(tColors.white) term.write((" "):rep(14)) term.setCursorPos(2 + player.xmod, 18 + player.ymod) - term.write("Lines: " .. player.lines) - term.write(" " .. player.garbage) + term.write("Lines: " .. cPlayer.lines) term.setCursorPos(2 + player.xmod, 19 + player.ymod) term.write((" "):rep(14)) end end -local drawLevel = function(player) +local drawLevel = function(player, cPlayer) term.setCursorPos(13 + player.xmod, 17 + player.ymod) - term.write("Lv" .. player.level .. " ") + term.write("Lv" .. cPlayer.level .. " ") end -- draws the player's simultaneous line clear after clearing one or more lines -- also tells the player's combo, which is nice -local drawComboMessage = function(player, lines, didTspin) +local drawComboMessage = function(cPlayer, lines, didTspin) local msgs = { "SINGLE", "DOUBLE", @@ -589,7 +653,7 @@ local drawComboMessage = function(player, lines, didTspin) if didTspin then term.write("T-SPIN ") else - if lines == player.lastLinesCleared then + if lines == cPlayer.lastLinesCleared then if lines == 3 then term.write("OH BABY A ") else @@ -598,30 +662,30 @@ local drawComboMessage = function(player, lines, didTspin) end end term.write(msgs[lines]) - if player.combo >= 2 then + if cPlayer.combo >= 2 then term.setCursorPos(2, 19) term.setTextColor(tColors.white) term.write((" "):rep(16)) term.setCursorPos(2, 19) - if lines == 4 and player.combo == 3 then + if lines == 4 and cPlayer.combo == 3 then term.write("HOLY SHIT!") - elseif lines == 4 and player.combo > 3 then + elseif lines == 4 and cPlayer.combo > 3 then term.write("ALRIGHT JACKASS") else - term.write(player.combo .. "x COMBO") + term.write(cPlayer.combo .. "x COMBO") end end end -- god damn it you've fucked up -local gameOver = function(player) +local gameOver = function(player, cPlayer) local mino - if player.lines == 0 then + if cPlayer.lines == 0 then mino = makeNewMino("eatmyass", player.board, 12, 3 + game.boardOverflow) - elseif player.lines <= 5 then + elseif cPlayer.lines <= 5 then mino = makeNewMino("yousuck", player.board, 12, 3 + game.boardOverflow) - elseif player.lines == 69 or player.lines == 690 then + elseif cPlayer.lines == 69 or cPlayer.lines == 690 then mino = makeNewMino("nice", player.board, 12, 3 + game.boardOverflow) else mino = makeNewMino("gameover", player.board, 12, 3 + game.boardOverflow) @@ -632,6 +696,7 @@ local gameOver = function(player) mino.x = mino.x - 1 end mino.draw() + sendInfo("send_info", false) renderBoard(player.board, 0, 0, true) for i = 1, 20 do color = color + 0.01 @@ -689,7 +754,7 @@ local doleOutGarbage = function(player, amount) gx = math.random(1, board.xSize) end end - player.garbage = 0 + cPlayer.garbage = 0 end -- initiates a game as a specific player (takes a number) @@ -697,371 +762,717 @@ local startGame = function(playerNumber) local mino, ghostMino local dropTimer, inputTimer, lockTimer, tickTimer, comboTimer - local evt, board, player + local evt, board, player, cPlayer, control local finished -- whether or not a mino is done being placed - local keysDown -- list of all pressed keys per for player playerNumber local clearedLines = {} -- used when calculating cleared lines - player = game.p[playerNumber] - board = player.board + if game.net.isHost then - local draw = function(isSolid) - term.setPaletteColor(4096, mino.ghostColor) - ghostMino.x = mino.x - ghostMino.y = mino.y - ghostMino.move(0, board.ySize, true) - ghostMino.draw(false) - mino.draw(isSolid) - renderBoard(board, 0, 0, true) - end + player = game.p[playerNumber] + cPlayer = game.pp[playerNumber] + board = player.board + control = player.control - local currentMinoType - local takeFromQueue = true - - local interpretInput = function() - finished = false - game.cancelTimer(inputTimer) - inputTimer = game.startTimer(game.inputDelay) - - if keysDown[game.control.quit] == 1 then - finished = true - game.running = false - return + local draw = function(isSolid) + if not ((game.p[game.you] or {}).flashingSpecial or (game.p[game.nou] or {}).flashingSpecial) then + term.setPaletteColor(4096, mino.ghostColor) + end + ghostMino.x = mino.x + ghostMino.y = mino.y + ghostMino.move(0, board.ySize, true) + ghostMino.draw(false) + mino.draw(isSolid, ageSpaces) + sendInfo("send_info", false, playerNumber) + renderBoard(board, 0, 0, true) end - if game.paused then - if keysDown[game.control.pause] == 1 then - game.paused = false - end - else - if keysDown[game.control.pause] == 1 then - game.paused = true - end - if keysDown[game.control.moveLeft] == 1 or (keysDown[game.control.moveLeft] or 0) > 1 + game.moveHoldDelay then - if mino.move(-1, 0) then - game.cancelTimer(lockTimer or 0) - mino.waitingForLock = false - draw() - end - end - if keysDown[game.control.moveRight] == 1 or (keysDown[game.control.moveRight] or 0) >= 1 + game.moveHoldDelay then - if mino.move(1, 0) then - game.cancelTimer(lockTimer or 0) - mino.waitingForLock = false - draw() - end - end - if keysDown[game.control.moveDown] then - game.cancelTimer(lockTimer or 0) - mino.waitingForLock = false - if mino.move(0, 1) then - draw() - else - if mino.waitingForLock then - game.alterTimer(lockTimer, -0.1) - else - mino.lockBreaks = mino.lockBreaks - 1 - lockTimer = game.startTimer(math.max(0.2 / player.fallSteps, 0.5)) - mino.waitingForLock = true - end - end - end - if keysDown[game.control.rotateLeft] == 1 then - if mino.rotate(-1) then - ghostMino.y = mino.y - ghostMino.rotate(-1) - game.cancelTimer(lockTimer or 0) - mino.waitingForLock = false - draw() - end - end - if keysDown[game.control.rotateRight] == 1 then - if mino.rotate(1) then - ghostMino.y = mino.y - ghostMino.rotate(1) - game.cancelTimer(lockTimer or 0) - mino.waitingForLock = false - draw() - end - end - if keysDown[game.control.hold] == 1 then - if player.canHold then - if player.hold == 0 then - takeFromQueue = true - else - takeFromQueue = false - end - player.hold, currentMinoType = currentMinoType, player.hold - player.canHold = false - makeNewMino( - player.hold, - player.holdBoard, - #minos[player.hold].shape[1] == 2 and 1 or 0, - 0 - ).draw() - renderBoard(player.holdBoard, 0, 0, true) - finished = true - end - end - if keysDown[game.control.fastDrop] == 1 then - mino.move(0, board.ySize, true) - draw(true) - player.canHold = true + local currentMinoType + local takeFromQueue = true + + local interpretInput = function() + finished = false + game.cancelTimer(inputTimer) + inputTimer = game.startTimer(game.inputDelay) + + if control.quit == 1 then finished = true - end - end - for k,v in pairs(keysDown) do - keysDown[k] = v + 0.05 - end - end - - term.setCursorPos(13 + player.xmod, 13 + player.ymod) - term.write("HOLD") - renderBoard(player.holdBoard, 0, 0, true) - - while game.running do - - player.level = math.ceil((1 + player.lines) / 10) - player.fallSteps = 0.075 * (1.33 ^ player.level) - - drawLevel(player) - - if takeFromQueue then - currentMinoType = player.queue[1] - end - - mino = makeNewMino( - currentMinoType, - board, - math.floor(board.xSize / 2) - 2, - game.boardOverflow - ) - - ghostMino = makeNewMino( - currentMinoType, - board, - math.floor(board.xSize / 2) - 2, - game.boardOverflow, - "c" - ) - - if takeFromQueue then - table.remove(player.queue, 1) - table.insert(player.queue, pseudoRandom(player.randomPieces)) - end - - -- draw queue - for i = 1, math.min(#player.queue, 4) do - local m = makeNewMino( - player.queue[i], - player.queueBoard, - #minos[player.queue[i]].shape[1] == 2 and 1 or 0, - 1 + (3 * (i - 1)) + (i > 1 and 2 or 0) - ) - m.draw() - end - renderBoard(player.queueBoard, 0, 0, true) - - -- draw held piece - if player.hold ~= 0 then - local m = makeNewMino( - player.hold, - player.holdBoard, - #minos[player.hold].shape[1] == 2 and 1 or 0, - 0 - ) - end - - takeFromQueue = true - - drawScore(player) - - -- check to see if you've lost - if mino.checkCollision() then - gameOver(player) - return - end - - draw() - - dropTimer = game.startTimer(0) - inputTimer = game.startTimer(game.inputDelay) - game.cancelTimer(lockTimer or 0) - - tickTimer = os.startTimer(0.05) - - -- drop a piece - while game.running do - - evt = {os.pullEvent()} - - keysDown = game.p[playerNumber].keysDown - - -- tick down internal game timer system - if evt[1] == "timer" then - if evt[2] == tickTimer then - --local delKeys = {} - for k,v in pairs(game.timers) do - game.timers[k] = v - 0.05 - if v <= 0 then - os.queueEvent("gameTimer", k) - game.timers[k] = nil - end - end - tickTimer = os.startTimer(0.05) - elseif evt[2] == comboTimer then - player.drawCombo = false - drawScore(player) - end + game.running = false + sendInfo("quit_game", false) + return end - if player.paused then - if evt[1] == "gameTimer" then - if keysDown[game.control.pause] == 1 then - game.paused = false - end + if game.paused then + if control.pause == 1 then + game.paused = false end else - if evt[1] == "key" and evt[3] == false then - - interpretInput() - if finished then - break + if control.pause == 1 then + game.paused = true + end + if not cPlayer.frozen then + if control.moveLeft == 1 or (control.moveLeft or 0) > 1 + game.moveHoldDelay then + if mino.move(-1, 0) then + game.cancelTimer(lockTimer or 0) + mino.waitingForLock = false + draw() + end end + if control.moveRight == 1 or (control.moveRight or 0) >= 1 + game.moveHoldDelay then + if mino.move(1, 0) then + game.cancelTimer(lockTimer or 0) + mino.waitingForLock = false + draw() + end + end + if control.moveDown then + game.cancelTimer(lockTimer or 0) + mino.waitingForLock = false + if mino.move(0, 1) then + draw() + else + if mino.waitingForLock then + game.alterTimer(lockTimer, -0.1) + else + mino.lockBreaks = mino.lockBreaks - 1 + lockTimer = game.startTimer(math.max(0.2 / cPlayer.fallSteps, 0.5)) + mino.waitingForLock = true + end + end + end + if control.rotateLeft == 1 then + if mino.rotate(-1) then + ghostMino.y = mino.y + ghostMino.rotate(-1) + game.cancelTimer(lockTimer or 0) + mino.waitingForLock = false + draw() + end + end + if control.rotateRight == 1 then + if mino.rotate(1) then + ghostMino.y = mino.y + ghostMino.rotate(1) + game.cancelTimer(lockTimer or 0) + mino.waitingForLock = false + draw() + end + end + if control.hold == 1 then + if cPlayer.canHold then + if cPlayer.hold == 0 then + takeFromQueue = true + else + takeFromQueue = false + end + cPlayer.hold, currentMinoType = currentMinoType, cPlayer.hold + cPlayer.canHold = false + player.holdBoard = clearBoard(player.holdBoard, nil, nil, nil, nil, nil, nil, true) + makeNewMino( + cPlayer.hold, + player.holdBoard, + #minos[cPlayer.hold].shape[1] == 2 and 1 or 0, + 0 + ).draw() + sendInfo("send_info", false, playerNumber) + renderBoard(player.holdBoard, 0, 0, false) + finished = true + end + end + if control.fastDrop == 1 then + mino.move(0, board.ySize, true) + draw(true) + cPlayer.canHold = true + finished = true + end + end + end + for k,v in pairs(player.control) do + player.control[k] = v + 0.05 + end + end - elseif evt[1] == "gameTimer" then + renderBoard(player.holdBoard, 0, 0, true) - if evt[2] == inputTimer then + while game.running do + + cPlayer.level = math.ceil((1 + cPlayer.lines) / 10) + cPlayer.fallSteps = 0.075 * (1.33 ^ cPlayer.level) + + if takeFromQueue then + currentMinoType = cPlayer.queue[1] + end + + mino = makeNewMino( + currentMinoType, + board, + math.floor(board.xSize / 2) - 2, + game.boardOverflow + ) + + ghostMino = makeNewMino( + currentMinoType, + board, + math.floor(board.xSize / 2) - 2, + game.boardOverflow, + "c" + ) + + if takeFromQueue then + table.remove(cPlayer.queue, 1) + table.insert(cPlayer.queue, pseudoRandom(player.randomPieces)) + end + + -- draw queue + player.queueBoard = clearBoard(player.queueBoard, nil, nil, nil, nil, nil, nil, true) + for i = 1, math.min(#cPlayer.queue, 4) do + local m = makeNewMino( + cPlayer.queue[i], + player.queueBoard, + #minos[cPlayer.queue[i]].shape[1] == 2 and 1 or 0, + 1 + (3 * (i - 1)) + (i > 1 and 2 or 0) + ) + m.draw() + end + sendInfo("send_info", false, playerNumber) + renderBoard(player.queueBoard, 0, 0, false) + + -- draw held piece + if cPlayer.hold ~= 0 then + local m = makeNewMino( + cPlayer.hold, + player.holdBoard, + #minos[cPlayer.hold].shape[1] == 2 and 1 or 0, + 0 + ) + end + + takeFromQueue = true + + drawScore(player, cPlayer) + drawLevel(player, cPlayer) + + term.setCursorPos(13 + player.xmod, 13 + player.ymod) + term.write("HOLD") + + -- check to see if you've topped out + if mino.checkCollision() then + for k,v in pairs(game.pp) do + game.pp[k].frozen = true + end + sendInfo("game_over", false) + gameOver(player, cPlayer) + sendInfo("quit_game", false) + return + end + + draw() + + dropTimer = game.startTimer(0) + inputTimer = game.startTimer(game.inputDelay) + game.cancelTimer(lockTimer or 0) + + tickTimer = os.startTimer(0.05) + + -- drop a piece + while game.running do + + evt = {os.pullEvent()} + + control = game.p[playerNumber].control + + -- tick down internal game timer system + if evt[1] == "timer" then + if evt[2] == tickTimer then + --local delKeys = {} + for k,v in pairs(game.timers) do + game.timers[k] = v - 0.05 + if v <= 0 then + os.queueEvent("gameTimer", k) + game.timers[k] = nil + end + end + tickTimer = os.startTimer(0.05) + elseif evt[2] == comboTimer then + cPlayer.drawCombo = false + drawScore(player, cPlayer) + end + end + + if player.paused then + if evt[1] == "gameTimer" then + if control.pause == 1 then + game.paused = false + end + end + else + if evt[1] == "key" and evt[3] == false then interpretInput() if finished then break end - elseif evt[2] == dropTimer then - dropTimer = game.startTimer(0) - if not game.paused then - if mino.checkCollision(0, 1) then - if mino.lockBreaks == 0 then - draw(true) - player.canHold = true - break - elseif not mino.waitingForLock then - mino.lockBreaks = mino.lockBreaks - 1 - lockTimer = game.startTimer(math.max(0.2 / player.fallSteps, 0.25)) - mino.waitingForLock = true + elseif evt[1] == "gameTimer" then + + if evt[2] == inputTimer then + + interpretInput() + if finished then + break + end + + elseif evt[2] == dropTimer then + dropTimer = game.startTimer(0) + if not game.paused then + if not cPlayer.frozen then + if mino.checkCollision(0, 1) then + if mino.lockBreaks == 0 then + draw(true) + cPlayer.canHold = true + break + elseif not mino.waitingForLock then + mino.lockBreaks = mino.lockBreaks - 1 + lockTimer = game.startTimer(math.max(0.2 / cPlayer.fallSteps, 0.25)) + mino.waitingForLock = true + end + else + mino.move(0, cPlayer.fallSteps, true) + draw() + end end - else - mino.move(0, player.fallSteps, true) - draw() + end + elseif evt[2] == lockTimer then + if not game.paused then + cPlayer.canHold = true + draw(true) + break end end - elseif evt[2] == lockTimer then - if not game.paused then - player.canHold = true - draw(true) - break + end + end + end + + clearedLines = {} + for y = 1, board.ySize do + if checkIfLineCleared(board, y) then + table.insert(clearedLines, y) + end + end + if #clearedLines == 0 then + if cPlayer.canHold then + cPlayer.combo = 0 + end + else + cPlayer.combo = cPlayer.combo + 1 + cPlayer.lines = cPlayer.lines + #clearedLines + cPlayer.drawCombo = true + os.cancelTimer(comboTimer or 0) + comboTimer = os.startTimer(2) + if cPlayer.lastLinesCleared == #clearedLines and #clearedLines >= 3 then + player.backToBack = player.backToBack + 1 + else + player.backToBack = 0 + end + + drawComboMessage(cPlayer, #clearedLines) + + cPlayer.lastLinesCleared = #clearedLines + + -- give the other fucktard(s) some garbage + cPlayer.garbage = cPlayer.garbage - calculateGarbage(#clearedLines, cPlayer.combo, player.backToBack, mino.didTspin) -- calculate T-spin later + if cPlayer.garbage < 0 then + for e, enemy in pairs(game.pp) do + if e ~= playerNumber then + enemy.garbage = enemy.garbage - cPlayer.garbage end end end - end - end + cPlayer.garbage = math.max(0, cPlayer.garbage) - clearedLines = {} - for y = 1, board.ySize do - if checkIfLineCleared(board, y) then - table.insert(clearedLines, y) - end - end - if #clearedLines == 0 then - if player.canHold then - player.combo = 0 - end - else - player.combo = player.combo + 1 - player.lines = player.lines + #clearedLines - player.drawCombo = true - os.cancelTimer(comboTimer or 0) - comboTimer = os.startTimer(2) - if player.lastLinesCleared == #clearedLines and #clearedLines >= 3 then - player.backToBack = player.backToBack + 1 - else - player.backToBack = 0 - end - - drawComboMessage(player, #clearedLines) - - player.lastLinesCleared = #clearedLines - - -- give the other fucktard(s) some garbage - player.garbage = player.garbage - calculateGarbage(#clearedLines, player.combo, player.backToBack, mino.didTspin) -- calculate T-spin later - if player.garbage < 0 then - for e, enemy in pairs(game.p) do - if e ~= playerNumber then - enemy.garbage = enemy.garbage - player.garbage - end - end - end - player.garbage = math.max(0, player.garbage) - - for i = 1, 0, -0.12 do - term.setPaletteColor(4096, i,i,i) for l = 1, #clearedLines do for x = 1, board.xSize do board[clearedLines[l]][x][2] = "c" end end + -- make the other network player see the flash + sendInfo("flash_special", false) + player.flashingSpecial = true renderBoard(board, 0, 0, true) - sleep(0.05) + for i = 1, 0, -0.12 do + term.setPaletteColor(4096, i,i,i) + sleep(0.05) + end + for i = #clearedLines, 1, -1 do + table.remove(board, clearedLines[i]) + end + for i = 1, #clearedLines do + table.insert(board, 1, false) + end + board = clearBoard(board) + player.flashingSpecial = false end - for i = #clearedLines, 1, -1 do - table.remove(board, clearedLines[i]) + + -- take some garbage for yourself + + if cPlayer.garbage > 0 then + doleOutGarbage(player, cPlayer.garbage) end - for i = 1, #clearedLines do - table.insert(board, 1, false) + end + else + -- if you're a client, take in all that board info and just fukkin draw it + + inputTimer = os.startTimer(game.inputDelay) + local cVal = 0 + + while game.running do + evt = {os.pullEvent()} + if evt[1] == "new_player_info" then + player = game.p[game.you] + for k,v in pairs(game.p) do + renderBoard(v.board, 0, 0, false) + renderBoard(v.holdBoard, 0, 0, false) + renderBoard(v.queueBoard, 0, 0, false) + drawScore(v, game.pp[k]) + drawLevel(v, game.pp[k]) + term.setCursorPos(13 + v.xmod, 13 + v.ymod) + term.write("HOLD") + end + elseif (evt[1] == "timer" and evt[2] == inputTimer) then + os.cancelTimer(inputTimer or 0) + inputTimer = os.startTimer(game.inputDelay) + -- man am I clever + cVal = math.max(0, cVal - 1) + for k,v in pairs(player.control) do + sendInfo("send_info", false) + cVal = 2 + break + end + if cVal == 1 then + sendInfo("send_info", false) + end + if player then + for k,v in pairs(player.control) do + player.control[k] = v + 0.05 + isControlling = true + end + end end - board = clearBoard(board) end - -- take some garbage for yourself - - if player.garbage > 0 then - doleOutGarbage(player, player.garbage) - end end end -- records all key input local getInput = function() local evt - local keysDown while true do evt = {os.pullEvent()} - keysDown = game.p[game.you].keysDown if evt[1] == "key" and evt[3] == false then keysDown[evt[2]] = 1 elseif evt[1] == "key_up" then keysDown[evt[2]] = nil end - end -end - -initializePlayers(game.amountOfPlayers or 1) - -local main = function() - local funcs = {} - for k,v in pairs(game.p) do - funcs[#funcs + 1] = function() - return startGame(k) + if (evt[1] == "key" and evt[3] == false) or (evt[1] == "key_up") then + if game.revControl[evt[2]] then + game.p[game.you].control[game.revControl[evt[2]]] = keysDown[evt[2]] + end end end - parallel.waitForAny(table.unpack(funcs)) end -term.setBackgroundColor(tColors.gray) -term.clear() +local cTime +local networking = function() + local evt, side, channel, repchannel, msg, distance + while true do + if game.net.useSkynet then + evt, channel, msg = os.pullEvent("skynet_message") + else + evt, side, channel, repchannel, msg, distance = os.pullEvent("modem_message") + end + if channel == game.net.channel and type(msg) == "table" then + if game.net.waitingForGame then + if type(msg.time) == "number" and msg.command == "find_game" then + if msg.time < cTime then + game.net.isHost = false + game.you = 2 + game.nou = 1 + game.net.gameID = msg.gameID + else + game.net.isHost = true + end -parallel.waitForAny(main, getInput) + transmit({ + gameID = game.net.gameID, + time = cTime, + command = "find_game" + }) + game.net.waitingForGame = false + os.queueEvent("new_game", game.net.gameID) + return game.net.gameID + end + else + if msg.gameID == game.net.gameID then + + if game.net.isHost then + if type(msg.control) == "table" then + game.p[game.nou].control = msg.control + sendInfo("send_info", false, game.nou) + end + else + if type(msg.p) == "table" then + if msg.pNum then + for k,v in pairs(msg.p) do + if k ~= "control" then + game.p[msg.pNum][k] = v + end + end + else + game.p = msg.p + end + if msg.specialColor then + term.setPaletteColor(tColors.special, table.unpack(msg.specialColor)) + end + os.queueEvent("new_player_info", msg.p) + end + if msg.command == "quit_game" then + return + end + if msg.command == "flash_special" then + for i = 1, 0, -0.12 do + term.setPaletteColor(4096, i,i,i) + renderBoard(game.p[game.nou].board, 0, 0, true) + sleep(0.05) + end + end + end + + end + end + end + end +end + +local cwrite = function(text, y, xdiff, wordPosCheck) + wordPosCheck = wordPosCheck or #text + term.setCursorPos(math.floor(scr_x / 2 - math.floor(0.5 + #text + (xdiff or 0)) / 2), y or (scr_y - 2)) + term.write(text) + return (scr_x / 2) - (#text / 2) + wordPosCheck +end + +local setUpModem = function() + if game.net.useSkynet then + if fs.exists(game.net.skynetPath) then + game.net.skynet = dofile(game.net.skynetPath) + term.clear() + cwrite("Connecting to Skynet...", scr_y / 2) + game.net.skynet.open(game.net.channel) + else + term.clear() + cwrite("Downloading Skynet...", scr_y / 2) + local prog = http.get(game.net.skynetURL) + if prog then + local file = fs.open(game.net.skynetPath, "w") + file.write(prog.readAll()) + file.close() + skynet = dofile(game.net.skynetPath) + cwrite("Connecting to Skynet...", 1 + scr_y / 2) + skynet.open(game.net.channel) + else + error("Could not download Skynet.") + end + end + else + -- unload / close skynet + if game.net.skynet then + if game.net.skynet.socket then + game.net.skynet.socket.close() + end + game.net.skynet = nil + end + game.net.modem = peripheral.find("modem") + if (not game.net.modem) and ccemux then + ccemux.attach("top", "wireless_modem") + game.net.modem = peripheral.find("modem") + end + if game.net.modem then + game.net.modem.open(game.net.channel) + else + error("You should attach a modem.") + end + end +end + +local pleaseWait = function() + local periods = 1 + local maxPeriods = 5 + term.setBackgroundColor(tColors.black) + term.setTextColor(tColors.gray) + term.clear() + + local tID = os.startTimer(0.2) + local evt, txt + if game.net.useSkynet then + txt = "Waiting for Skynet game" + else + txt = "Waiting for modem game" + end + + while true do + cwrite("(Press 'Q' to cancel)", 2) + cwrite(txt, scr_y - 2, maxPeriods) + term.write(("."):rep(periods)) + evt = {os.pullEvent()} + if evt[1] == "timer" and evt[2] == tID then + tID = os.startTimer(0.5) + periods = (periods % maxPeriods) + 1 + term.clearLine() + elseif evt[1] == "key" and evt[2] == keys.q then + return + end + end +end + +local titleScreen = function() -- mondo placeholder + term.setTextColor(tColors.white) + term.setBackgroundColor(tColors.black) + term.clear() + cwrite("LDris", 3) + cwrite("by LDDestroier", 5) + cwrite("Press 1 to play a game.", 7) + if game.net.useSkynet then + cwrite("Press 2 to play an HTTP game.", 8) + cwrite("Press S to disable Skynet.", 9) + else + cwrite("Press 2 to play a modem game.", 8) + cwrite("Press S to enable Skynet.", 9) + end + cwrite("Press Q to quit.", 10) + cwrite(tostring(math.random(10000, 99999)), 12) + local evt + while true do + evt = {os.pullEvent()} + if evt[1] == "key" then + if evt[2] == keys.one then + return "1P" + elseif evt[2] == keys.two then + return "2P" + elseif evt[2] == keys.s then + return "skynet" + elseif evt[2] == keys.q then + return "quit" + end + end + end +end + +local main = function() + + local rVal -- please wait result + local modeVal -- title screen mode result + local funcs + + setUpModem() + + while true do + + game.you = 1 + game.nou = 2 + game.net.isHost = true + finished = false + game.running = true + + while true do + modeVal = titleScreen() + + if modeVal == "1P" then + game.net.active = false + game.amountOfPlayers = 1 + break + elseif modeVal == "2P" then + game.net.active = true + game.amountOfPlayers = 2 + break + elseif modeVal == "skynet" then + game.net.useSkynet = not game.net.useSkynet + setUpModem() + elseif modeVal == "quit" then + return false + end + end + + if game.net.active then + + game.net.waitingForGame = true + + cTime = getTime() + transmit({ + gameID = game.net.gameID, + time = cTime, + command = "find_game" + }) + if game.net.useSkynet then + rVal = parallel.waitForAny( networking, pleaseWait, game.net.skynet.listen ) + else + rVal = parallel.waitForAny( networking, pleaseWait ) + end + sleep(0.1) + + if rVal == 1 then + + funcs = { + getInput, + } + + if game.net.active then + table.insert(funcs, networking) + if game.net.useSkynet and game.net.skynet then + table.insert(funcs, game.net.skynet.listen) + end + end + + initializePlayers(game.amountOfPlayers or 1) + + if game.net.isHost then + for k,v in pairs(game.p) do + funcs[#funcs + 1] = function() + return startGame(k) + end + end + else + funcs[#funcs + 1] = startGame + end + + else + finished = true + end + + else + + funcs = {getInput} + initializePlayers(game.amountOfPlayers or 1) + + for k,v in pairs(game.p) do + funcs[#funcs + 1] = function() + return startGame(k) + end + end + + end + + if not finished then + + term.setBackgroundColor(tColors.gray) + term.clear() + + parallel.waitForAny(table.unpack(funcs)) + + end + + end + +end + +main() -- reset palette to back from whence it came for k,v in pairs(colors) do @@ -1070,11 +1481,15 @@ for k,v in pairs(colors) do end end -print(colors.white) - term.setBackgroundColor(colors.black) term.setTextColor(colors.white) +if game.net.skynet then + if game.net.skynet.socket then + game.net.skynet.socket.close() + end +end + for i = 1, 5 do term.scroll(1) if i == 3 then