mirror of
https://github.com/LDDestroier/CC/
synced 2024-11-12 13:00:01 +00:00
809c23c2fe
This will allow LDris to technically handle more than two players, And yes, with updated matchmaking code, that means you can technically have 99 players.
1584 lines
40 KiB
Lua
1584 lines
40 KiB
Lua
--
|
|
-- ## ##### ###### ###### ######
|
|
-- ## ## ## ## ## ## ## ###
|
|
-- ## ## ## ## ## ## ###
|
|
-- ## ## ## ###### ## ######
|
|
-- ## ## ## ## ## ## ###
|
|
-- ## ## ## ## ## ## ### ##
|
|
-- ##### ##### ## ## ###### ######
|
|
--
|
|
-- ComputerCraft port of Tetris
|
|
-- by LDDestroier
|
|
--
|
|
-- Supports wall kicking, holding, fast-dropping,
|
|
-- and ghost pieces.
|
|
--
|
|
-- TO-DO:
|
|
-- + Add random color pulsation (for effect!)
|
|
-- + Add a proper title screen. The current one is pathetic.
|
|
|
|
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
|
|
instanceID = math.random(1, 2^31-1), -- random per-instance value to ensure skynet doesn't poopie
|
|
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
|
|
boardOverflow = 12, -- amount of space above the board that it can overflow
|
|
paused = false, -- whether or not game is paused
|
|
canPause = true, -- if false, cannot pause game (such as in online multiplayer)
|
|
inputDelay = 0.05, -- amount of time between each input
|
|
gameDelay = 0.05, -- amount of time between game ticks
|
|
config = {
|
|
TGMlock = true, -- replicate the piece locking from Tetris: The Grand Master
|
|
scrubMode = false, -- gives you nothing but I-pieces
|
|
},
|
|
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
|
|
return game.timerNo - 1
|
|
end
|
|
|
|
game.cancelTimer = function(tID)
|
|
game.timers[tID or 0] = nil
|
|
end
|
|
|
|
game.alterTimer = function(tID, mod)
|
|
if game.timers[tID] then
|
|
game.timers[tID] = game.timers[tID] + mod
|
|
end
|
|
end
|
|
|
|
local tableCopy
|
|
tableCopy = function(tbl)
|
|
local output = {}
|
|
for k,v in pairs(tbl) do
|
|
if type(v) == "table" then
|
|
output[k] = tableCopy(v)
|
|
else
|
|
output[k] = v
|
|
end
|
|
end
|
|
return output
|
|
end
|
|
|
|
-- sets up brown as colors.special, for palette swapping magic(k)
|
|
local tColors = tableCopy(colors)
|
|
|
|
tColors.white = 1
|
|
tColors.brown = nil -- brown is now white
|
|
tColors.special = 4096
|
|
term.setPaletteColor(tColors.special, 0xf0f0f0)
|
|
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, clearContent)
|
|
board = board or {}
|
|
board.x = board.x or xpos or 1
|
|
board.y = board.y or ypos or 1
|
|
board.xSize = board.xSize or newXsize or 10
|
|
board.ySize = board.ySize or newYsize or 24 + game.boardOverflow
|
|
board.topCull = board.topCull or topCull or game.boardOverflow
|
|
board.BGcolor = board.BGcolor or newBGcolor or "f"
|
|
for y = 1, board.ySize do
|
|
board[y] = board[y] or {}
|
|
for x = 1, board.xSize do
|
|
-- explanation on each space:
|
|
-- {
|
|
-- boolean; if true, the space is solid
|
|
-- string; the hex color of the space
|
|
-- 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)
|
|
-- }
|
|
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
|
|
end
|
|
|
|
-- tetramino information
|
|
-- don't tamper with this or I'll beat your ass so hard that war veterans would blush
|
|
local minos = {
|
|
[1] = { -- I-piece
|
|
canRotate = true,
|
|
canTspin = false,
|
|
shape = {
|
|
" ",
|
|
"3333",
|
|
" ",
|
|
" ",
|
|
}
|
|
},
|
|
[2] = { -- L-piece
|
|
canRotate = true,
|
|
canTspin = false,
|
|
shape = {
|
|
" 1",
|
|
"111",
|
|
" ",
|
|
}
|
|
},
|
|
[3] = { -- J-piece
|
|
canRotate = true,
|
|
canTspin = false,
|
|
shape = {
|
|
"b ",
|
|
"bbb",
|
|
" ",
|
|
}
|
|
},
|
|
[4] = { -- O-piece
|
|
canRotate = true,
|
|
canTspin = false,
|
|
shape = {
|
|
"44",
|
|
"44",
|
|
}
|
|
},
|
|
[5] = { -- T-piece
|
|
canRotate = true,
|
|
canTspin = true,
|
|
shape = {
|
|
" a ",
|
|
"aaa",
|
|
" ",
|
|
}
|
|
},
|
|
[6] = { -- Z-piece
|
|
canRotate = true,
|
|
canTspin = false,
|
|
shape = {
|
|
"ee ",
|
|
" ee",
|
|
" ",
|
|
}
|
|
},
|
|
[7] = { -- S-piece
|
|
canRotate = true,
|
|
canTspin = false,
|
|
shape = {
|
|
" 55",
|
|
"55 ",
|
|
" ",
|
|
}
|
|
},
|
|
["gameover"] = { -- special "mino" for game over
|
|
canRotate = false,
|
|
shape = {
|
|
" ccc ccc c c ccccc ccc c c ccccc cccc",
|
|
"c c c cc cc c c c c c c c c",
|
|
"c c c cc cc c c c c c c c c",
|
|
"c cc ccccc c c c cccc c c c c cccc cccc",
|
|
"c c c c c c c c c c c c c c",
|
|
"c c c c c c c c c c c c c c",
|
|
"c c c c c c c c c c c c c c",
|
|
" ccc c c c c ccccc ccc c ccccc c c",
|
|
}
|
|
},
|
|
["yousuck"] = {
|
|
canRotate = false,
|
|
shape = {
|
|
"c c ccc c c ccc c c ccc c c",
|
|
"c c c c c c c c c c c c c c ",
|
|
"c c c c c c c c c c c c ",
|
|
" c c c c c c ccc c c c cc ",
|
|
" c c c c c c c c c c c ",
|
|
" c c c c c c c c c c c ",
|
|
" c c c c c c c c c c c c c",
|
|
" c ccc ccc ccc ccc ccc c c",
|
|
}
|
|
},
|
|
["eatmyass"] = {
|
|
canRotate = false,
|
|
shape = {
|
|
"ccccc ccc ccccc c c c c ccc ccc ccc ",
|
|
"c c c c cc cc c c c c c c c c",
|
|
"c c c c c c c c c c c c c c ",
|
|
"cccc ccccc c c c c c ccccc ccc ccc ",
|
|
"c c c c c c c c c c c",
|
|
"c c c c c c c c c c c",
|
|
"c c c c c c c c c c c c c",
|
|
"ccccc c c c c c c c c ccc ccc ",
|
|
}
|
|
},
|
|
["nice"] = { -- nice
|
|
canRotate = false,
|
|
shape = {
|
|
" c ",
|
|
" ",
|
|
"c ccc c cccc cccc ",
|
|
"c c c c c c c c ",
|
|
"cc c c c c c ",
|
|
"c c c c cccccc ",
|
|
"c c c c c ",
|
|
"c c c c c ",
|
|
"c c c c c c c ",
|
|
"c c c cccc cccc c",
|
|
}
|
|
}
|
|
}
|
|
|
|
local images = {
|
|
-- to do...add images...
|
|
}
|
|
|
|
-- converts blit colors to colors api, and back
|
|
local to_colors, to_blit = {
|
|
[' '] = 0,
|
|
['0'] = 1,
|
|
['1'] = 2,
|
|
['2'] = 4,
|
|
['3'] = 8,
|
|
['4'] = 16,
|
|
['5'] = 32,
|
|
['6'] = 64,
|
|
['7'] = 128,
|
|
['8'] = 256,
|
|
['9'] = 512,
|
|
['a'] = 1024,
|
|
['b'] = 2048,
|
|
['c'] = 4096,
|
|
['d'] = 8192,
|
|
['e'] = 16384,
|
|
['f'] = 32768,
|
|
}, {}
|
|
for k,v in pairs(to_colors) do
|
|
to_blit[v] = k
|
|
end
|
|
|
|
-- checks if (x, y) is a valid space on the board
|
|
local doesSpaceExist = function(board, x, y)
|
|
return (x >= 1 and x <= board.xSize) and (y >= 1 and y <= board.ySize)
|
|
end
|
|
|
|
-- checks if (x, y) is being occupied by a tetramino (or if it's off-board)
|
|
local isSpaceSolid = function(board, _x, _y)
|
|
local x, y = math.floor(_x), math.floor(_y)
|
|
if doesSpaceExist(board, x, y) then
|
|
return board[y][x][1]
|
|
else
|
|
return true
|
|
end
|
|
end
|
|
|
|
-- ticks down a space's timers, which can cause it to become non-solid or background-colored
|
|
local ageSpace = function(board, _x, _y)
|
|
local x, y = math.floor(_x), math.floor(_y)
|
|
if doesSpaceExist(board, x, y) then
|
|
-- make space non-solid if timer elapses
|
|
if board[y][x][3] ~= 0 then
|
|
board[y][x][3] = board[y][x][3] - 1
|
|
if board[y][x][3] == 0 then
|
|
board[y][x][1] = false
|
|
end
|
|
end
|
|
-- color space board.BGcolor if timer elapses
|
|
if board[y][x][4] ~= 0 then
|
|
board[y][x][4] = board[y][x][4] - 1
|
|
if board[y][x][4] == 0 then
|
|
board[y][x][2] = board.BGcolor
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local transmit = function(msg)
|
|
if game.net.active then
|
|
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
|
|
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,
|
|
you = game.you,
|
|
specialColor = {term.getPaletteColor(tColors.special)}
|
|
})
|
|
else
|
|
transmit({
|
|
command = command,
|
|
gameID = game.net.gameID,
|
|
time = doSendTime and getTime(),
|
|
pNum = playerNumber,
|
|
you = game.you,
|
|
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])
|
|
if replaceColor then
|
|
for yy = 1, #mino.shape do
|
|
mino.shape[yy] = mino.shape[yy]:gsub("[^ ]", replaceColor)
|
|
end
|
|
end
|
|
-- what color the ghost mino will be
|
|
mino.ghostColor = 0x353535
|
|
|
|
mino.x = x
|
|
mino.y = y
|
|
mino.didTspin = false -- if the player has done a T-spin with this piece
|
|
mino.lockBreaks = 16 -- anti-infinite measure
|
|
mino.waitingForLock = false
|
|
mino.board = board
|
|
mino.minoType = minoType
|
|
-- checks to see if the mino is currently clipping with a solid board space (with the offset values)
|
|
mino.checkCollision = function(xOffset, yOffset)
|
|
local cx, cy
|
|
for y = 1, #mino.shape do
|
|
for x = 1, #mino.shape[y] do
|
|
cx = mino.x + x + (xOffset or 0)
|
|
cy = mino.y + y + (yOffset or 0)
|
|
if mino.shape[y]:sub(x,x) ~= " " then
|
|
if isSpaceSolid(mino.board, cx, cy) then
|
|
return true
|
|
end
|
|
end
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
-- rotates a mino, and kicks it off a wall if need be
|
|
mino.rotate = function(direction)
|
|
local output = {}
|
|
local oldShape = tableCopy(mino.shape)
|
|
local origX, origY = mino.x, mino.y
|
|
for y = 1, #mino.shape do
|
|
output[y] = {}
|
|
for x = 1, #mino.shape[y] do
|
|
if direction == 1 then
|
|
output[y][x] = mino.shape[#mino.shape - (x - 1)]:sub(y,y)
|
|
elseif direction == -1 then
|
|
output[y][x] = mino.shape[x]:sub(-y, -y)
|
|
else
|
|
error("invalid rotation direction (must be 1 or -1)")
|
|
end
|
|
end
|
|
output[y] = table.concat(output[y])
|
|
end
|
|
mino.shape = output
|
|
-- try to kick off wall/floor
|
|
if mino.checkCollision(0, 0) then
|
|
-- try T-spin triple rotation
|
|
if not mino.checkCollision(-direction, 2) then
|
|
mino.y = mino.y + 2
|
|
mino.x = mino.x - direction
|
|
mino.didTspin = true
|
|
return true
|
|
end
|
|
-- kick off floor
|
|
for y = 1, math.floor(#mino.shape) do
|
|
if not mino.checkCollision(0, -y) then
|
|
mino.y = mino.y - y
|
|
return true
|
|
end
|
|
end
|
|
-- kick off right wall
|
|
for x = 0, -math.floor(#mino.shape[1] / 2), -1 do
|
|
if not mino.checkCollision(x, 0) then
|
|
mino.x = mino.x + x
|
|
return true
|
|
end
|
|
-- try diagonal-down
|
|
if not mino.checkCollision(x, 1) then
|
|
mino.x = mino.x + x
|
|
mino.y = mino.y + 1
|
|
mino.didTspin = true
|
|
return true
|
|
end
|
|
end
|
|
-- kick off left wall
|
|
for x = 0, math.floor(#mino.shape[1] / 2) do
|
|
if not mino.checkCollision(x, 0) then
|
|
mino.x = mino.x + x
|
|
return true
|
|
end
|
|
-- try diagonal-down
|
|
if not mino.checkCollision(x, 1) then
|
|
mino.x = mino.x + x
|
|
mino.y = mino.y + 1
|
|
mino.didTspin = true
|
|
return true
|
|
end
|
|
end
|
|
mino.shape = oldShape
|
|
return false
|
|
else
|
|
return true
|
|
end
|
|
end
|
|
-- draws a mino onto a board; you'll still need to render the board, though
|
|
mino.draw = function(isSolid)
|
|
for y = 1, #mino.shape do
|
|
for x = 1, #mino.shape[y] do
|
|
if mino.shape[y]:sub(x,x) ~= " " then
|
|
if doesSpaceExist(mino.board, x + math.floor(mino.x), y + math.floor(mino.y)) then
|
|
mino.board[y + math.floor(mino.y)][x + math.floor(mino.x)] = {
|
|
isSolid or false,
|
|
mino.shape[y]:sub(x,x),
|
|
isSolid and 0 or 0,
|
|
isSolid and 0 or 1
|
|
}
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
-- moves a mino, making sure not to clip with solid board spaces
|
|
mino.move = function(x, y, doSlam)
|
|
if not mino.checkCollision(x, y) then
|
|
mino.x = mino.x + x
|
|
mino.y = mino.y + y
|
|
mino.didTspin = false
|
|
return true
|
|
elseif doSlam then
|
|
for sx = 0, x, math.abs(x) / x do
|
|
if mino.checkCollision(sx, 0) then
|
|
mino.x = mino.x + sx - math.abs(x) / x
|
|
break
|
|
end
|
|
mino.didTspin = false
|
|
end
|
|
for sy = 0, math.ceil(y), math.abs(y) / y do
|
|
if mino.checkCollision(0, sy) then
|
|
mino.y = mino.y + sy - math.abs(y) / y
|
|
break
|
|
end
|
|
mino.didTspin = false
|
|
end
|
|
else
|
|
return false
|
|
end
|
|
end
|
|
|
|
return mino
|
|
end
|
|
|
|
-- generates a random number, excluding those listed in the _psExclude table
|
|
local pseudoRandom = function(randomPieces)
|
|
if game.config.scrubMode then
|
|
return 1
|
|
else
|
|
if #randomPieces == 0 then
|
|
for i = 1, #minos do
|
|
randomPieces[i] = i
|
|
end
|
|
end
|
|
local rand = math.random(1, #randomPieces)
|
|
local num = randomPieces[rand]
|
|
table.remove(randomPieces, rand)
|
|
return num
|
|
end
|
|
end
|
|
|
|
-- initialize players
|
|
local initializePlayers = function(amountOfPlayers)
|
|
local newPlayer = function(xmod, ymod)
|
|
return {
|
|
xmod = xmod,
|
|
ymod = ymod,
|
|
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
|
|
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
|
|
garbage = 0, -- amount of garbage you'll get after the next drop
|
|
lines = 0, -- amount of lines cleared, "points"
|
|
combo = 0, -- amount of consequative line clears
|
|
drawCombo = false, -- draw the combo message
|
|
lastLinesClear = 0, -- previous amount of simultaneous line clears (does not reset if miss)
|
|
level = 1, -- level determines speed of mino drop
|
|
fallSteps = 0.1, -- amount of spaces the mino will draw each drop
|
|
}
|
|
end
|
|
|
|
game.p = {}
|
|
game.pp = {}
|
|
|
|
for i = 1, (amountOfPlayers or 1) do
|
|
game.p[i], game.pp[i] = newPlayer((scr_x/2 - 8 * amountOfPlayers) + (i - 1) * 18, 0)
|
|
end
|
|
|
|
-- generates the initial queue of minos per player
|
|
for p = 1, #game.pp do
|
|
for i = 1, #minos do
|
|
game.pp[p].queue[i] = pseudoRandom(game.p[p].randomPieces)
|
|
end
|
|
end
|
|
end
|
|
|
|
-- actually renders a board to the screen
|
|
local renderBoard = function(board, bx, by, doAgeSpaces, blankColor)
|
|
local char, line
|
|
local tY = board.y + (by or 0)
|
|
for y = (board.topCull or 0) + 1, board.ySize, 3 do
|
|
line = {("\143"):rep(board.xSize),"",""}
|
|
term.setCursorPos(board.x + (bx or 0), tY)
|
|
for x = 1, board.xSize do
|
|
line[2] = line[2] .. (blankColor or board[y][x][2])
|
|
if board[y + 1] then
|
|
line[3] = line[3] .. (blankColor or board[y + 1][x][2])
|
|
else
|
|
line[3] = line[3] .. board.BGcolor
|
|
end
|
|
end
|
|
term.blit(line[1], line[2], line[3])
|
|
line = {("\131"):rep(board.xSize),"",""}
|
|
term.setCursorPos(board.x + (bx or 0), tY + 1)
|
|
for x = 1, board.xSize do
|
|
if board[y + 2] then
|
|
line[2] = line[2] .. (blankColor or board[y + 1][x][2])
|
|
line[3] = line[3] .. (blankColor or board[y + 2][x][2])
|
|
elseif board[y + 1] then
|
|
line[2] = line[2] .. (blankColor or board[y + 1][x][2])
|
|
line[3] = line[3] .. board.BGcolor
|
|
else
|
|
line[2] = line[2] .. board.BGcolor
|
|
line[3] = line[3] .. board.BGcolor
|
|
end
|
|
end
|
|
term.blit(line[1], line[2], line[3])
|
|
tY = tY + 2
|
|
end
|
|
if doAgeSpaces then
|
|
for y = 1, board.ySize do
|
|
for x = 1, board.xSize do
|
|
ageSpace(board, x, y)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- checks if you've done the one thing in tetris that you need to be doing
|
|
local checkIfLineCleared = function(board, y)
|
|
for x = 1, board.xSize do
|
|
if not board[y][x][1] then
|
|
return false
|
|
end
|
|
end
|
|
return true
|
|
end
|
|
|
|
-- draws the score of a player, and clears the space where the combo text is drawn
|
|
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: " .. cPlayer.lines)
|
|
term.setCursorPos(2 + player.xmod, 19 + player.ymod)
|
|
term.write((" "):rep(14))
|
|
end
|
|
end
|
|
|
|
local drawLevel = function(player, cPlayer)
|
|
term.setCursorPos(13 + player.xmod, 17 + player.ymod)
|
|
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, cPlayer, lines, didTspin)
|
|
local msgs = {
|
|
"SINGLE",
|
|
"DOUBLE",
|
|
"TRIPLE",
|
|
"TETRIS"
|
|
}
|
|
if not msgs[lines] then
|
|
return
|
|
end
|
|
term.setCursorPos(player.xmod + 2, player.ymod + 18)
|
|
term.setTextColor(tColors.white)
|
|
term.write((" "):rep(16))
|
|
term.setCursorPos(player.xmod + 2, player.ymod + 18)
|
|
if didTspin then
|
|
term.write("T-SPIN ")
|
|
else
|
|
if lines == cPlayer.lastLinesCleared then
|
|
if lines == 3 then
|
|
term.write("OH BABY A ")
|
|
else
|
|
term.write("ANOTHER ")
|
|
end
|
|
end
|
|
end
|
|
term.write(msgs[lines])
|
|
if cPlayer.combo >= 2 then
|
|
term.setCursorPos(player.xmod + 2, player.ymod + 19)
|
|
term.setTextColor(tColors.white)
|
|
term.write((" "):rep(16))
|
|
term.setCursorPos(player.xmod + 2, player.ymod + 19)
|
|
if lines == 4 and cPlayer.combo == 3 then
|
|
term.write("HOLY SHIT!")
|
|
elseif lines == 4 and cPlayer.combo > 3 then
|
|
term.write("ALRIGHT JACKASS")
|
|
else
|
|
term.write(cPlayer.combo .. "x COMBO")
|
|
end
|
|
end
|
|
|
|
end
|
|
|
|
-- god damn it you've fucked up
|
|
local gameOver = function(player, cPlayer)
|
|
local mino
|
|
if cPlayer.lines == 0 then
|
|
mino = makeNewMino("eatmyass", player.board, 12, 3 + game.boardOverflow)
|
|
elseif cPlayer.lines <= 5 then
|
|
mino = makeNewMino("yousuck", player.board, 12, 3 + game.boardOverflow)
|
|
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)
|
|
end
|
|
local color = 0
|
|
for i = 1, 140 do
|
|
mino.x = mino.x - 1
|
|
mino.draw()
|
|
sendInfo("send_info", false)
|
|
renderBoard(player.board, 0, 0, true)
|
|
for i = 1, 20 do
|
|
color = color + 0.01
|
|
term.setPaletteColor(4096, math.sin(color) / 2 + 0.5, math.sin(color) / 2, math.sin(color) / 2)
|
|
end
|
|
sleep(0.1)
|
|
end
|
|
return
|
|
end
|
|
|
|
-- calculates the amount of garbage to send
|
|
local calculateGarbage = function(lines, combo, backToBack, didTspin)
|
|
local output = 0
|
|
local clearTbl = {}
|
|
if didTspin then
|
|
clearTbl = {
|
|
2,
|
|
4,
|
|
6,
|
|
8,
|
|
10,
|
|
12,
|
|
14,
|
|
}
|
|
else
|
|
clearTbl = {
|
|
0,
|
|
1,
|
|
2,
|
|
4,
|
|
6,
|
|
8,
|
|
10
|
|
}
|
|
end
|
|
return (clearTbl[lines] or 0) + backToBack + math.max(0, combo - 2)
|
|
end
|
|
|
|
-- actually give a player some garbage
|
|
local doleOutGarbage = function(player, cPlayer, amount)
|
|
local board = player.board
|
|
local gx = math.random(1, board.xSize)
|
|
local repeatProbability = 75 -- percent probability that garbage will leave the same hole open
|
|
for i = 1, amount do
|
|
table.remove(player.board, 1)
|
|
player.board[board.ySize] = {}
|
|
for x = 1, board.xSize do
|
|
if x ~= gx then
|
|
player.board[board.ySize][x] = {true, "8", 0, 0}
|
|
else
|
|
player.board[board.ySize][x] = {false, board.BGcolor, 0, 0}
|
|
end
|
|
end
|
|
if math.random(0, 100) > repeatProbability then
|
|
gx = math.random(1, board.xSize)
|
|
end
|
|
end
|
|
cPlayer.garbage = 0
|
|
end
|
|
|
|
-- initiates a game as a specific player (takes a number)
|
|
local startGame = function(playerNumber)
|
|
|
|
local mino, ghostMino
|
|
local dropTimer, inputTimer, lockTimer, tickTimer, comboTimer
|
|
local evt, board, player, cPlayer, control
|
|
local finished -- whether or not a mino is done being placed
|
|
local clearedLines = {} -- used when calculating cleared lines
|
|
|
|
if game.net.isHost then
|
|
|
|
player = game.p[playerNumber]
|
|
cPlayer = game.pp[playerNumber]
|
|
board = player.board
|
|
control = player.control
|
|
|
|
local draw = function(isSolid)
|
|
local canChangeSpecial = true
|
|
for k,v in pairs(game.p) do
|
|
if v.flashingSpecial then
|
|
canChangeSpecial = false
|
|
break
|
|
end
|
|
end
|
|
if canChangeSpecial 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
|
|
|
|
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
|
|
game.running = false
|
|
sendInfo("quit_game", false)
|
|
return
|
|
end
|
|
|
|
if game.paused then
|
|
if control.pause == 1 then
|
|
game.paused = false
|
|
end
|
|
else
|
|
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 + game.inputDelay
|
|
end
|
|
end
|
|
|
|
renderBoard(player.holdBoard, 0, 0, true)
|
|
|
|
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
|
|
)
|
|
cPlayer.mino = mino
|
|
|
|
ghostMino = makeNewMino(
|
|
currentMinoType,
|
|
board,
|
|
math.floor(board.xSize / 2) - 2,
|
|
game.boardOverflow,
|
|
"c"
|
|
)
|
|
cPlayer.ghostMino = ghostMino
|
|
|
|
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(game.inputDelay)
|
|
|
|
-- 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(game.inputDelay)
|
|
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[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
|
|
end
|
|
elseif evt[2] == lockTimer then
|
|
if not game.paused then
|
|
cPlayer.canHold = true
|
|
draw(true)
|
|
break
|
|
end
|
|
end
|
|
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(player, 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
|
|
cPlayer.garbage = math.max(0, cPlayer.garbage)
|
|
|
|
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)
|
|
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
|
|
|
|
-- take some garbage for yourself
|
|
|
|
if cPlayer.garbage > 0 then
|
|
doleOutGarbage(player, cPlayer, cPlayer.garbage)
|
|
end
|
|
end
|
|
else
|
|
-- if you're a client, take in all that board info and just fukkin draw it
|
|
|
|
inputTimer = os.startTimer(game.inputDelay)
|
|
|
|
local timeoutTimer = os.startTimer(3)
|
|
|
|
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
|
|
os.cancelTimer(timeoutTimer or 0)
|
|
timeoutTimer = os.startTimer(3)
|
|
elseif evt[1] == "timer" then
|
|
if evt[2] == inputTimer then
|
|
os.cancelTimer(inputTimer or 0)
|
|
inputTimer = os.startTimer(game.inputDelay)
|
|
if player then
|
|
for k,v in pairs(player.control) do
|
|
player.control[k] = v + game.inputDelay
|
|
end
|
|
end
|
|
elseif evt[2] == timeoutTimer then
|
|
return
|
|
end
|
|
end
|
|
end
|
|
|
|
end
|
|
end
|
|
|
|
-- records all key input
|
|
local getInput = function()
|
|
local evt
|
|
while true do
|
|
evt = {os.pullEvent()}
|
|
if evt[1] == "key" and evt[3] == false then
|
|
keysDown[evt[2]] = 1
|
|
elseif evt[1] == "key_up" then
|
|
keysDown[evt[2]] = nil
|
|
end
|
|
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]]
|
|
if not game.net.isHost then
|
|
sendInfo("send_info", false)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local cTime
|
|
local networking = function()
|
|
local evt, side, channel, repchannel, msg, distance
|
|
local currentPlayers = 1
|
|
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.instanceID ~= game.instanceID then
|
|
if msg.time < cTime then
|
|
game.net.isHost = false
|
|
game.you = 2
|
|
game.net.gameID = msg.gameID
|
|
else
|
|
game.net.isHost = true
|
|
end
|
|
|
|
transmit({
|
|
gameID = game.net.gameID,
|
|
time = cTime,
|
|
command = "find_game",
|
|
instanceID = game.instanceID
|
|
})
|
|
game.net.waitingForGame = false
|
|
os.queueEvent("new_game", game.net.gameID)
|
|
return game.net.gameID
|
|
end
|
|
end
|
|
else
|
|
if msg.gameID == game.net.gameID then
|
|
|
|
if game.net.isHost then
|
|
if type(msg.control) == "table" then
|
|
if type(msg.you) == "number" and msg.you ~= game.you then
|
|
game.p[msg.you].control = msg.control
|
|
for y = 1, game.p[msg.you].board.ySize do
|
|
for x = 1, game.p[msg.you].board.xSize do
|
|
ageSpace(game.p[msg.you].board, x, y)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
else
|
|
if type(msg.you) == "number" and msg.you ~= game.you then
|
|
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[msg.you].board, 0, 0, true)
|
|
sleep(0.05)
|
|
end
|
|
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)
|
|
return true
|
|
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)
|
|
return true
|
|
else
|
|
return false, "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)
|
|
return true
|
|
else
|
|
return false, "No modem was found."
|
|
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 H to see controls.", 10)
|
|
cwrite("Press Q to quit.", 11)
|
|
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.h then
|
|
return "help"
|
|
elseif evt[2] == keys.q then
|
|
return "quit"
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local screenError = function(...)
|
|
local lines = {...}
|
|
term.setBackgroundColor(tColors.black)
|
|
term.setTextColor(tColors.white)
|
|
term.clear()
|
|
for i = 1, #lines do
|
|
cwrite(lines[i], 2 + i)
|
|
end
|
|
cwrite("Press any key to continue.", #lines + 4)
|
|
sleep(0)
|
|
repeat until os.pullEvent("key")
|
|
end
|
|
|
|
-- a lot of these menus and whatnot are very primitive. I can improve that later
|
|
local showHelp = function()
|
|
term.setBackgroundColor(tColors.black)
|
|
term.setTextColor(tColors.white)
|
|
term.clear()
|
|
cwrite("CONTROLS (defaults):", 2)
|
|
term.setCursorPos(1, 4)
|
|
print(" Move Piece: LEFT and RIGHT")
|
|
print(" Hard Drop: UP")
|
|
print(" Fast Drop: DOWN")
|
|
print(" Rotate Piece: Z and X")
|
|
print(" Hold Piece: L.SHIFT")
|
|
print(" Quit: Q")
|
|
print("\n Press any key to continue.")
|
|
sleep(0)
|
|
repeat until os.pullEvent("key")
|
|
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.net.isHost = true
|
|
finished = false
|
|
game.running = true
|
|
|
|
while true do
|
|
modeVal = titleScreen()
|
|
|
|
if modeVal == "1P" then
|
|
game.net.active = false
|
|
game.amountOfPlayers = 1
|
|
game.gameDelay = 0.05
|
|
break
|
|
elseif modeVal == "2P" then
|
|
if setUpModem() then
|
|
if (game.net.skynet and game.net.useSkynet) then
|
|
game.gameDelay = 0.1
|
|
else
|
|
game.gameDelay = 0.05
|
|
end
|
|
game.net.active = true
|
|
game.amountOfPlayers = 2
|
|
break
|
|
else
|
|
screenError("A modem is required for multiplayer.")
|
|
finished = true
|
|
end
|
|
elseif modeVal == "skynet" then
|
|
if http.websocket then
|
|
game.net.useSkynet = not game.net.useSkynet
|
|
setUpModem()
|
|
else
|
|
screenError(
|
|
"Skynet requires websocket support.",
|
|
"Use CCEmuX, CC:Tweaked,",
|
|
"or CraftOS-PC 2.2 or higher to play."
|
|
)
|
|
finished = true
|
|
end
|
|
elseif modeVal == "help" then
|
|
showHelp()
|
|
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",
|
|
instanceID = game.instanceID
|
|
})
|
|
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
|
|
if type(v) == "number" then
|
|
term.setPaletteColor(v, term.nativePaletteColor(v))
|
|
end
|
|
end
|
|
|
|
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
|
|
term.setCursorPos(1, scr_y)
|
|
term.write("Thanks for playing!")
|
|
end
|
|
sleep(0.05)
|
|
end
|
|
term.setCursorPos(1, scr_y)
|