mirror of
https://github.com/LDDestroier/CC/
synced 2024-11-17 15:24:52 +00:00
1814 lines
46 KiB
Lua
1814 lines
46 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 sentInfos = 0
|
|
|
|
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.1, -- amount of time to hold left or right for it to keep moving that way
|
|
boardOverflow = 20, -- 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
|
|
minimumLockTimer = 0.4, -- shortest amount of time before a piece locks upon touching the ground
|
|
maximumLockTimer = 0.6, -- longest amount of time before a piece locks upon touching the ground
|
|
appearanceDelay = 0.05, -- amount of time to wait after placing a piece
|
|
config = {
|
|
TGMlock = false, -- 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
|
|
softDrop = keys.down, -- shift downwards
|
|
rotateLeft = keys.z, -- rotate counter-clockwise
|
|
rotateRight = keys.x, -- rotate clockwise
|
|
hardDrop = keys.up, -- instantly drop and place piece
|
|
sonicDrop = keys.space, -- instantly drop piece, but not place
|
|
hold = keys.leftShift, -- slot piece into hold buffer
|
|
quit = keys.q, -- fuck off
|
|
pause = keys.p
|
|
},
|
|
softDropSpeed = 2, -- amount of spaces a soft drop will move a mino downwards
|
|
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
|
|
|
|
game.setPaletteColor = function(...)
|
|
if game.amountOfPlayers <= 1 then
|
|
term.setPaletteColor(...)
|
|
end
|
|
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, 0x353535)
|
|
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"
|
|
board.minoAmount = board.minoAmount or 0
|
|
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,
|
|
mainColor = "3",
|
|
shape = {
|
|
" ",
|
|
"3333",
|
|
" ",
|
|
" ",
|
|
}
|
|
},
|
|
[2] = { -- L-piece
|
|
canRotate = true,
|
|
canTspin = false,
|
|
mainColor = "1",
|
|
shape = {
|
|
" 1",
|
|
"111",
|
|
" ",
|
|
}
|
|
},
|
|
[3] = { -- J-piece
|
|
canRotate = true,
|
|
canTspin = false,
|
|
mainColor = "b",
|
|
shape = {
|
|
"b ",
|
|
"bbb",
|
|
" ",
|
|
}
|
|
},
|
|
[4] = { -- O-piece
|
|
canRotate = true,
|
|
canTspin = false,
|
|
mainColor = "4",
|
|
shape = {
|
|
"44",
|
|
"44",
|
|
}
|
|
},
|
|
[5] = { -- T-piece
|
|
canRotate = true,
|
|
canTspin = true,
|
|
mainColor = "a",
|
|
shape = {
|
|
" a ",
|
|
"aaa",
|
|
" ",
|
|
}
|
|
},
|
|
[6] = { -- Z-piece
|
|
canRotate = true,
|
|
canTspin = false,
|
|
mainColor = "e",
|
|
shape = {
|
|
"ee ",
|
|
" ee",
|
|
" ",
|
|
}
|
|
},
|
|
[7] = { -- S-piece
|
|
canRotate = true,
|
|
canTspin = false,
|
|
mainColor = "5",
|
|
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",
|
|
}
|
|
}
|
|
}
|
|
|
|
for k,mino in pairs(minos) do
|
|
minos[k].size = 0
|
|
for y = 1, #mino.shape do
|
|
for x = 1, #mino.shape[y] do
|
|
if mino.shape[y]:sub(x, x) ~= " " then
|
|
minos[k].size = minos[k].size + 1
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
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)
|
|
sentInfos = sentInfos + 1
|
|
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.didWallKick = false
|
|
mino.color = minos[minoType].mainColor or "c"
|
|
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, doNotCountBorder)
|
|
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) and not (doNotCountBorder and not doesSpaceExist(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
|
|
-- check T-spin
|
|
local checkTspin = function(mino)
|
|
if (mino.checkCollision(-1, 0, false) and mino.checkCollision(1, 0, false) and mino.checkCollision(0, -1, false)) then
|
|
if mino.didWallKick then
|
|
mino.didTspin = 1
|
|
else
|
|
mino.didTspin = 2
|
|
end
|
|
else
|
|
mino.didTspin = false
|
|
end
|
|
return mino.didTspin
|
|
end
|
|
-- 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.didWallKick = true
|
|
checkTspin(mino)
|
|
return true
|
|
end
|
|
-- kick off ceiling
|
|
for y = 1, #mino.shape do
|
|
for x = -1, 1 do
|
|
if not mino.checkCollision(x, y) then
|
|
mino.x = mino.x + x
|
|
mino.y = mino.y + y
|
|
mino.didWallKick = true
|
|
|
|
--checkTspin(mino)
|
|
--return true
|
|
end
|
|
end
|
|
end
|
|
-- kick off floor
|
|
for y = 1, math.floor(#mino.shape / 2) do
|
|
if not mino.checkCollision(0, -y) then
|
|
mino.y = mino.y - y
|
|
mino.didWallKick = true
|
|
checkTspin(mino)
|
|
return true
|
|
elseif not mino.checkCollision(-1, -y) then
|
|
mino.y = mino.y - y
|
|
mino.x = mino.x - 1
|
|
mino.didWallKick = true
|
|
checkTspin(mino)
|
|
return true
|
|
elseif not mino.checkCollision(1, -y) then
|
|
mino.y = mino.y - y
|
|
mino.x = mino.x + 1
|
|
mino.didWallKick = true
|
|
checkTspin(mino)
|
|
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
|
|
checkTspin(mino)
|
|
return true
|
|
end
|
|
-- try diagonal-down
|
|
if not mino.checkCollision(x, 1) then
|
|
mino.x = mino.x + x
|
|
mino.y = mino.y + 1
|
|
sendInfo("send_info", false)
|
|
checkTspin(mino)
|
|
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
|
|
mino.didWallKick = true
|
|
checkTspin(mino)
|
|
return true
|
|
end
|
|
-- try diagonal-down
|
|
if not mino.checkCollision(x, 1) then
|
|
mino.x = mino.x + x
|
|
mino.y = mino.y + 1
|
|
sendInfo("send_info", false)
|
|
mino.didWallKick = true
|
|
checkTspin(mino)
|
|
return true
|
|
end
|
|
end
|
|
mino.shape = oldShape
|
|
return false
|
|
else
|
|
checkTspin(mino)
|
|
return true
|
|
end
|
|
end
|
|
-- draws a mino onto a board; you'll still need to render the board, though
|
|
mino.draw = function(isSolid, colorReplace)
|
|
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,
|
|
colorReplace or 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
|
|
if not (x == 0 and y == 0) then
|
|
mino.x = mino.x + x
|
|
mino.y = mino.y + y
|
|
mino.didTspin = false
|
|
mino.didWallKick = false
|
|
end
|
|
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
|
|
if sx ~= 0 then
|
|
mino.didWallKick = false
|
|
mino.didTspin = false
|
|
end
|
|
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
|
|
if sy ~= 0 then
|
|
mino.didWallKick = false
|
|
end
|
|
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, didDoAllClear)
|
|
local msgs = {
|
|
[0] = "",
|
|
"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 didDoAllClear then
|
|
term.write("ALL CLEAR!! ")
|
|
else
|
|
if didTspin then
|
|
if didTspin == 1 then
|
|
term.write("T-SPIN MINI ")
|
|
elseif didTspin == 2 then
|
|
term.write("T-SPIN ")
|
|
end
|
|
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])
|
|
end
|
|
if cPlayer.combo >= 2 and lines > 0 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
|
|
local waitTime
|
|
if cPlayer.lines == 0 then
|
|
mino = makeNewMino("eatmyass", player.board, 12, 3 + game.boardOverflow)
|
|
waitTime = 70
|
|
elseif cPlayer.lines <= 5 then
|
|
mino = makeNewMino("yousuck", player.board, 12, 3 + game.boardOverflow)
|
|
waitTime = 60
|
|
elseif cPlayer.lines == 69 or cPlayer.lines == 690 then
|
|
mino = makeNewMino("nice", player.board, 12, 3 + game.boardOverflow)
|
|
waitTime = 50
|
|
else
|
|
mino = makeNewMino("gameover", player.board, 12, 3 + game.boardOverflow)
|
|
waitTime = 70
|
|
end
|
|
local color = 0
|
|
local evt
|
|
local sTimer
|
|
local cTimer = os.startTimer(1)
|
|
local canSkip = false
|
|
for i = 1, waitTime 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
|
|
game.setPaletteColor(4096, math.sin(color) / 2 + 0.5, math.sin(color) / 2, math.sin(color) / 2)
|
|
end
|
|
|
|
sTimer = os.startTimer(0.05)
|
|
while true do
|
|
evt = {os.pullEvent()}
|
|
if evt[1] == "timer" then
|
|
if evt[2] == sTimer then
|
|
break
|
|
elseif evt[2] == cTimer then
|
|
canSkip = true
|
|
end
|
|
elseif evt[1] == "key" then
|
|
if canSkip then
|
|
return
|
|
end
|
|
end
|
|
end
|
|
end
|
|
return
|
|
end
|
|
|
|
-- used when darkening just-placed pieces
|
|
local normalPalettes = {}
|
|
local darkerPalettes = {}
|
|
for i = 1, 16 do
|
|
normalPalettes[("0123456789abcdef"):sub(i,i)] = {term.getPaletteColor(2 ^ (i - 1))}
|
|
end
|
|
local genDarkerPalettes = function(mult)
|
|
for i = 1, 16 do
|
|
darkerPalettes[("0123456789abcdef"):sub(i,i)] = {
|
|
normalPalettes[("0123456789abcdef"):sub(i,i)][1] * (mult or 0.6),
|
|
normalPalettes[("0123456789abcdef"):sub(i,i)][2] * (mult or 0.6),
|
|
normalPalettes[("0123456789abcdef"):sub(i,i)][3] * (mult or 0.6),
|
|
}
|
|
end
|
|
end
|
|
genDarkerPalettes()
|
|
|
|
-- 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
|
|
board.minoAmount = board.minoAmount + board.xSize - 1
|
|
end
|
|
cPlayer.garbage = 0
|
|
end
|
|
|
|
local ldPalettes = function(light)
|
|
if light then
|
|
for k,v in pairs(normalPalettes) do
|
|
if k == "c" then
|
|
--term.setPaletteColor(to_colors[k], 0xf0f0f0)
|
|
else
|
|
game.setPaletteColor(to_colors[k], table.unpack(v))
|
|
end
|
|
end
|
|
else
|
|
for i = 1, 0.3, -0.1 do
|
|
genDarkerPalettes(i)
|
|
for k,v in pairs(darkerPalettes) do
|
|
if k == "c" then
|
|
--game.setPaletteColor(to_colors[k], 0xf0f0f0)
|
|
else
|
|
game.setPaletteColor(to_colors[k], table.unpack(v))
|
|
end
|
|
end
|
|
sleep(0.05)
|
|
end
|
|
end
|
|
genDarkerPalettes()
|
|
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, doSendInfo)
|
|
local canChangeSpecial = true
|
|
for k,v in pairs(game.p) do
|
|
if v.flashingSpecial then
|
|
canChangeSpecial = false
|
|
break
|
|
end
|
|
end
|
|
if canChangeSpecial then
|
|
game.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)
|
|
if doSendInfo then
|
|
sendInfo("send_info", false, playerNumber)
|
|
end
|
|
renderBoard(board, 0, 0, true)
|
|
renderBoard(player.queueBoard, 0, 0, false)
|
|
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
|
|
game.paused = false
|
|
ldPalettes(true)
|
|
sendInfo("quit_game", false)
|
|
return
|
|
end
|
|
|
|
if game.paused then
|
|
if control.pause == 1 then
|
|
ldPalettes(true)
|
|
game.paused = false
|
|
end
|
|
else
|
|
if control.pause == 1 then
|
|
ldPalettes(false)
|
|
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(nil, true)
|
|
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(nil, true)
|
|
end
|
|
end
|
|
if control.softDrop then
|
|
game.cancelTimer(lockTimer or 0)
|
|
mino.waitingForLock = false
|
|
local oldY = mino.y
|
|
mino.move(0, game.softDropSpeed, true)
|
|
if mino.y ~= oldY then
|
|
draw(nil, true)
|
|
else
|
|
if game.config.TGMlock then
|
|
draw(true, true)
|
|
cPlayer.canHold = true
|
|
finished = true
|
|
board.minoAmount = board.minoAmount + mino.size
|
|
else
|
|
if mino.waitingForLock then
|
|
game.alterTimer(lockTimer, -0.1)
|
|
else
|
|
mino.lockBreaks = mino.lockBreaks - 1
|
|
lockTimer = game.startTimer(math.min(math.max(0.2 / cPlayer.fallSteps, game.minimumLockTimer), game.maximumLockTimer))
|
|
mino.waitingForLock = true
|
|
end
|
|
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(nil, true)
|
|
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(nil, true)
|
|
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(false)
|
|
sendInfo("send_info", false, playerNumber)
|
|
renderBoard(player.holdBoard, 0, 0, false)
|
|
finished = true
|
|
cPlayer.didHold = true
|
|
end
|
|
end
|
|
if control.hardDrop == 1 then
|
|
mino.move(0, board.ySize, true)
|
|
if game.config.TGMlock then
|
|
draw(false)
|
|
else
|
|
draw(true, true)
|
|
cPlayer.canHold = true
|
|
finished = true
|
|
board.minoAmount = board.minoAmount + mino.size
|
|
end
|
|
elseif control.sonicDrop == 1 then
|
|
mino.move(0, board.ySize, true)
|
|
draw(false)
|
|
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)
|
|
cPlayer.didHold = false
|
|
|
|
if takeFromQueue then
|
|
currentMinoType = cPlayer.queue[1]
|
|
end
|
|
|
|
mino = makeNewMino(
|
|
currentMinoType,
|
|
board,
|
|
math.floor(board.xSize / 2) - 2,
|
|
game.boardOverflow - 1
|
|
)
|
|
if mino.checkCollision() then
|
|
mino.y = mino.y - 1
|
|
end
|
|
cPlayer.mino = mino
|
|
|
|
ghostMino = makeNewMino(
|
|
currentMinoType,
|
|
board,
|
|
math.floor(board.xSize / 2) - 2,
|
|
game.boardOverflow - 1,
|
|
"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
|
|
|
|
-- 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
|
|
|
|
mino.draw()
|
|
ghostMino.move(0, board.ySize, true)
|
|
ghostMino.draw()
|
|
sendInfo("send_info", false, playerNumber)
|
|
|
|
draw()
|
|
|
|
|
|
game.cancelTimer(dropTimer)
|
|
game.cancelTimer(lockTimer)
|
|
|
|
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
|
|
mino.oldY = mino.y
|
|
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.min(math.max(0.2 / cPlayer.fallSteps, game.minimumLockTimer), game.maximumLockTimer))
|
|
mino.waitingForLock = true
|
|
end
|
|
else
|
|
mino.move(0, cPlayer.fallSteps, true)
|
|
if math.floor(mino.oldY) ~= math.floor(mino.y) then
|
|
draw(false, true)
|
|
--sendInfo("send_info", false)
|
|
end
|
|
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)
|
|
board.minoAmount = board.minoAmount - board.xSize
|
|
end
|
|
end
|
|
if #clearedLines == 0 then
|
|
if cPlayer.canHold then
|
|
cPlayer.combo = 0
|
|
end
|
|
if mino.didTspin then
|
|
cPlayer.drawCombo = true
|
|
os.cancelTimer(comboTimer or 0)
|
|
comboTimer = os.startTimer(2)
|
|
drawComboMessage(player, cPlayer, #clearedLines, mino.didTspin)
|
|
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
|
|
|
|
-- check for all clear
|
|
local didDoAllClear = board.minoAmount == 0
|
|
|
|
drawComboMessage(player, cPlayer, #clearedLines, mino.didTspin, didDoAllClear)
|
|
|
|
cPlayer.lastLinesCleared = #clearedLines
|
|
|
|
-- give the other fucktard(s) some garbage
|
|
cPlayer.garbage = (didDoAllClear and -10 or 0) + 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
|
|
game.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
|
|
|
|
if #clearedLines == 0 and game.running and not cPlayer.didHold then
|
|
mino.draw(false, "c")
|
|
game.setPaletteColor(colors.brown, table.unpack(darkerPalettes[mino.color]))
|
|
renderBoard(board, 0, 0, true)
|
|
sleep(game.appearanceDelay)
|
|
mino.draw(true)
|
|
--board.minoAmount = board.minoAmount + mino.size
|
|
renderBoard(board, 0, 0, false)
|
|
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
|
|
game.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
|
|
game.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(" Soft Drop: DOWN")
|
|
print(" Sonic Drop: SPACE")
|
|
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)
|