Fixed rotation, added levels

among other things
This commit is contained in:
LDDestroier 2019-10-10 22:11:24 -04:00 committed by GitHub
parent 39f7d03506
commit ba32360948
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 243 additions and 151 deletions

394
ldris.lua
View File

@ -23,7 +23,6 @@ local game = {
p = {}, -- stores player information p = {}, -- stores player information
paused = false, -- whether or not game is paused paused = false, -- whether or not game is paused
canPause = true, -- if false, cannot pause game (such as in online multiplayer) canPause = true, -- if false, cannot pause game (such as in online multiplayer)
fallDelay = 0.25, -- amount of time for each tetramino to fall down one space
inputDelay = 0.05, -- amount of time between each input inputDelay = 0.05, -- amount of time between each input
control = { control = {
moveLeft = keys.left, moveLeft = keys.left,
@ -34,9 +33,21 @@ local game = {
fastDrop = keys.up, fastDrop = keys.up,
hold = keys.leftShift, hold = keys.leftShift,
quit = keys.q quit = keys.q
} },
timers = {},
timerNo = 1
} }
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
local tableCopy local tableCopy
tableCopy = function(tbl) tableCopy = function(tbl)
local output = {} local output = {}
@ -59,31 +70,6 @@ tColors.special = 4096
term.setPaletteColor(tColors.special, 0xf0f0f0) term.setPaletteColor(tColors.special, 0xf0f0f0)
term.setPaletteColor(tColors.white, 0xf0f0f0) term.setPaletteColor(tColors.white, 0xf0f0f0)
-- generates a random number, excluding those listed in the _psExclude table
local _psExclude = {}
local pseudoRandom = function(min, max)
local num
-- check if it will exclude all values
local reset = true
for i = min, max do
if not _psExclude[i] then
reset = false
break
end
end
if reset then
_psExclude = {}
return math.random(min, max)
else
repeat
result = math.random(min, max)
until not _psExclude[result]
_psExclude[result] = true
return result
end
end
-- initializes and fixes up a board -- initializes and fixes up a board
-- boards are 2D objects that can display perfectly square graphics -- boards are 2D objects that can display perfectly square graphics
local clearBoard = function(board, xpos, ypos, newXsize, newYsize, newBGcolor) local clearBoard = function(board, xpos, ypos, newXsize, newYsize, newBGcolor)
@ -213,7 +199,8 @@ local doesSpaceExist = function(board, x, y)
end end
-- checks if (x, y) is being occupied by a tetramino (or if it's off-board) -- checks if (x, y) is being occupied by a tetramino (or if it's off-board)
local isSpaceSolid = function(board, x, y) local isSpaceSolid = function(board, _x, _y)
local x, y = math.floor(_x), math.floor(_y)
if doesSpaceExist(board, x, y) then if doesSpaceExist(board, x, y) then
return board[y][x][1] return board[y][x][1]
else else
@ -222,7 +209,8 @@ local isSpaceSolid = function(board, x, y)
end end
-- ticks down a space's timers, which can cause it to become non-solid or background-colored -- ticks down a space's timers, which can cause it to become non-solid or background-colored
local ageSpace = function(board, x, y) local ageSpace = function(board, _x, _y)
local x, y = math.floor(_x), math.floor(_y)
if doesSpaceExist(board, x, y) then if doesSpaceExist(board, x, y) then
-- make space non-solid if timer elapses -- make space non-solid if timer elapses
if board[y][x][3] ~= 0 then if board[y][x][3] ~= 0 then
@ -254,7 +242,7 @@ local makeNewMino = function(minoType, board, x, y, replaceColor)
mino.x = x mino.x = x
mino.y = y mino.y = y
mino.lockBreaks = 8 -- anti-infinite measure mino.lockBreaks = 16 -- anti-infinite measure
mino.waitingForLock = false mino.waitingForLock = false
mino.board = board mino.board = board
mino.minoType = minoType mino.minoType = minoType
@ -293,25 +281,45 @@ local makeNewMino = function(minoType, board, x, y, replaceColor)
output[y] = table.concat(output[y]) output[y] = table.concat(output[y])
end end
mino.shape = output mino.shape = output
-- try to kick off wall/floor
if mino.checkCollision(0, 0) then if mino.checkCollision(0, 0) then
-- kick off floor
for y = 1, math.floor(#mino.shape) do for y = 1, math.floor(#mino.shape) do
if not mino.checkCollision(0, -y) then if not mino.checkCollision(0, -y) then
mino.y = mino.y - y mino.y = mino.y - y
return return true
end end
end end
for x = 0, -math.floor(#mino.shape[1]), -1 do -- kick off right wall
for x = 0, -math.floor(#mino.shape[1] / 2), -1 do
if not mino.checkCollision(x, 0) then if not mino.checkCollision(x, 0) then
mino.x = mino.x + x mino.x = mino.x + x
return return true
end
-- try diagonal-down
if not mino.checkCollision(x, 1) then
mino.x = mino.x + x
mino.y = mino.y + 1
return true
end end
end end
for x = 0, math.floor(#mino.shape[1]) do -- kick off left wall
for x = 0, math.floor(#mino.shape[1] / 2) do
if not mino.checkCollision(x, 0) then if not mino.checkCollision(x, 0) then
mino.x = mino.x + x mino.x = mino.x + x
return return true
end
-- try diagonal-down
if not mino.checkCollision(x, 1) then
mino.x = mino.x + x
mino.y = mino.y + 1
return true
end end
end end
mino.shape = oldShape
return false
else
return true
end end
end end
-- draws a mino onto a board; you'll still need to render the board, though -- draws a mino onto a board; you'll still need to render the board, though
@ -319,8 +327,8 @@ local makeNewMino = function(minoType, board, x, y, replaceColor)
for y = 1, #mino.shape do for y = 1, #mino.shape do
for x = 1, #mino.shape[y] do for x = 1, #mino.shape[y] do
if mino.shape[y]:sub(x,x) ~= " " then if mino.shape[y]:sub(x,x) ~= " " then
if doesSpaceExist(mino.board, x + mino.x, y + mino.y) then if doesSpaceExist(mino.board, x + math.floor(mino.x), y + math.floor(mino.y)) then
mino.board[y + mino.y][x + mino.x] = { mino.board[y + math.floor(mino.y)][x + math.floor(mino.x)] = {
isSolid or false, isSolid or false,
mino.shape[y]:sub(x,x), mino.shape[y]:sub(x,x),
isSolid and 0 or 0, isSolid and 0 or 0,
@ -358,47 +366,68 @@ local makeNewMino = function(minoType, board, x, y, replaceColor)
return mino return mino
end end
-- generates a random number, excluding those listed in the _psExclude table
local pseudoRandom = function(randomPieces)
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
-- initialize players -- initialize players
local initializePlayers = function() local initializePlayers = function()
game.p[1] = { game.p[1] = {
board = clearBoard({}, 2, 2, 10, 24, "f"), board = clearBoard({}, 2, 2, 10, 24, "f"),
holdBoard = clearBoard({}, 13, 14, 4, 4, "f"), holdBoard = clearBoard({}, 13, 14, 4, 3, "f"),
queueBoard = clearBoard({}, 13, 2, 4, 14, "f"), queueBoard = clearBoard({}, 13, 2, 4, 14, "f"),
hold = 0, randomPieces = {}, -- list of all minos for pseudo-random selection
canHold = true, hold = 0, -- current piece being held
queue = {}, canHold = true, -- whether or not player can hold (can't hold twice in a row)
lines = 0, queue = {}, -- current queue of minos to use
combo = 0, lines = 0, -- amount of lines cleared, "points"
combo = 0, -- amount of consequative line clears
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
} }
game.p[2] = { game.p[2] = {
board = clearBoard({}, 18, 2, 10, 24, "f"), board = clearBoard({}, 18, 2, 10, 24, "f"),
holdBoard = clearBoard({}, 29, 14, 4, 4, "f"), holdBoard = clearBoard({}, 29, 14, 4, 3, "f"),
queueBoard = clearBoard({}, 29, 2, 4, 14, "f"), queueBoard = clearBoard({}, 29, 2, 4, 14, "f"),
randomPieces = {},
hold = 0, hold = 0,
canHold = true, canHold = true,
queue = {}, queue = {},
lines = 0, lines = 0,
combo = 0, combo = 0,
lastLinesClear = 0,
level = 1,
fallSteps = 0.1,
} }
-- generates the initial queue of minos per player -- generates the initial queue of minos per player
for p = 1, #game.p do for p = 1, #game.p do
for i = 1, #minos do for i = 1, #minos do
game.p[p].queue[i] = pseudoRandom(1, #minos) game.p[p].queue[i] = pseudoRandom(game.p[p].randomPieces)
end end
end end
end end
-- actually renders a board to the screen -- actually renders a board to the screen
local renderBoard = function(board, bx, by, doAgeSpaces) local renderBoard = function(board, bx, by, doAgeSpaces, blankColor)
local char, line local char, line
local tY = board.y + (by or 0) local tY = board.y + (by or 0)
for y = 1, board.ySize, 3 do for y = 1, board.ySize, 3 do
line = {("\143"):rep(board.xSize),"",""} line = {("\143"):rep(board.xSize),"",""}
term.setCursorPos(board.x + (bx or 0), tY) term.setCursorPos(board.x + (bx or 0), tY)
for x = 1, board.xSize do for x = 1, board.xSize do
line[2] = line[2] .. board[y][x][2] line[2] = line[2] .. (blankColor or board[y][x][2])
if board[y + 1] then if board[y + 1] then
line[3] = line[3] .. board[y + 1][x][2] line[3] = line[3] .. (blankColor or board[y + 1][x][2])
else else
line[3] = line[3] .. board.BGcolor line[3] = line[3] .. board.BGcolor
end end
@ -408,10 +437,10 @@ local renderBoard = function(board, bx, by, doAgeSpaces)
term.setCursorPos(board.x + (bx or 0), tY + 1) term.setCursorPos(board.x + (bx or 0), tY + 1)
for x = 1, board.xSize do for x = 1, board.xSize do
if board[y + 2] then if board[y + 2] then
line[2] = line[2] .. board[y + 1][x][2] line[2] = line[2] .. (blankColor or board[y + 1][x][2])
line[3] = line[3] .. board[y + 2][x][2] line[3] = line[3] .. (blankColor or board[y + 2][x][2])
elseif board[y + 1] then elseif board[y + 1] then
line[2] = line[2] .. board[y + 1][x][2] line[2] = line[2] .. (blankColor or board[y + 1][x][2])
line[3] = line[3] .. board.BGcolor line[3] = line[3] .. board.BGcolor
else else
line[2] = line[2] .. board.BGcolor line[2] = line[2] .. board.BGcolor
@ -451,6 +480,11 @@ local drawScore = function(player)
term.write((" "):rep(16)) term.write((" "):rep(16))
end end
local drawLevel = function(player)
term.setCursorPos(13, 17)
term.write("Lv" .. player.level .. " ")
end
-- draws the player's simultaneous line clear after clearing one or more lines -- draws the player's simultaneous line clear after clearing one or more lines
-- also tells the player's combo, which is nice -- also tells the player's combo, which is nice
local drawComboMessage = function(player, lines) local drawComboMessage = function(player, lines)
@ -464,6 +498,13 @@ local drawComboMessage = function(player, lines)
"TETRIS" "TETRIS"
} }
term.setCursorPos(2, 18) term.setCursorPos(2, 18)
if lines == player.lastLinesCleared then
if lines == 3 then
term.write("OH BABY A ")
else
term.write("ANOTHER ")
end
end
term.write(msgs[lines]) term.write(msgs[lines])
if player.combo >= 2 then if player.combo >= 2 then
term.setCursorPos(2, 19) term.setCursorPos(2, 19)
@ -502,7 +543,7 @@ local startGame = function(playerNumber)
initializePlayers() initializePlayers()
local mino, ghostMino local mino, ghostMino
local dropTimer, inputTimer, lockTimer local dropTimer, inputTimer, lockTimer, tickTimer
local evt, board, player local evt, board, player
local clearedLines = {} local clearedLines = {}
@ -528,6 +569,11 @@ local startGame = function(playerNumber)
while true do while true do
player.level = math.ceil((1 + player.lines) / 10)
player.fallSteps = 0.075 * (1.33 ^ player.level)
drawLevel(player)
if takeFromQueue then if takeFromQueue then
currentMinoType = player.queue[1] currentMinoType = player.queue[1]
end end
@ -549,7 +595,7 @@ local startGame = function(playerNumber)
if takeFromQueue then if takeFromQueue then
table.remove(player.queue, 1) table.remove(player.queue, 1)
table.insert(player.queue, pseudoRandom(1, #minos)) table.insert(player.queue, pseudoRandom(player.randomPieces))
end end
-- draw queue -- draw queue
@ -570,14 +616,10 @@ local startGame = function(playerNumber)
player.hold, player.hold,
player.holdBoard, player.holdBoard,
#minos[player.hold].shape[1] == 2 and 1 or 0, #minos[player.hold].shape[1] == 2 and 1 or 0,
1 0
) )
end end
dropTimer = os.startTimer(game.fallDelay)
inputTimer = os.startTimer(game.inputDelay)
os.cancelTimer(lockTimer or 0)
takeFromQueue = true takeFromQueue = true
drawScore(player) drawScore(player)
@ -590,106 +632,153 @@ local startGame = function(playerNumber)
draw() draw()
dropTimer = game.startTimer(0)
inputTimer = game.startTimer(game.inputDelay)
game.cancelTimer(lockTimer or 0)
tickTimer = os.startTimer(0.05)
-- drop a piece -- drop a piece
while true do while true do
evt = {os.pullEvent()} evt = {os.pullEvent()}
if evt[1] == "key" then
if evt[2] == game.control.quit then -- tick down internal game timer system
return if evt[1] == "timer" and evt[2] == tickTimer then
elseif evt[2] == game.control.rotateRight then --local delKeys = {}
mino.rotate(1) for k,v in pairs(game.timers) do
ghostMino.rotate(1) game.timers[k] = v - 0.05
os.cancelTimer(lockTimer or 0) if v <= 0 then
mino.waitingForLock = false os.queueEvent("gameTimer", k)
draw() game.timers[k] = nil
elseif evt[2] == game.control.rotateLeft then end
mino.rotate(-1)
ghostMino.rotate(-1)
os.cancelTimer(lockTimer or 0)
mino.waitingForLock = false
draw()
end end
if evt[3] == false then tickTimer = os.startTimer(0.05)
if evt[2] == game.control.moveLeft then end
mino.move(-1, 0)
os.cancelTimer(lockTimer or 0) if player.paused then
mino.waitingForLock = false if evt[1] == "key" then
draw() if evt[2] == game.control.pause then
os.cancelTimer(inputTimer or 0) game.paused = false
inputTimer = os.startTimer(game.inputDelay) end
elseif evt[2] == game.control.moveRight then end
mino.move(1, 0) else
os.cancelTimer(lockTimer or 0) if evt[1] == "key" then
mino.waitingForLock = false if evt[2] == game.control.quit then
draw() return
os.cancelTimer(inputTimer or 0) elseif evt[2] == game.control.pause then
inputTimer = os.startTimer(game.inputDelay) game.paused = true
elseif evt[2] == game.control.fastDrop then elseif evt[2] == game.control.rotateRight then
mino.move(0, board.ySize, true) if mino.rotate(1) then
draw(true) ghostMino.y = mino.y
player.canHold = true ghostMino.rotate(1)
break game.cancelTimer(lockTimer or 0)
elseif evt[2] == game.control.hold then mino.waitingForLock = false
if player.canHold then draw()
if player.hold == 0 then end
takeFromQueue = true elseif evt[2] == game.control.rotateLeft then
else if mino.rotate(-1) then
takeFromQueue = false ghostMino.y = mino.y
end ghostMino.rotate(-1)
player.hold, currentMinoType = currentMinoType, player.hold game.cancelTimer(lockTimer or 0)
player.canHold = false mino.waitingForLock = false
makeNewMino( draw()
player.hold,
player.holdBoard,
#minos[player.hold].shape[1] == 2 and 1 or 0,
1
).draw()
renderBoard(player.holdBoard, 0, 0, true)
break
end end
end end
end if evt[3] == false then
elseif evt[1] == "timer" then if evt[2] == game.control.moveLeft then
if evt[2] == inputTimer then mino.move(-1, 0)
inputTimer = os.startTimer(game.inputDelay) game.cancelTimer(lockTimer or 0)
if keysDown[game.control.moveLeft] == 2 then mino.waitingForLock = false
mino.move(-1, 0) draw()
os.cancelTimer(lockTimer or 0) game.cancelTimer(inputTimer or 0)
mino.waitingForLock = false inputTimer = game.startTimer(game.inputDelay)
draw() elseif evt[2] == game.control.moveRight then
end mino.move(1, 0)
if keysDown[game.control.moveRight] == 2 then game.cancelTimer(lockTimer or 0)
mino.move(1, 0) mino.waitingForLock = false
os.cancelTimer(lockTimer or 0) draw()
mino.waitingForLock = false game.cancelTimer(inputTimer or 0)
draw() inputTimer = game.startTimer(game.inputDelay)
end elseif evt[2] == game.control.fastDrop then
if keysDown[game.control.moveDown] then mino.move(0, board.ySize, true)
mino.move(0, 1)
os.cancelTimer(lockTimer or 0)
mino.waitingForLock = false
draw()
end
elseif evt[2] == dropTimer then
if mino.checkCollision(0, 1) then
if mino.lockBreaks == 0 then
draw(true) draw(true)
player.canHold = true player.canHold = true
break break
elseif not mino.waitingForLock then elseif evt[2] == game.control.hold then
mino.lockBreaks = mino.lockBreaks - 1 if player.canHold then
lockTimer = os.startTimer(game.fallDelay * 2) if player.hold == 0 then
mino.waitingForLock = true 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)
break
end
end
end
elseif evt[1] == "gameTimer" then
if evt[2] == inputTimer then
inputTimer = game.startTimer(game.inputDelay)
if not game.paused then
if keysDown[game.control.moveLeft] == 2 then
if mino.move(-1, 0) then
game.cancelTimer(lockTimer or 0)
mino.waitingForLock = false
draw()
end
end
if keysDown[game.control.moveRight] == 2 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
draw(true)
break
end
end
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
end
else
mino.move(0, player.fallSteps, true)
draw()
end
end
elseif evt[2] == lockTimer then
if not game.paused then
player.canHold = true
draw(true)
break
end end
else
mino.y = mino.y + 1
draw()
end end
dropTimer = os.startTimer(game.fallDelay)
elseif evt[2] == lockTimer then
player.canHold = true
draw(true)
break
end end
end end
end end
@ -708,6 +797,7 @@ local startGame = function(playerNumber)
player.combo = player.combo + 1 player.combo = player.combo + 1
player.lines = player.lines + #clearedLines player.lines = player.lines + #clearedLines
drawComboMessage(player, #clearedLines) drawComboMessage(player, #clearedLines)
player.lastLinesCleared = #clearedLines
for i = 1, 0, -0.12 do for i = 1, 0, -0.12 do
term.setPaletteColor(4096, i,i,i) term.setPaletteColor(4096, i,i,i)
for l = 1, #clearedLines do for l = 1, #clearedLines do
@ -740,8 +830,10 @@ local getInput = function()
keysDown[evt[2]] = 1 keysDown[evt[2]] = 1
timerKey[evt[2]] = os.startTimer(0.2) timerKey[evt[2]] = os.startTimer(0.2)
keyTimer[timerKey[evt[2]]] = evt[2] keyTimer[timerKey[evt[2]]] = evt[2]
elseif evt[1] == "timer" and keyTimer[evt[2]] then elseif evt[1] == "timer" then
keysDown[keyTimer[evt[2]]] = 2 if keysDown[keyTimer[evt[2]]] then
keysDown[keyTimer[evt[2]]] = 2
end
elseif evt[1] == "key_up" then elseif evt[1] == "key_up" then
keysDown[evt[2]] = nil keysDown[evt[2]] = nil
os.cancelTimer(timerKey[evt[2]] or 0) os.cancelTimer(timerKey[evt[2]] or 0)
@ -769,9 +861,9 @@ print(colors.white)
term.setBackgroundColor(colors.black) term.setBackgroundColor(colors.black)
term.setTextColor(colors.white) term.setTextColor(colors.white)
for i = 1, 8 do for i = 1, 5 do
term.scroll(1) term.scroll(1)
if i == 5 then if i == 3 then
term.setCursorPos(1, scr_y) term.setCursorPos(1, scr_y)
term.write("Thanks for playing!") term.write("Thanks for playing!")
end end