655 lines
15 KiB
Lua
655 lines
15 KiB
Lua
-- SPDX-FileCopyrightText: 2017 Daniel Ratcliffe
|
|
--
|
|
-- SPDX-License-Identifier: LicenseRef-CCPL
|
|
|
|
--[[
|
|
Falling - Based on Tetris by Alexey Pajitnov
|
|
This version written by Gopher, at the request of Dan200, for
|
|
ComputerCraft v1.6. No particular rights are reserved.
|
|
--]]
|
|
|
|
local function colorass(c, bw)
|
|
return term.isColor() and c or bw
|
|
end
|
|
|
|
local block_s1 = {
|
|
{
|
|
{ 1, 0, 0, 0 },
|
|
{ 1, 1, 0, 0 },
|
|
{ 0, 1, 0, 0 },
|
|
{ 0, 0, 0, 0 },
|
|
},
|
|
{
|
|
{ 0, 0, 0, 0 },
|
|
{ 0, 1, 1, 0 },
|
|
{ 1, 1, 0, 0 },
|
|
{ 0, 0, 0, 0 },
|
|
},
|
|
ch = colorass(" ", "{}"),
|
|
fg = colorass(colors.blue, colors.black),
|
|
bg = colorass(colors.cyan, colors.white),
|
|
}
|
|
local block_s2 = {
|
|
{
|
|
{ 0, 1, 0, 0 },
|
|
{ 1, 1, 0, 0 },
|
|
{ 1, 0, 0, 0 },
|
|
{ 0, 0, 0, 0 },
|
|
},
|
|
{
|
|
{ 0, 0, 0, 0 },
|
|
{ 1, 1, 0, 0 },
|
|
{ 0, 1, 1, 0 },
|
|
{ 0, 0, 0, 0 },
|
|
},
|
|
ch = colorass(" ", "{}"),
|
|
fg = colorass(colors.green, colors.black),
|
|
bg = colorass(colors.lime, colors.white),
|
|
}
|
|
local block_line = {
|
|
{
|
|
{ 0, 1, 0, 0 },
|
|
{ 0, 1, 0, 0 },
|
|
{ 0, 1, 0, 0 },
|
|
{ 0, 1, 0, 0 },
|
|
},
|
|
{
|
|
{ 0, 0, 0, 0 },
|
|
{ 1, 1, 1, 1 },
|
|
{ 0, 0, 0, 0 },
|
|
{ 0, 0, 0, 0 },
|
|
},
|
|
ch = colorass(" ", "[]"),
|
|
fg = colorass(colors.pink, colors.black),
|
|
bg = colorass(colors.red, colors.white),
|
|
}
|
|
local block_square = {
|
|
{
|
|
{ 1, 1, 0, 0 },
|
|
{ 1, 1, 0, 0 },
|
|
{ 0, 0, 0, 0 },
|
|
{ 0, 0, 0, 0 },
|
|
},
|
|
ch = colorass(" ", "[]"),
|
|
fg = colorass(colors.lightBlue, colors.black),
|
|
bg = colorass(colors.blue, colors.white),
|
|
}
|
|
local block_L1 = {
|
|
{
|
|
{ 1, 1, 0, 0 },
|
|
{ 0, 1, 0, 0 },
|
|
{ 0, 1, 0, 0 },
|
|
{ 0, 0, 0, 0 },
|
|
},
|
|
{
|
|
{ 0, 0, 0, 0 },
|
|
{ 1, 1, 1, 0 },
|
|
{ 1, 0, 0, 0 },
|
|
{ 0, 0, 0, 0 },
|
|
},
|
|
{
|
|
{ 0, 1, 0, 0 },
|
|
{ 0, 1, 0, 0 },
|
|
{ 0, 1, 1, 0 },
|
|
{ 0, 0, 0, 0 },
|
|
},
|
|
{
|
|
{ 0, 0, 1, 0 },
|
|
{ 1, 1, 1, 0 },
|
|
{ 0, 0, 0, 0 },
|
|
{ 0, 0, 0, 0 },
|
|
},
|
|
ch = colorass(" ", "()"),
|
|
fg = colorass(colors.orange, colors.black),
|
|
bg = colorass(colors.yellow, colors.white),
|
|
}
|
|
local block_L2 = {
|
|
{
|
|
{ 0, 1, 0, 0 },
|
|
{ 0, 1, 0, 0 },
|
|
{ 1, 1, 0, 0 },
|
|
{ 0, 0, 0, 0 },
|
|
},
|
|
{
|
|
{ 0, 0, 0, 0 },
|
|
{ 1, 1, 1, 0 },
|
|
{ 0, 0, 1, 0 },
|
|
{ 0, 0, 0, 0 },
|
|
},
|
|
{
|
|
{ 0, 1, 1, 0 },
|
|
{ 0, 1, 0, 0 },
|
|
{ 0, 1, 0, 0 },
|
|
{ 0, 0, 0, 0 },
|
|
},
|
|
{
|
|
{ 1, 0, 0, 0 },
|
|
{ 1, 1, 1, 0 },
|
|
{ 0, 0, 0, 0 },
|
|
{ 0, 0, 0, 0 },
|
|
},
|
|
ch = colorass(" ", "()"),
|
|
fg = colorass(colors.brown, colors.black),
|
|
bg = colorass(colors.orange, colors.white),
|
|
}
|
|
local block_T = {
|
|
{
|
|
{ 0, 1, 0, 0 },
|
|
{ 1, 1, 0, 0 },
|
|
{ 0, 1, 0, 0 },
|
|
{ 0, 0, 0, 0 },
|
|
},
|
|
{
|
|
{ 0, 0, 0, 0 },
|
|
{ 1, 1, 1, 0 },
|
|
{ 0, 1, 0, 0 },
|
|
{ 0, 0, 0, 0 },
|
|
},
|
|
{
|
|
{ 0, 1, 0, 0 },
|
|
{ 0, 1, 1, 0 },
|
|
{ 0, 1, 0, 0 },
|
|
{ 0, 0, 0, 0 },
|
|
},
|
|
{
|
|
{ 0, 1, 0, 0 },
|
|
{ 1, 1, 1, 0 },
|
|
{ 0, 0, 0, 0 },
|
|
{ 0, 0, 0, 0 },
|
|
},
|
|
ch = colorass(" ", "<>"),
|
|
fg = colorass(colors.cyan, colors.black),
|
|
bg = colorass(colors.purple, colors.white),
|
|
}
|
|
|
|
local blocks = { block_line, block_square, block_s1, block_s2, block_L1, block_L2, block_T }
|
|
|
|
local points = { 4, 10, 30, 120 }
|
|
|
|
local function lpad(text, amt)
|
|
text = tostring(text)
|
|
return string.rep(" ", amt - #text) .. text
|
|
end
|
|
|
|
local width, height = term.getSize()
|
|
|
|
if height < 19 or width < 26 then
|
|
print("Your screen is too small to play :(")
|
|
return
|
|
end
|
|
|
|
|
|
local speedsByLevel = {
|
|
1.2,
|
|
1.0,
|
|
.8,
|
|
.65,
|
|
.5,
|
|
.4,
|
|
.3,
|
|
.25,
|
|
.2,
|
|
.15,
|
|
.1,
|
|
.05, }
|
|
|
|
local level = 1
|
|
|
|
local function playGame()
|
|
local score = 0
|
|
local lines = 0
|
|
local initialLevel = level
|
|
local next = blocks[math.random(1, #blocks)]
|
|
|
|
local pit = {}
|
|
|
|
|
|
local heightAdjust = 0
|
|
|
|
if height <= 19 then
|
|
heightAdjust = 1
|
|
end
|
|
|
|
|
|
|
|
local function drawScreen()
|
|
term.setTextColor(colors.white)
|
|
term.setBackgroundColor(colors.black)
|
|
term.clear()
|
|
|
|
term.setTextColor(colors.black)
|
|
term.setBackgroundColor(colorass(colors.lightGray, colors.white))
|
|
term.setCursorPos(22, 2)
|
|
term.write("Score") --score
|
|
term.setCursorPos(22, 5)
|
|
term.write("Level") --level
|
|
term.setCursorPos(22, 8)
|
|
term.write("Lines") --lines
|
|
term.setCursorPos(22, 12)
|
|
term.write("Next") --next
|
|
|
|
term.setCursorPos(21, 1)
|
|
term.write(" ")
|
|
term.setCursorPos(21, 2)
|
|
term.write(" ") --score
|
|
term.setCursorPos(21, 3)
|
|
term.write(" ")
|
|
term.setCursorPos(21, 4)
|
|
term.write(" ")
|
|
term.setCursorPos(21, 5)
|
|
term.write(" ") --level
|
|
term.setCursorPos(21, 6)
|
|
term.write(" ")
|
|
term.setCursorPos(21, 7)
|
|
term.write(" ")
|
|
term.setCursorPos(21, 8)
|
|
term.write(" ") --lines
|
|
term.setCursorPos(21, 9)
|
|
term.write(" ")
|
|
term.setCursorPos(21, 10)
|
|
term.write(" ")
|
|
term.setCursorPos(21, 11)
|
|
term.write(" ")
|
|
term.setCursorPos(21, 12)
|
|
term.write(" ") --next
|
|
term.setCursorPos(26, 12)
|
|
term.write(" ") --next
|
|
term.setCursorPos(21, 13)
|
|
term.write(" ")
|
|
term.setCursorPos(21, 14)
|
|
term.write(" ")
|
|
term.setCursorPos(21, 15)
|
|
term.write(" ")
|
|
term.setCursorPos(21, 16)
|
|
term.write(" ")
|
|
term.setCursorPos(21, 17)
|
|
term.write(" ")
|
|
term.setCursorPos(21, 18)
|
|
term.write(" ")
|
|
term.setCursorPos(21, 19)
|
|
term.write(" ")
|
|
term.setCursorPos(21, 20)
|
|
term.write(" ")
|
|
end
|
|
|
|
local function updateNumbers()
|
|
term.setTextColor(colors.white)
|
|
term.setBackgroundColor(colors.black)
|
|
|
|
term.setCursorPos(22, 3)
|
|
term.write(lpad(score, 5)) --score
|
|
term.setCursorPos(22, 6)
|
|
term.write(lpad(level, 5)) --level
|
|
term.setCursorPos(22, 9)
|
|
term.write(lpad(lines, 5)) --lines
|
|
end
|
|
|
|
local function drawBlockAt(block, xp, yp, rot)
|
|
term.setTextColor(block.fg)
|
|
term.setBackgroundColor(block.bg)
|
|
for y = 1, 4 do
|
|
for x = 1, 4 do
|
|
if block[rot][y][x] == 1 then
|
|
term.setCursorPos((xp + x) * 2 - 3, yp + y - 1 - heightAdjust)
|
|
term.write(block.ch)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local function eraseBlockAt(block, xp, yp, rot)
|
|
term.setTextColor(colors.white)
|
|
term.setBackgroundColor(colors.black)
|
|
for y = 1, 4 do
|
|
for x = 1, 4 do
|
|
if block[rot][y][x] == 1 then
|
|
term.setCursorPos((xp + x) * 2 - 3, yp + y - 1 - heightAdjust)
|
|
term.write(" ")
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local function testBlockAt(block, xp, yp, rot)
|
|
for y = 1, 4 do
|
|
local ty = yp + y - 1
|
|
for x = 1, 4 do
|
|
local tx = xp + x - 1
|
|
if block[rot][y][x] == 1 then
|
|
if tx > 10 or tx < 1 or ty > 20 or pit[ty][tx] ~= 0 then
|
|
return true
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local function pitBlock(block, xp, yp, rot)
|
|
for y = 1, 4 do
|
|
for x = 1, 4 do
|
|
if block[rot][y][x] == 1 then
|
|
pit[yp + y - 1][xp + x - 1] = block
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
local function clearPit()
|
|
for row = 1, 20 do
|
|
pit[row] = {}
|
|
for col = 1, 10 do
|
|
pit[row][col] = 0
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
|
|
drawScreen()
|
|
updateNumbers()
|
|
|
|
--declare & init the pit
|
|
clearPit()
|
|
|
|
|
|
|
|
local halt = false
|
|
local dropSpeed = speedsByLevel[math.min(level, 12)]
|
|
|
|
|
|
local curBlock = next
|
|
next = blocks[math.random(1, 7)]
|
|
|
|
local curX, curY, curRot = 4, 1, 1
|
|
local dropTimer = os.startTimer(dropSpeed)
|
|
|
|
drawBlockAt(next, 11.5, 15 + heightAdjust, 1)
|
|
drawBlockAt(curBlock, curX, curY, curRot)
|
|
|
|
local function redrawPit()
|
|
for r = 1 + heightAdjust, 20 do
|
|
term.setCursorPos(1, r - heightAdjust)
|
|
for c = 1, 10 do
|
|
if pit[r][c] == 0 then
|
|
term.setTextColor(colors.black)
|
|
term.setBackgroundColor(colors.black)
|
|
term.write(" ")
|
|
else
|
|
term.setTextColor(pit[r][c].fg)
|
|
term.setBackgroundColor(pit[r][c].bg)
|
|
term.write(pit[r][c].ch)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local function hidePit()
|
|
for r = 1 + heightAdjust, 20 do
|
|
term.setCursorPos(1, r - heightAdjust)
|
|
term.setTextColor(colors.black)
|
|
term.setBackgroundColor(colors.black)
|
|
term.write(" ")
|
|
end
|
|
end
|
|
|
|
local function msgBox(message)
|
|
local x = math.floor((17 - #message) / 2)
|
|
term.setBackgroundColor(colorass(colors.lightGray, colors.white))
|
|
term.setTextColor(colors.black)
|
|
term.setCursorPos(x, 9)
|
|
term.write("+" .. string.rep("-", #message + 2) .. "+")
|
|
term.setCursorPos(x, 10)
|
|
term.write("|")
|
|
term.setCursorPos(x + #message + 3, 10)
|
|
term.write("|")
|
|
term.setCursorPos(x, 11)
|
|
term.write("+" .. string.rep("-", #message + 2) .. "+")
|
|
term.setTextColor(colors.white)
|
|
term.setBackgroundColor(colors.black)
|
|
term.setCursorPos(x + 1, 10)
|
|
term.write(" " .. message .. " ")
|
|
end
|
|
|
|
local function clearRows()
|
|
local rows = {}
|
|
for r = 1, 20 do
|
|
local count = 0
|
|
for c = 1, 10 do
|
|
if pit[r][c] ~= 0 then
|
|
count = count + 1
|
|
else
|
|
break
|
|
end
|
|
end
|
|
if count == 10 then
|
|
rows[#rows + 1] = r
|
|
end
|
|
end
|
|
|
|
if #rows > 0 then
|
|
for _ = 1, 4 do
|
|
sleep(.1)
|
|
for r = 1, #rows do
|
|
r = rows[r]
|
|
term.setCursorPos(1, r - heightAdjust)
|
|
for c = 1, 10 do
|
|
term.setTextColor(pit[r][c].bg)
|
|
term.setBackgroundColor(pit[r][c].fg)
|
|
term.write(pit[r][c].ch)
|
|
end
|
|
end
|
|
sleep(.1)
|
|
for r = 1, #rows do
|
|
r = rows[r]
|
|
term.setCursorPos(1, r - heightAdjust)
|
|
for c = 1, 10 do
|
|
term.setTextColor(pit[r][c].fg)
|
|
term.setBackgroundColor(pit[r][c].bg)
|
|
term.write(pit[r][c].ch)
|
|
end
|
|
end
|
|
end
|
|
--now remove the rows and drop everything else
|
|
term.setBackgroundColor(colors.black)
|
|
for r = 1, #rows do
|
|
r = rows[r]
|
|
term.setCursorPos(1, r - heightAdjust)
|
|
term.write(" ")
|
|
end
|
|
sleep(.25)
|
|
for r = 1, #rows do
|
|
table.remove(pit, rows[r])
|
|
table.insert(pit, 1, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 })
|
|
end
|
|
redrawPit()
|
|
lines = lines + #rows
|
|
score = score + points[#rows] * math.min(level, 20)
|
|
level = math.floor(lines / 10) + initialLevel
|
|
dropSpeed = speedsByLevel[math.min(level, 12)]
|
|
updateNumbers()
|
|
end
|
|
sleep(.25)
|
|
end
|
|
|
|
local function blockFall()
|
|
if testBlockAt(curBlock, curX, curY + 1, curRot) then
|
|
pitBlock(curBlock, curX, curY, curRot)
|
|
--detect rows that clear
|
|
clearRows()
|
|
|
|
curBlock = next
|
|
curX = 4
|
|
curY = 1
|
|
curRot = 1
|
|
if testBlockAt(curBlock, curX, curY, curRot) then
|
|
halt = true
|
|
end
|
|
drawBlockAt(curBlock, curX, curY, curRot)
|
|
eraseBlockAt(next, 11.5, 15 + heightAdjust, 1)
|
|
next = blocks[math.random(1, 7)]
|
|
drawBlockAt(next, 11.5, 15 + heightAdjust, 1)
|
|
return true
|
|
else
|
|
eraseBlockAt(curBlock, curX, curY, curRot)
|
|
curY = curY + 1
|
|
drawBlockAt(curBlock, curX, curY, curRot)
|
|
return false
|
|
end
|
|
end
|
|
|
|
|
|
while not halt do
|
|
local e = { os.pullEvent() }
|
|
if e[1] == "timer" then
|
|
if e[2] == dropTimer then
|
|
blockFall()
|
|
dropTimer = os.startTimer(dropSpeed)
|
|
end
|
|
elseif e[1] == "key" then
|
|
local key = e[2]
|
|
local dx, dy, dr = 0, 0, 0
|
|
if key == keys.left or key == keys.a then
|
|
dx = -1
|
|
elseif key == keys.right or key == keys.d then
|
|
dx = 1
|
|
elseif key == keys.up or key == keys.w then
|
|
dr = 1
|
|
elseif key == keys.down or key == keys.s then
|
|
while not blockFall() do end
|
|
dropTimer = os.startTimer(dropSpeed)
|
|
elseif key == keys.space then
|
|
hidePit()
|
|
msgBox("Paused")
|
|
while ({ os.pullEvent("key") })[2] ~= keys.space do end
|
|
redrawPit()
|
|
drawBlockAt(curBlock, curX, curY, curRot)
|
|
dropTimer = os.startTimer(dropSpeed)
|
|
end
|
|
if dx + dr ~= 0 then
|
|
if not testBlockAt(curBlock, curX + dx, curY + dy, dr > 0 and curRot % #curBlock + dr or curRot) then
|
|
eraseBlockAt(curBlock, curX, curY, curRot)
|
|
curX = curX + dx
|
|
curY = curY + dy
|
|
curRot = dr == 0 and curRot or curRot % #curBlock + dr
|
|
drawBlockAt(curBlock, curX, curY, curRot)
|
|
end
|
|
end
|
|
elseif e[1] == "term_resize" then
|
|
local _, h = term.getSize()
|
|
if h == 20 then
|
|
heightAdjust = 0
|
|
else
|
|
heightAdjust = 1
|
|
end
|
|
redrawPit()
|
|
drawBlockAt(curBlock, curX, curY, curRot)
|
|
end
|
|
end
|
|
|
|
msgBox("Game Over!")
|
|
while true do
|
|
local _, k = os.pullEvent("key")
|
|
if k == keys.space or k == keys.enter or k == keys.numPadEnter then
|
|
break
|
|
end
|
|
end
|
|
|
|
level = math.min(level, 9)
|
|
end
|
|
|
|
|
|
local selected = 1
|
|
local playersDetected = false
|
|
|
|
local function drawMenu()
|
|
term.setBackgroundColor(colors.black)
|
|
term.setTextColor(colorass(colors.red, colors.white))
|
|
term.clear()
|
|
|
|
local cx, cy = math.floor(width / 2), math.floor(height / 2)
|
|
|
|
term.setCursorPos(cx - 6, cy - 2)
|
|
term.write("F A L L I N G")
|
|
|
|
if playersDetected then
|
|
if selected == 0 then
|
|
term.setTextColor(colorass(colors.blue, colors.black))
|
|
term.setBackgroundColor(colorass(colors.gray, colors.white))
|
|
else
|
|
term.setTextColor(colorass(colors.lightBlue, colors.white))
|
|
term.setBackgroundColor(colors.black)
|
|
end
|
|
term.setCursorPos(cx - 12, cy)
|
|
term.write(" Play head-to-head game! ")
|
|
end
|
|
|
|
term.setCursorPos(cx - 10, cy + 1)
|
|
if selected == 1 then
|
|
term.setTextColor(colorass(colors.blue, colors.black))
|
|
term.setBackgroundColor(colorass(colors.lightGray, colors.white))
|
|
else
|
|
term.setTextColor(colorass(colors.lightBlue, colors.white))
|
|
term.setBackgroundColor(colors.black)
|
|
end
|
|
term.write(" Play from level: <" .. level .. "> ")
|
|
|
|
term.setCursorPos(cx - 3, cy + 3)
|
|
if selected == 2 then
|
|
term.setTextColor(colorass(colors.blue, colors.black))
|
|
term.setBackgroundColor(colorass(colors.lightGray, colors.white))
|
|
else
|
|
term.setTextColor(colorass(colors.lightBlue, colors.white))
|
|
term.setBackgroundColor(colors.black)
|
|
end
|
|
term.write(" Quit ")
|
|
end
|
|
|
|
|
|
local function runMenu()
|
|
drawMenu()
|
|
|
|
while true do
|
|
local event = { os.pullEvent() }
|
|
if event[1] == "key" then
|
|
local key = event[2]
|
|
if key == keys.right or key == keys.d and selected == 1 then
|
|
level = math.min(level + 1, 9)
|
|
drawMenu()
|
|
elseif key == keys.left or key == keys.a and selected == 1 then
|
|
level = math.max(level - 1, 1)
|
|
drawMenu()
|
|
elseif key >= keys.one and key <= keys.nine and selected == 1 then
|
|
level = key - keys.one + 1
|
|
drawMenu()
|
|
elseif key == keys.up or key == keys.w then
|
|
selected = selected - 1
|
|
if selected == 0 then
|
|
selected = 2
|
|
end
|
|
drawMenu()
|
|
elseif key == keys.down or key == keys.s then
|
|
selected = selected % 2 + 1
|
|
drawMenu()
|
|
elseif key == keys.enter or key == keys.numPadEnter or key == keys.space then
|
|
break --begin play!
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
while true do
|
|
runMenu()
|
|
if selected == 2 then
|
|
break
|
|
end
|
|
|
|
playGame()
|
|
end
|
|
|
|
|
|
term.setTextColor(colors.white)
|
|
term.setBackgroundColor(colors.black)
|
|
term.clear()
|
|
term.setCursorPos(1, 1)
|