ldd-CC/tron.lua

2135 lines
59 KiB
Lua

--[[
TRON Light Cycle Game
programmed by LDDestroier
Get with:
wget https://raw.githubusercontent.com/LDDestroier/CC/master/tron.lua
--]]
local port = 701
local kioskMode = false -- disables options menu
local useLegacyMouseControl = false -- if true, click move regions will be divided into diagonal quadrants
local scr_x, scr_y = term.getSize()
local scr_mx, scr_my = scr_x / 2, scr_y / 2
local isColor = term.isColor()
local doShowByImage = true -- show "By LDDestroier" in title
local gameDelayInit = 0.1 -- lower value = faster game. I recommend 0.1 for SMP play.
local doDrawPlayerNames = true -- draws the names of players onscreen
local doRenderOwnName = false -- if doDrawPlayerNames, also draws your own name
local useSetVisible = false -- use term.current().setVisible, which speeds things up at the cost of multishell
local gridID = 1 -- determines which grid is used
local mode = "menu"
-- initial grid information, (hopefully) transferred to non-host players
local initGrid = {
x1 = -100,
y1 = -100,
x2 = 100,
y2 = 100,
border = "#",
voidcol = "f",
forecol = "8",
backcol = "7",
edgecol = "0"
}
local resetPlayers = function()
return {
[1] = {
num = 1,
x = -3,
y = -5,
direction = -1,
char = "@",
color = {
colors.blue,
colors.blue,
colors.blue,
colors.cyan,
colors.cyan,
colors.lightBlue,
colors.lightBlue,
colors.cyan,
colors.cyan
},
dead = false,
trailLevel = 10,
trailMax = 10,
trailRegen = 0.1,
putTrail = true,
name = "BLU",
initName = "BLU"
},
[2] = {
num = 2,
x = 3,
y = -5,
direction = -1,
char = "@",
color = {
colors.red,
colors.red,
colors.red,
colors.orange,
colors.orange,
colors.yellow,
colors.yellow,
colors.orange,
colors.orange
},
dead = false,
trailLevel = 10,
trailMax = 10,
trailRegen = 0.1,
putTrail = true,
name = "RED",
initName = "RED"
}
}
end
local function interpretArgs(tInput, tArgs)
local output = {}
local errors = {}
local usedEntries = {}
for aName, aType in pairs(tArgs) do
output[aName] = false
for i = 1, #tInput do
if not usedEntries[i] then
if tInput[i] == aName and not output[aName] then
if aType then
usedEntries[i] = true
if type(tInput[i+1]) == aType or type(tonumber(tInput[i+1])) == aType then
usedEntries[i+1] = true
if aType == "number" then
output[aName] = tonumber(tInput[i+1])
else
output[aName] = tInput[i+1]
end
else
output[aName] = nil
errors[1] = errors[1] and (errors[1] + 1) or 1
errors[aName] = "expected " .. aType .. ", got " .. type(tInput[i+1])
end
else
usedEntries[i] = true
output[aName] = true
end
end
end
end
end
for i = 1, #tInput do
if not usedEntries[i] then
output[#output+1] = tInput[i]
end
end
return output, errors
end
local argData = {
["skynet"] = false, -- use Skynet HTTP multiplayer
["quick"] = false, -- start one game immediately
["griddemo"] = false, -- only move the grid
["--update"] = false, -- updates TRON to the latest version
["--gridID"] = "number" -- grid ID to use
}
local gridFore, gridBack
local gridList = {
[1] = { -- broken up and cool looking
{
"+- -+------",
"| | ",
" | ",
". | ",
"+------+- --",
"| | ",
"| ",
"| . ",
},
{
"+- -+--------",
"| | ",
" | ",
" | ",
" | ",
"| | ",
"+--------+- -",
"| | ",
"| ",
"| ",
"| ",
"| | ",
}
},
[2] = { -- flat diagonal sorta
{
" / ",
" / ",
" / ",
" / ",
"/__________"
},
{
" / ",
" / ",
" / ",
" / ",
" / ",
" / ",
" / ",
"/_______________"
}
},
[3] = { -- basic simple grid
{
"+-------",
"| ",
"| ",
"| ",
"| "
},
{
"+------------",
"| ",
"| ",
"| ",
"| ",
"| ",
"| ",
"| "
}
},
[4] = { -- diamond grid
{
" /\\ ",
" / \\ ",
" / \\ ",
"/ \\",
"\\ /",
" \\ / ",
" \\ / ",
" \\/ ",
},
{
" /\\ ",
" / \\ ",
" / \\ ",
" / \\ ",
" / \\ ",
"/ \\",
"\\ /",
" \\ / ",
" \\ / ",
" \\ / ",
" \\ / ",
" \\/ ",
}
},
[5] = { -- brick and mortar
{
"| ",
"| ",
"| ",
"| ",
"===========",
" | ",
" | ",
" | ",
" | ",
"===========",
},
{
"| ",
"| ",
"=======",
" | ",
" | ",
"=======",
},
},
[6] = { -- pain background
{
"@ ",
"@ ",
"@ ",
"@ ",
"@ ",
"@ ",
"@ ",
"@ ",
"@ ",
"@ ",
"@ ",
"@ ",
"@ ",
"@ ",
"@ ",
"@ ",
"@ ",
"@ ",
"@ ",
"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@SCREEN@MAX@",
},
{
"%%..",
"%%..",
"%%..",
"..%%",
"..%%",
"..%%"
},
},
[7] = { -- some
{
" "
},
{
"+-----------------------------------------------------",
"| Somebody once told me the world is gonna roll me ",
"| I ain't the sharpest tool in the shed / She was ",
"| looking kind of dumb with her finger and her thumb ",
"| In the shape of an \"L\" on her forehead / Well the ",
"| years start coming and they don't stop coming ",
"| Fed to the rules and I hit the ground running ",
"| Didn't make sense not to live for fun / Your brain ",
"| gets smart but your head gets dumb / So much to ",
"| do, so much to see / So what's wrong with taking ",
"| the back streets? / You'll never know if you don't ",
"| go / You'll never shine if you don't glow / Hey ",
"| now, you're an all-star, get your game on, go play ",
"| Hey now, you're a rock star, get the show on, get ",
"| paid / And all that glitters is gold / Only ",
"| shooting stars break the mold / It's a cool place ",
"| and they say it gets colder / You're bundled up ",
"| now, wait till you get older / But the meteor men ",
"| beg to differ / Judging by the hole in the ",
"| satellite picture / The ice we skate is getting ",
"| pretty thin / The water's getting warm so you might ",
"| as well swim / My world's on fire, how about yours? ",
"| That's the way I like it and I never get bored ",
"| Hey now, you're an all-star, get your game on, go ",
"| play / Hey now, you're a rock star, get the show ",
"| on, get paid / All that glitters is gold / Only ",
"| shooting stars break the mold / Hey now, you're ",
"| an all-star, get your game on, go play / Hey now, ",
"| you're a rock star, get the show, on get paid ",
"| And all that glitters is gold / Only shooting ",
"| stars... / Somebody once asked could I spare some ",
"| change for gas? / I need to get myself away from ",
"| this place / I said yep, what a concept / I could ",
"| use a little fuel myself / And we could all use a ",
"| little change / Well, the years start coming and ",
"| they don't stop coming / Fed to the rules and I ",
"| hit the ground running / Didn't make sense not to ",
"| live for fun / Your brain gets smart but your head ",
"| gets dumb / So much to do, so much to see / So ",
"| what's wrong with taking the back streets? ",
"| You'll never know if you don't go (go!) / You'll ",
"| never shine if you don't glow / Hey now, you're ",
"| an all-star, get your game on, go play / Hey now, ",
"| you're a rock star, get the show on, get paid ",
"| And all that glitters is gold / Only shooting ",
"| stars break the mold / And all that glitters is ",
"| gold / Only shooting stars break the mold ",
}
}
}
local argList = interpretArgs({...}, argData)
local useSkynet = argList["skynet"]
local useOnce = argList["quick"]
local doGridDemo = argList["griddemo"]
local doUpdateGame = argList["--update"]
if gridList[argList["--gridID"]] then
gridID = argList["--gridID"]
end
local argumentName = argList[1]
local argumentPassword = argList[2] or ""
if useSkynet and (not http.websocket) then
error("Skynet is not supported on this version of ComputerCraft.")
end
local skynetPath = fs.combine(fs.getDir(shell.getRunningProgram()), "skynet.lua")
local skynetURL = "https://github.com/LDDestroier/CC/raw/master/API/skynet.lua"
if argumentName then
argumentName = argumentName:sub(1, 15) -- gotta enforce that limit
end
local toblit = {
[0] = " ",
[colors.white] = "0",
[colors.orange] = "1",
[colors.magenta] = "2",
[colors.lightBlue] = "3",
[colors.yellow] = "4",
[colors.lime] = "5",
[colors.pink] = "6",
[colors.gray] = "7",
[colors.lightGray] = "8",
[colors.cyan] = "9",
[colors.purple] = "a",
[colors.blue] = "b",
[colors.brown] = "c",
[colors.green] = "d",
[colors.red] = "e",
[colors.black] = "f"
}
local tograyCol, tograyBlit = {
[0] = 0,
[colors.white] = colors.white,
[colors.orange] = colors.lightGray,
[colors.magenta] = colors.lightGray,
[colors.lightBlue] = colors.white,
[colors.yellow] = colors.white,
[colors.lime] = colors.lightGray,
[colors.pink] = colors.lightGray,
[colors.gray] = colors.gray,
[colors.lightGray] = colors.lightGray,
[colors.cyan] = colors.lightGray,
[colors.purple] = colors.gray,
[colors.blue] = colors.gray,
[colors.brown] = colors.gray,
[colors.green] = colors.gray,
[colors.red] = colors.white,
[colors.black] = colors.black
}, {}
local tocolors = {}
for k,v in pairs(toblit) do
tocolors[v] = k
end
for k,v in pairs(tograyCol) do
tograyBlit[toblit[k]] = toblit[v]
end
local termwrite, termclear = term.write, term.clear
local termsetCursorPos, termgetCursorPos = term.setCursorPos, term.getCursorPos
local tableunpack, tableremove = unpack, table.remove
local mathfloor, mathceil, mathcos, mathsin, mathrandom, mathrad = math.floor, math.ceil, math.cos, math.sin, math.random, math.rad
local termsetTextColor = function(col)
return term.setTextColor(isColor and col or tograyCol[col])
end
local termsetBackgroundColor = function(col)
return term.setBackgroundColor(isColor and col or tograyCol[col])
end
local termblit = function(char, text, back)
if isColor then
return term.blit(char, text, back)
else
return term.blit(
char,
text:gsub(".", tograyBlit),
back:gsub(".", tograyBlit)
)
end
end
local tsv = function(visible)
if term.current().setVisible and useSetVisible then
term.current().setVisible(visible)
end
end
local round = function(num, places)
return math.floor(num * 10^places + 0.5) / 10^places
end
if doUpdateGame then
print("Downloading...")
local net = http.get("https://github.com/LDDestroier/CC/raw/master/tron.lua")
if net then
local file = fs.open(shell.getRunningProgram(), "w")
file.write(net.readAll())
file.close()
print("Updated!")
else
printError("Couldn't update!")
end
if useOnce then
return
else
sleep(0.2)
shell.run( shell.getRunningProgram(), table.concat({...}, " "):gsub("--update", "") )
return
end
end
local cwrite = function(text, y, xdiff, wordPosCheck)
wordPosCheck = wordPosCheck or #text
termsetCursorPos(mathfloor(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 modem, skynet
local setUpModem = function()
if not doGridDemo then
if useSkynet then
if fs.exists(skynetPath) then
skynet = dofile(skynetPath)
term.clear()
cwrite("Connecting to Skynet...", scr_y / 2)
skynet.open(port)
else
term.clear()
cwrite("Downloading Skynet...", scr_y / 2)
local prog = http.get(skynetURL)
if prog then
local file = fs.open(skynetPath, "w")
file.write(prog.readAll())
file.close()
skynet = dofile(skynetPath)
cwrite("Connecting to Skynet...", 1 + scr_y / 2)
skynet.open(port)
else
error("Could not download Skynet.")
end
end
else
modem = peripheral.find("modem")
if (not modem) and ccemux then
ccemux.attach("top", "wireless_modem")
modem = peripheral.find("modem")
end
if modem then
modem.open(port)
else
error("You should attach a modem.")
end
end
end
end
setUpModem()
local transmit = function(port, message)
if useSkynet then
skynet.send(port, message)
else
modem.transmit(port, port, message)
end
end
local gamename = ""
local isHost
local waitingForGame = true
-- used in skynet matches if you are player 2
local ping = 0
local copyTable
copyTable = function(tbl, ...)
local output = {}
local arg = arg or {...}
for k,v in pairs(tbl) do
if type(v) == "table" then
output[k] = copyTable(v)
else
output[k] = v
end
end
for i = 1, #arg do
output[#output+1] = arg[i]
end
return output
end
grid = copyTable(initGrid)
local you, nou = 1, 2
local keysDown, netKeysDown = {}, {}
local miceDown = {}
local lastDirectionPressed, netLastDirectionPressed
-- the scrolling of the screen
local scrollX = 0
local scrollY = 0
-- used when panning with WASD
local scrollAdjX = 0
local scrollAdjY = 0
local lockInput = false
local player
player = resetPlayers()
local images
if _HOST then -- need to add some NFP image replacements for older versions of CC
images = {
logo = {
{
" \149\131\131\131\131\131\131\131\131\131\149\151\131\131\131\131\131\131\131\139\139 \135\135\131\131\131\139\139 \159\139 \149\131\131\149",
" \149\131\131\131\148\128\151\131\131\131\149\130\131\131\131\131\131\139\128\128\128\138 \151\128\159\131\131\131\144\128\148 \149\130\130\144 \149\128\128\149",
" \149\128\149 \130\130\131\131\129\149\128\128\151\128\128\128\148\128\128\149\149\128\128\139\139 \149\128\128\149",
" \149\128\149 \151\131\148\139\147\131\131\139\128\128\128\149\128\128\149\128\128\128\149\128\128\149\149\128\149\143\143\136\131\128\128\149",
" \149\128\149 \149\128\149 \130\139\128\128\139\144\128\138\144\128\139\143\143\143\135\128\159\133\149\128\149 \130\130\144\128\149",
" \149\128\149 \149\128\149 \139\144\128\130\139 \139\139\144\128\128\128\159\135\135 \149\128\149 \139\139\149",
" \143\143\143 \143\143\143 \138\143\143\143 \130\139\143\143\143\135\129 \143\143\143 \130\133",
},
{
" f7777777777777777777f f77777f 7f f777",
" f99979999979999999999f 799999799 77f7 f997",
" 799 79999f997 9977997f f997",
" 799 7797777fffff997 9977997797997",
" 799 799 799977f7797fff7997799 79797",
" 799 799 7797f 797999997 799 797",
" 777 777 7777 7777777 777 77",
},
{
" 7999999999f9999999997 7999997 97 799f",
" 7777997777f77777779997 997777997 997f 799f",
" 997 f7777799 799f99997 799f",
" 997 997f9997fff799 799f997ff7999f",
" 997 997 f7999fff999777997f997 f799f",
" 997 997 f9997 f7999977f 997 f7f",
" fff fff ffff fffffff fff ff",
}
},
win = {
{
"\128\149\128\128\128\128\128\128\128\149\149\128\128\128\128\128\128\128\128\138\128\128\128\128\149\128\128\128\149",
"\128\149\128\128\128\128\128\128\128\149\130\129\128\128\149\128\131\128\128\128\130\144\128\128\149\128\128\128\149",
"\128\149\128\128\135\144\128\128\128\149\128\128\128\128\149\128\128\128\128\149\139\128\139\128\149\128\128\128\149",
"\128\149\159\129\159\128\139\128\128\149\128\128\128\128\149\128\128\128\128\149\128\130\144\130\133\128\128\128\149",
"\128\130\128\135\128\130\144\130\128\149\159\144\128\128\149\128\143\128\128\149\128\128\128\139\128\128\128\143\144",
"\128\159\129\128\128\128\128\139\128\149\149\128\128\128\128\128\128\128\128\149\128\128\128\128\149\128\128\128\149",
},
{
"55 55 555555 5 5 55",
"55 5555 55 5 55 5 5 55",
"55 5 55 55 5555 5 55",
"55 55 55 55 55 5 5 55",
"5 55 5 55 5 55 55 555 5",
"555 555 555555 55 5 55",
},
{
"5 5 5555555 55 55 5 ",
"5 5 5 555 55 5 ",
"5 5 5 5 5 55 55 5 ",
"5 55 55 5 5 5 5555 5 ",
"555 555 5 5 5 5 55 5 ",
"5 5 5555555 5 55 5 ",
}
},
lose = {
{
"\128\149\128\128\128\128\128\128\159\129\128\128\128\130\144\128\129\128\128\128\128\128\130\128\128\128\128\128\128\128\128",
"\128\149\128\128\128\128\128\128\128\159\129\128\130\144\128\128\128\151\128\128\128\130\131\128\128\149\128\128\128\130\131",
"\128\149\128\128\128\128\128\128\128\149\128\128\128\149\128\128\128\128\131\131\131\131\139\128\128\130\131\131\131\148\128",
"\128\149\128\128\128\128\128\128\128\149\128\128\128\149\128\128\130\131\131\131\131\144\128\128\128\151\131\131\131\129\128",
"\128\149\128\128\128\128\128\128\128\130\144\128\159\129\128\128\143\144\128\128\128\133\128\128\128\149\128\128\128\159\143",
"\128\128\128\128\128\128\128\128\130\144\128\128\128\159\129\128\144\128\128\128\128\128\159\128\128\128\128\128\128\128\128",
},
{
"ee eee e eeeee eeeeeee",
"ee eee e e ee ee ee ee",
"ee ee e ee e e ",
"ee ee e eeeee e eeeeee ",
"ee e e e e e ee ",
"eeeeeee e eeeee eeeeee eeeeeee",
},
{
"e eeeeee eeeeeee eeeeeee",
"e e ee e e ",
"e e ee eeeeeee eeeee ",
"e e ee ee e ",
"e ee eee e ee e ee",
"eeeeeee eeee eeeeee eeeeeee",
}
},
tie = {
{
"\128\128\128\128\128\128\128\149\149\128\128\128\128\128\128\128\128\128\128\128\128\128\128\128",
"\128\128\128\128\149\128\128\128\130\129\128\149\128\128\128\131\128\128\149\128\128\128\128\131",
"\128\128\128\128\149\128\128\128\128\128\128\149\128\128\128\128\128\128\130\131\131\131\148\128",
"\128\128\128\128\149\128\128\128\128\128\128\149\128\128\128\128\128\128\151\131\131\131\129\128",
"\128\128\128\128\149\128\128\128\159\144\128\149\128\128\128\143\128\128\149\128\128\128\128\143",
"\128\128\128\128\149\128\128\128\149\128\128\128\128\128\128\128\128\128\128\128\128\128\128\128",
},
{
"77888800 0000000 0888877",
" 88 00 0 0 08 7",
" 88 0 0 7 ",
" 88 0 088887 ",
" 88 0 0 08 ",
" 88 0000000 0888877",
},
{
"7788880 00000000 0888877",
" 8 00 0 ",
" 8 00 08888 ",
" 8 00 0 ",
" 8 0 00 0 0 7",
" 8 00000000 0888877",
},
},
timeout = {
{
"\151\131\131\131\131\149\151\131\131\131\131\149\151\131\155\159\134\131\149\151\131\131\131\148",
"\141\147\128\151\140\133\141\147\128\151\140\133\149\128\128\129\128\128\149\149\128\140\140\158",
" \149\128\149\128\128\143\133\128\149\143\144\149\128\157\152\149\128\149\149\128\136\140\142",
" \149\128\149\128\128\149\128\128\128\128\149\149\128\149\128\149\128\149\149\128\128\128\149",
" \130\131\131\128\128\131\131\131\131\131\129\131\131\129\128\131\131\129\131\131\131\131\131",
" \151\131\131\131\131\149\149\131\148\149\131\148\149\131\131\131\131\148",
" \149\128\156\148\128\149\149\128\149\149\128\149\138\140\148\128\156\142",
" \149\128\138\149\128\149\149\128\149\133\128\149 \128\149\128\149",
" \149\128\128\128\128\149\149\128\128\128\128\149 \128\149\128\149",
" \131\131\131\131\131\129\130\131\131\131\131\131\128\128\131\131\129",
},
{
"00000000000000ff0000000f",
"0fff000fff000ff0ff00f000",
"0ffffffffff00f000f00ffff",
" fffff0ffff00f0f0f00ffff",
" 000ff000000000f00000000",
" 000000f0ff0ff0000f",
" 0f00f0ffffff000f00",
" 0ff0f0ffffff7f0f0",
" 0ffff0ffffff7f0f0",
" 000000000000ff000",
},
{
"ffffffffffffff00fffffff0",
" 0f0fff0f0ffffffffffffff",
" 0f0ff00f00ffffffffff000",
" 0f0fffffffffffffffffff0",
" fffffffffffffffffffffff",
" ffffff0f00f00ffff0",
" ffffff0f00f0ffffff",
" ff0fff0f00f0fffff",
" ffffff0ffff0fffff",
" fffffffffffffffff",
},
},
ldd = {
{
" ",
" \131\140\139\151\148\151\148 \143 \151\156\147\144\128\131\130\149\136\140\129\135\140\140\159\143\143\144\143\143\144\159\156\147\144\131\128\131\149\136\140\129\131\140\139",
" \128\131\130 \148\151 \128 \149\149\149\149\128\143\159\149\138\143\144\141\131\130 \149\149 \128\140\136\149\149\149\149\143\128\143\149\138\143\144\128\131\130",
" \131\131\129 \130\129 \143\140\140\130\131\131 \130\131\129 \138\133 \143 \143 \131\131 \131 \131",
},
{
" ",
" f7ff7f7 f fbfbbbffff9f99fff9ff9f9f9999fff9f9f",
" 77f f7 b fbfbbfbfff9f9f f9 99ff9f9f9ffff999f",
" 777 77 bbbbbb 999 99 9 9 99 9 9",
},
{
" ",
" 7f77f7f b bfbfbfb999f9ff999f99f9f9ff9f999f9f9",
" 7f7 7f b bfbfbbf999f9f9 9f 9f99f9f999999f9f9",
" fff ff ffffff fff ff f f ff f f",
},
}
}
else
images = {
logo = {
{
" ",
" ",
" ",
" ",
" ",
" ",
" ",
},
{
"7777777777 77777777 77777777 77 77",
" 77 777 777 777 777 77",
" 77 777 77 77 7777 77",
" 77 7777777 77 77 77777 77",
" 77 77 7777 77 77 77 7777",
" 77 77 7777 777 777 77 777",
" 77 77 7777 77777777 77 77",
},
{
"9999999999 99999999 99999999 99 99",
" 99 999 999 999 999 99",
" 99 999 99 99 9999 99",
" 99 9999999 99 99 99999 99",
" 99 99 9999 99 99 99 9999",
" 99 99 9999 999 999 99 999",
" 99 99 9999 99999999 99 99",
},
},
win = {
{
" ",
" ",
" ",
" ",
" ",
" ",
" ",
},
{
"77 77 777777 77 77 77",
"77 77 777777 777 77 77",
"77 7 77 77 7777 77 77",
"77 777 77 77 77 77 77 77",
"7777 7777 77 77 7777 ",
"777 777 777777 77 777 77",
"77 77 777777 77 77 77",
},
{
"55 55 555555 55 55 55",
"55 55 555555 555 55 55",
"55 5 55 55 5555 55 55",
"55 555 55 55 55 55 55 55",
"5555 5555 55 55 5555 ",
"555 555 555555 55 555 55",
"55 55 555555 55 55 55",
},
},
lose = {
{
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
},
{
"77 777777 77777 77777",
"77 77777777 777777 77777",
"77 777 777 77 77 ",
"77 77 77 77777 7777 ",
"77 77 77 77777 77 ",
"77 777 777 77 77 ",
"77777 77777777 777777 77777",
"77777 777777 77777 77777",
},
{
"ee eeeeee eeeee eeeee",
"ee eeeeeeee eeeeee eeeee",
"ee eee eee ee ee ",
"ee ee ee eeeee eeee ",
"ee ee ee eeeee ee ",
"ee eee eee ee ee ",
"eeeee eeeeeeee eeeeee eeeee",
"eeeee eeeeee eeeee eeeee",
},
},
tie = {
{
" ",
" ",
" ",
" ",
" ",
" ",
" ",
},
{
"77777777 77777777 7777777",
" 77 77 77 ",
" 77 77 77 ",
" 77 77 777777 ",
" 77 77 77 ",
" 77 77 77 ",
" 77 77777777 7777777",
},
{
"77888800 00000000 0888877",
" 88 00 08 ",
" 88 00 08 ",
" 88 00 088887 ",
" 88 00 08 ",
" 88 00 08 ",
" 88 00000000 0888877",
},
},
timeout = {
{
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
},
{
"7777777 777 777 777 777777",
"7777777 777 7777 7777 777777",
"7777777 777 777777777 777777",
" 777 777 777777777 77777 ",
" 777 777 777777777 777777",
" 777 777 777777777 777777",
" 777 777 777 777 777777",
" ",
" 7777777 777 777 7777777 ",
" 7777777 777 777 7777777 ",
" 7777777 777 777 7777777 ",
" 777 777 777 777 777 ",
" 777 777 777 777 777 ",
" 7777777 7777777 777 ",
" 7777777 7777777 777 ",
" 7777777 7777777 777 ",
},
{
"0000000 000 000 000 000000",
"0fffff0 0f0 0ff0 0ff0 0ffff0",
"000f000 0f0 0fff0fff0 0f0000",
" 0f0 0f0 0f0fff0f0 0fff0 ",
" 0f0 0f0 0f00f00f0 0f0000",
" 0f0 0f0 0f00000f0 0ffff0",
" 000 000 000 000 000000",
" ",
" 0000000 000 000 0000000 ",
" 0fffff0 0f0 0f0 0fffff0 ",
" 0f000f0 0f0 0f0 000f000 ",
" 0f0 0f0 0f0 0f0 0f0 ",
" 0f0 0f0 0f0 0f0 0f0 ",
" 0f000f0 0f000f0 0f0 ",
" 0fffff0 0fffff0 0f0 ",
" 0000000 0000000 000 ",
},
},
ldd = {
{
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
},
{
" 777 7 7 ",
" 7 7 7 7 ",
" 77 7 ",
" 7 7 7 ",
" 77 77 777 7 777 777 ",
"7 7 7 7 7 77 777 7 7 7 777 ",
"7 7 7 7 7 7 777 777 7 7 7 7 7 7 777",
"7 7 7 7 7 77 7 7 77 7 7 7 77 7 7",
"7 77 77 7 777 7 7 7 777 777 7 77 ",
"777 77 7 7 7 7 777 7 7",
" 777 7 7 7",
},
{
" 777 7 7 ",
" 7 7 7 7 ",
" 77 7 ",
" 7 7 7 ",
" bb bb 777 7 999 999 ",
"b b b b b 99 999 9 9 9 999 ",
"b b b b b 9 999 999 9 9 9 9 9 9 999",
"b b b b b 99 9 9 99 9 9 9 99 9 9",
"b bb bb 9 999 9 9 9 999 999 9 99 ",
"bbb 99 9 9 9 9 999 9 9",
" 999 9 9 9",
},
}
}
end
for k,v in pairs(images) do
-- give them easy-to-access x and y sizes
v.x = #v[1][1]
v.y = #v[1]
-- fix white artifacting that occurs due to " " correlating to WHITE in term.blit
for y = 1, v.y do
for x = 1, v.x do
if v[2][y]:sub(x,x) ~= "" and v[3][y]:sub(x,x) ~= "" then
if (v[2][y]:sub(x,x) == " " and v[3][y]:sub(x,x) ~= " ") then
images[k][2][y] = v[2][y]:sub(1, x - 1) .. initGrid.voidcol .. v[2][y]:sub(x + 1)
elseif (v[2][y]:sub(x,x) ~= " " and v[3][y]:sub(x,x) == " ") then
images[k][3][y] = v[3][y]:sub(1, x - 1) .. initGrid.voidcol .. v[3][y]:sub(x + 1)
end
end
end
end
end
local drawImage = function(im, x, y)
local cx, cy = termgetCursorPos()
termsetBackgroundColor( tocolors[initGrid.voidcol] )
termsetTextColor( tocolors[initGrid.voidcol] )
for iy = 1, #im[1] do
for ix = 1, #im[1][iy] do
termsetCursorPos(x+(ix-1),y+(iy-1))
if not (im[2][iy]:sub(ix,ix) == " " and im[3][iy]:sub(ix,ix) == " ") then
termblit(
im[1][iy]:sub(ix,ix),
im[2][iy]:sub(ix,ix):gsub("[ f]",initGrid.voidcol),
im[3][iy]:sub(ix,ix):gsub("[ f]",initGrid.voidcol)
)
end
end
end
termsetCursorPos(cx,cy)
end
local deadGuys = {}
local trail = {}
local lastTrails = {}
isPuttingDown = false
local putTrailXY = function(x, y, p)
trail[y] = trail[y] or {}
trail[y][x] = {
player = p,
age = 0
}
end
local putTrail = function(p)
putTrailXY(p.x, p.y, p.num)
end
local getTrail = function(x, y)
if trail[y] then
if trail[y][x] then
return player[trail[y][x].player].char, player[trail[y][x].player].color, trail[y][x].age
end
end
return false
end
local ageTrails = function()
for y,l in pairs(trail) do
for x,v in pairs(l) do
trail[y][x].age = trail[y][x].age + 1
end
end
end
local control, revControl = {
up = keys.up,
down = keys.down,
left = keys.left,
right = keys.right,
lookUp = keys.w,
lookDown = keys.s,
lookLeft = keys.a,
lookRight = keys.d,
release = keys.space
}, {}
for k,v in pairs(control) do
revControl[v] = k
end
gridFore, gridBack = table.unpack(gridList[gridID])
local dirArrow = {
[-1] = "^",
[0] = ">",
[1] = "V",
[2] = "<"
}
local doesIntersectBorder = function(x, y)
return mathfloor(x) == grid.x1 or mathfloor(x) == grid.x2 or mathfloor(y) == grid.y1 or mathfloor(y) == grid.y2
end
--draws grid and background at scroll 'x' and 'y', along with trails and players
local drawGrid = function(x, y, onlyDrawGrid, useSetVisible)
tsv(false)
x, y = mathfloor(x + 0.5), mathfloor(y + 0.5)
local bg = {{},{},{}}
local foreX, foreY
local backX, backY
local adjX, adjY
local trailChar, trailColor, trailAge, isPlayer, isPredict
for sy = 1, scr_y do
bg[1][sy] = ""
bg[2][sy] = ""
bg[3][sy] = ""
for sx = 1, scr_x do
adjX = (sx + x)
adjY = (sy + y)
foreX = 1 + (sx + x) % #gridFore[1]
foreY = 1 + (sy + y) % #gridFore
backX = 1 + mathfloor(sx + (x / 2)) % #gridBack[1]
backY = 1 + mathfloor(sy + (y / 2)) % #gridBack
trailChar, trailColor, trailAge = getTrail(adjX, adjY)
isPlayer = false
isPredict = false
if not onlyDrawGrid then
for i = 1, #player do
if player[i].x == adjX and player[i].y == adjY then
isPlayer = i
break
elseif (not isHost) and useSkynet and i == you and (
adjX == math.floor(player[i].x + (0.02 * round(ping, 0)) * math.cos(math.rad(player[i].direction * 90))) and
adjY == math.floor(player[i].y + (0.02 * round(ping, 0)) * math.sin(math.rad(player[i].direction * 90)))
) then
isPredict = i
break
end
end
end
if isPlayer and (not onlyDrawGrid) and (not doesIntersectBorder(adjX, adjY)) then
bg[1][sy] = bg[1][sy] .. dirArrow[player[isPlayer].direction]
bg[2][sy] = bg[2][sy] .. toblit[player[isPlayer].color[1]]
bg[3][sy] = bg[3][sy] .. grid.voidcol
elseif isPredict and (not onlyDrawGrid) and (not doesIntersectBorder(adjX, adjY)) then
bg[1][sy] = bg[1][sy] .. "o"
bg[2][sy] = bg[2][sy] .. grid.forecol
bg[3][sy] = bg[3][sy] .. grid.voidcol
else
if (not onlyDrawGrid) and trailChar and trailColor then
trailColor = trailColor[1 + ((trailAge - 1) % #trailColor)]
bg[1][sy] = bg[1][sy] .. trailChar
bg[2][sy] = bg[2][sy] .. toblit[trailColor]
bg[3][sy] = bg[3][sy] .. grid.voidcol
else
if (not onlyDrawGrid) and (adjX < grid.x1 or adjX > grid.x2 or adjY < grid.y1 or adjY > grid.y2) then
bg[1][sy] = bg[1][sy] .. " "
bg[2][sy] = bg[2][sy] .. grid.voidcol
bg[3][sy] = bg[3][sy] .. grid.voidcol
elseif (not onlyDrawGrid) and doesIntersectBorder(adjX, adjY) then
bg[1][sy] = bg[1][sy] .. grid.border
bg[2][sy] = bg[2][sy] .. grid.voidcol
bg[3][sy] = bg[3][sy] .. grid.edgecol
else
if gridFore[foreY]:sub(foreX,foreX) ~= " " then
bg[1][sy] = bg[1][sy] .. gridFore[foreY]:sub(foreX,foreX)
bg[2][sy] = bg[2][sy] .. grid.forecol
bg[3][sy] = bg[3][sy] .. grid.voidcol
elseif gridBack[backY]:sub(backX,backX) ~= " " then
bg[1][sy] = bg[1][sy] .. gridBack[backY]:sub(backX,backX)
bg[2][sy] = bg[2][sy] .. grid.backcol
bg[3][sy] = bg[3][sy] .. grid.voidcol
else
bg[1][sy] = bg[1][sy] .. " "
bg[2][sy] = bg[2][sy] .. grid.voidcol
bg[3][sy] = bg[3][sy] .. grid.voidcol
end
end
end
end
end
end
for sy = 1, scr_y do
termsetCursorPos(1,sy)
termblit(
bg[1][sy],
bg[2][sy],
bg[3][sy]
)
end
if doDrawPlayerNames and (not onlyDrawGrid) then
for i = 1, #player do
if doRenderOwnName or (i ~= you) then
termsetTextColor(player[i].color[1])
adjX = mathfloor(player[i].x - (scrollX + scrollAdjX) - (#player[i].name / 2) + 1)
adjY = mathfloor(player[i].y - (scrollY + scrollAdjY) - 1.5)
for cx = adjX, adjX + #player[i].name do
if doesIntersectBorder(adjX + mathfloor(0.5 + scrollX + scrollAdjX), adjY + mathfloor(0.5 + scrollY + scrollAdjY)) then
termsetBackgroundColor(tocolors[grid.edgecol])
else
termsetBackgroundColor(tocolors[grid.voidcol])
end
termsetCursorPos(cx, adjY)
termwrite(player[i].name:sub(cx-adjX+1, cx-adjX+1))
end
end
end
end
tsv(true)
end
local getTime = function()
if os.epoch then
return os.epoch("utc")
else
return 24 * os.day() + os.time()
end
end
local render = function(useSetVisible, netTime)
local p = player[you]
drawGrid(scrollX + scrollAdjX, scrollY + scrollAdjY, false, useSetVisible)
termsetCursorPos(1,1)
termsetTextColor(player[you].color[1])
termsetBackgroundColor(tocolors[grid.voidcol])
term.write("P" .. you)
term.setTextColor(colors.white)
for x = 0, p.trailMax - 1 do
if not (x - p.trailLevel >= -0.4) then
if (x - p.trailLevel) > -0.7 then
term.setTextColor(colors.gray)
term.write("@")
elseif (x - p.trailLevel) > -1 then
term.setTextColor(colors.lightGray)
term.write("@")
else
term.setTextColor(colors.white)
term.write("@")
end
end
end
term.setCursorPos(1,2)
if netTime and useSkynet then
ping = (getTime() - netTime)
term.setTextColor(colors.white)
term.write(" " .. tostring(ping) .. " ms")
end
term.setTextColor(colors.white)
end
local pleaseWait = function()
local periods = 1
local maxPeriods = 5
termsetBackgroundColor(colors.black)
termsetTextColor(colors.gray)
termclear()
local tID = os.startTimer(0.2)
local evt, txt
if 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)
termwrite(("."):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 startCountdown = function()
local cName = "PLAYER " .. you
local col = colors.white
for k,v in pairs(colors) do
if player[you].color[1] == v then
cName = k:upper()
col = v
break
end
end
local cMessage = "You are "
scrollX = player[you].x - mathfloor(scr_x / 2)
scrollY = player[you].y - mathfloor(scr_y / 2)
for i = 3, 1, -1 do
render(true)
termsetTextColor(colors.white)
for x = 1, #cMessage+1 do
termsetCursorPos(-1 + x + mathfloor(scr_x / 2 - (#cMessage + #cName) / 2), mathfloor(scr_y / 2) + 2)
if cMessage:sub(x,x) ~= " " and x <= #cMessage then
termwrite(cMessage:sub(x,x))
end
end
termsetTextColor(col)
termwrite(player[you].name)
termsetTextColor(colors.white)
termsetCursorPos(mathfloor(scr_x / 2 - 2), mathfloor(scr_y / 2) + 4)
termwrite(i .. "...")
sleep(1)
end
end
local makeMenu = function(x, fromX, y, options, doAnimate, scrollInfo, _cpos)
local cpos = _cpos or 1
local xmod = 0
local cursor = "> "
local gsX, gsY = (scrollInfo or {})[2] or 0, (scrollInfo or {})[3] or 0
local step = (scrollInfo or {})[1] or 0
local lastPos = cpos
local image
if not doAnimate then
drawImage(images.logo, mathceil(scr_x / 2 - images.logo.x / 2), 2)
if useSkynet then
term.setTextColor(colors.lightGray)
cwrite("Skynet Enabled", 2 + images.logo.y)
end
end
local rend = function()
if (step % 150 > 100) and doShowByImage then
image = images.ldd
else
image = images.logo
end
if doAnimate then
drawImage(
image,
mathceil(scr_x / 2 - image.x / 2),
2
)
if useSkynet then
term.setTextColor(colors.lightGray)
cwrite("Skynet Enabled", 2 + image.y)
end
end
for i = 1, #options do
if i == cpos then
termsetCursorPos(fromX + xmod, y + (i - 1))
termsetTextColor(colors.white)
termwrite(cursor .. options[i])
else
if i == lastPos then
termsetCursorPos(fromX + xmod, y + (i - 1))
termwrite((" "):rep(#cursor))
lastPos = nil
else
termsetCursorPos(fromX + xmod + #cursor, y + (i - 1))
end
termsetTextColor(colors.gray)
termwrite(options[i])
end
end
end
rend()
local tID = os.startTimer(0.05)
while true do
evt = {os.pullEvent()}
if evt[1] == "key" then
if evt[2] == keys.up then
lastPos = cpos
cpos = (cpos - 2) % #options + 1
elseif evt[2] == keys.down then
lastPos = cpos
cpos = (cpos % #options) + 1
elseif evt[2] == keys.home then
lastPos = cpos
cpos = 1
elseif evt[2] == keys["end"] then
lastPos = cpos
cpos = #options
elseif evt[2] == keys.enter then
return cpos, {step, gsX, gsY}
end
elseif evt[1] == "mouse_click" then
if evt[4] >= y and evt[4] < y+#options then
if cpos == evt[4] - (y - 1) then
return cpos, {step, gsX, gsY}
else
cpos = evt[4] - (y - 1)
doRend = true
end
end
elseif evt[1] == "timer" then
if evt[2] == tID then
tID = os.startTimer(0.05)
drawGrid(gsX, gsY, true)
step = step + 1
if mathceil(step / 100) % 2 == 1 then
gsX = gsX + 1
else
gsY = gsY - 1
end
if x > fromX and xmod < x - fromX then
xmod = math.min(xmod + 1, x - fromX)
elseif xmod > x - fromX then
xmod = math.max(xmod - 1, x - fromX)
end
doRend = true
end
end
if lastPos ~= cpos or doRend then
rend()
doRend = false
end
end
end
local specialRead = function(scrollInfo, specialNames, message, preInput)
specialNames = specialNames or {}
local gsX, gsY = (scrollInfo or {})[2] or 0, (scrollInfo or {})[3] or 0
local step = (scrollInfo or {})[1] or 0
local tID = os.startTimer(0.05)
local buff = {}
local cpos = 1
local maxSize = 15
local evt
for x = 1, #preInput do
buff[x] = preInput:sub(x, x)
cpos = cpos + 1
end
term.setCursorBlink(true)
local rend = function()
drawGrid(gsX, gsY, true)
term.setTextColor(colors.white)
cwrite(message, scr_y - 5)
termsetTextColor(specialNames[table.concat(buff):lower()] or colors.white)
term.setCursorPos( cwrite(table.concat(buff), scr_y - 3, nil, cpos) - 1, scr_y - 3)
term.setTextColor(colors.white)
end
while true do
evt = {os.pullEvent()}
if evt[1] == "timer" and evt[2] == tID then
-- render the bg
tID = os.startTimer(0.05)
step = step + 1
if mathceil(step / 100) % 2 == 1 then
gsX = gsX + 1
else
gsY = gsY - 1
end
rend()
elseif evt[1] == "char" then
if #buff < maxSize then
table.insert(buff, cpos, evt[2])
cpos = cpos + 1
rend()
end
elseif evt[1] == "key" then
if evt[2] == keys.left then
cpos = math.max(1, cpos - 1)
elseif evt[2] == keys.right then
cpos = math.min(#buff + 1, cpos + 1)
elseif evt[2] == keys.home then
cpos = 1
elseif evt[2] == keys["end"] then
cpos = #buff
elseif evt[2] == keys.backspace then
if cpos > 1 then
table.remove(buff, cpos - 1)
cpos = cpos - 1
rend()
end
elseif evt[2] == keys.delete then
if buff[cpos] then
table.remove(buff, cpos)
rend()
end
elseif evt[2] == keys.enter then
term.setCursorBlink(false)
return table.concat(buff), {step, gsX, gsY}
end
end
end
end
local passwordChange = function(scrollInfo)
return specialRead(scrollInfo, {}, "Enter a password.", argumentPassword or "") or ""
end
local nameChange = function(scrollInfo)
-- this has no functional significance. just some shoutouts
local specialNames = {
["blu"] = colors.blue,
["red"] = colors.red,
["ldd"] = colors.orange,
["lddestroier"] = colors.orange,
["hydraz"] = colors.yellow,
["hugeblank"] = colors.orange,
["bagel"] = colors.orange,
["3d6"] = colors.lime,
["lyqyd"] = colors.red,
["squiddev"] = colors.cyan,
["oeed"] = colors.lime,
["dog"] = colors.purple,
["nothy"] = colors.lightGray,
["kepler"] = colors.cyan,
["kepler155c"] = colors.cyan,
["anavrins"] = colors.blue,
["redmatters"] = colors.red,
["fatmanchummy"] = colors.purple,
["crazed"] = colors.lightBlue,
["ape"] = colors.brown,
["everyos"] = colors.red,
["lemmmy"] = colors.red,
["yemmel"] = colors.red,
["apemanzilla"] = colors.brown,
["osmarks"] = colors.green,
["gollark"] = colors.green,
["dece"] = colors.cyan,
["hpwebcamable"] = colors.lightGray,
["theoriginalbit"] = colors.blue,
["bombbloke"] = colors.red,
["kingofgamesyami"] = colors.lightBlue,
["pixeltoast"] = colors.lime,
["creator"] = colors.yellow,
["dannysmc"] = colors.purple,
["dannysmc95"] = colors.purple,
["kingdaro"] = colors.blue,
["valithor"] = colors.orange,
["logandark"] = colors.lightGray,
["lupus590"] = colors.lightGray,
["nitrogenfingers"] = colors.green,
["gravityscore"] = colors.lime,
["1lann"] = colors.gray,
["konlab"] = colors.brown,
["elvishjerricco"] = colors.pink
}
return specialRead(scrollInfo, specialNames, "Enter your name.", argumentName or player[you].initName)
end
local titleScreen = function()
termclear()
local menuOptions, options, choice, scrollInfo
if kioskMode then
menuOptions = {
"Start Game",
"How to Play",
}
else
menuOptions = {
"Start Game",
"How to Play",
"Options...",
"Exit"
}
end
local currentX = 2
while true do
choice, scrollInfo = makeMenu(2, currentX, scr_y - #menuOptions, menuOptions, true, scrollInfo)
currentX = 2
if choice == 1 then
return "start"
elseif choice == 2 then
return "help"
elseif choice == 3 then
local _cpos
while true do
options = {
"Grid Demo",
"Change Name",
"Change Grid",
"Change Password",
(useSkynet and "Disable" or "Enable") .. " Skynet",
"Back..."
}
choice, scrollInfo = makeMenu(8, currentX, scr_y - #options, options, true, scrollInfo, _cpos)
currentX = 8
_cpos = choice
if choice == 1 then
return "demo"
elseif choice == 2 then
local newName = nameChange(scrollInfo)
if #newName > 0 then
if newName:upper() == "BLU" or newName:upper() == "RED" or newName:gsub(" ","") == "" then
argumentName = nil
else
argumentName = newName
end
else
argumentName = nil
end
elseif choice == 3 then
gridID = (gridID % #gridList) + 1
gridFore, gridBack = table.unpack(gridList[gridID])
elseif choice == 4 then
argumentPassword = passwordChange(scrollInfo)
elseif choice == 5 then
if http.websocket then
useSkynet = not useSkynet
setUpModem()
if skynet and not useSkynet then
skynet.socket.close()
end
else
term.clear()
term.setTextColor(colors.white)
cwrite("Alas, this version of CC", -2 + scr_y / 2)
cwrite("does not support Skynet.", -1 + scr_y / 2)
term.setTextColor(colors.lightGray)
cwrite("Use CC:Tweaked or CCEmuX", 1 + scr_y / 2)
cwrite("instead for netplay.", 2 + scr_y / 2)
cwrite("Press any key to go back.", 4 + scr_y / 2)
sleep(0.1)
os.pullEvent("key")
end
elseif choice == 6 then
break
end
end
elseif choice == 4 then
return "exit"
end
end
end
local cleanExit = function()
termsetBackgroundColor(colors.black)
termsetTextColor(colors.white)
termclear()
cwrite("Thanks for playing!", 2)
termsetCursorPos(1, scr_y)
end
local parseMouseInput = function(button, x, y, direction)
local output = false
local cx = x - scr_mx
local cy = y - scr_my
if useLegacyMouseControl or mode == "demo" then -- outdated mouse input, useful for grid demo though
cx = cx * (scr_y / scr_x)
if cx > cy then
if -cx > cy then
output = "up"
else
output = "right"
end
else
if -cx < cy then
output = "down"
else
output = "left"
end
end
else
cx = cx + scrollAdjX
cy = cy + scrollAdjY
if button == 1 then -- move player
if direction % 2 == 0 then -- moving horizontally
if cy > 0 then
output = "down"
elseif cy < 0 then
output = "up"
end
else -- moving vertically
if cx > 0 then
output = "right"
elseif cx < 0 then
output = "left"
end
end
elseif button == 2 then -- release trail
output = "release"
end
end
return control[output]
end
local getInput = function()
local evt
local mkey = -1
while true do
evt = {os.pullEvent()}
if lockInput then
keysDown = {}
miceDown = {}
else
if evt[1] == "key" then
if (not keysDown[evt[2]]) and (
evt[2] == control.up or
evt[2] == control.down or
evt[2] == control.left or
evt[2] == control.right
) then
lastDirectionPressed = revControl[evt[2]]
end
keysDown[evt[2]] = true
elseif evt[1] == "key_up" then
keysDown[evt[2]] = false
elseif evt[1] == "mouse_click" or (useLegacyMouseControl and evt[1] == "mouse_drag") then
if evt[1] == "mouse_drag" then
keysDown[mkey] = false
end
miceDown[evt[2]] = {evt[3], evt[4]}
mkey = parseMouseInput(evt[2], evt[3], evt[4], player[you].direction) or -1
lastDirectionPressed = revControl[mkey]
keysDown[mkey] = true
elseif evt[1] == "mouse_drag" then
miceDown[evt[2]] = {evt[3], evt[4]}
elseif evt[1] == "mouse_up" then
keysDown[mkey] = false
miceDown[evt[2]] = nil
mkey = parseMouseInput(evt[2], evt[3], evt[4], player[you].direction) or -1
keysDown[mkey] = false
end
end
end
end
local scrollToPosition = function(x, y)
for i = 1, 16 do
scrollX = (scrollX + x - (scr_x/2)) / 2
scrollY = (scrollY + y - (scr_y/2)) / 2
render(true)
sleep(0.05)
end
end
local gridDemo = function()
keysDown = {}
miceDown = {}
scrollX, scrollY = math.floor(scr_x * -0.5), math.floor(scr_y * -0.75)
while true do
if keysDown[keys.left] then
scrollX = scrollX - 1
end
if keysDown[keys.right] then
scrollX = scrollX + 1
end
if keysDown[keys.up] then
scrollY = scrollY - 1
end
if keysDown[keys.down] then
scrollY = scrollY + 1
end
if keysDown[keys.q] then
return "end"
end
drawGrid(scrollX, scrollY, false, true)
ageTrails()
sleep(0.05)
end
end
local sendInfo = function(gameID, doSendTime)
transmit(port, {
player = isHost and player or nil,
name = player[you].name,
putTrail = isPuttingDown,
gameID = gameID,
time = doSendTime and getTime(),
keysDown = isHost and nil or keysDown,
trail = isHost and lastTrails or nil,
deadGuys = isHost and deadGuys or nil,
lastDir = lastDirectionPressed
})
end
local waitForKey = function(time, blockMouse)
sleep(time or 0.5)
local evt
repeat
evt = os.pullEvent()
until evt == "key" or ((not blockMouse) and evt == "mouse_click")
end
local imageAnim = function(image)
while true do
drawImage(image, mathceil(scr_x / 2 - image.x / 2), mathfloor(scr_y / 2 - image.y / 2))
sleep(0.5)
render(true)
sleep(0.5)
end
end
local deadAnimation = function(doSend)
for k,v in pairs(deadGuys) do
player[k].char = "X"
lockInput = true
end
if doSend then
sendInfo(gamename, isHost)
end
if deadGuys[you] or deadGuys[nou] then
termsetTextColor(colors.white)
if deadGuys[you] and deadGuys[nou] then
os.queueEvent("tron_complete", "tie", isHost, player[nou].name)
scrollToPosition(player[nou].x, player[nou].y)
scrollToPosition(player[you].x, player[you].y)
parallel.waitForAny(function() imageAnim(images.tie) end, waitForKey)
return "end"
else
if deadGuys[you] then
scrollX, scrollY = player[nou].x - scr_x / 2, player[nou].y - scr_y / 2
os.queueEvent("tron_complete", "lose", isHost, player[nou].name)
scrollToPosition(player[you].x, player[you].y)
parallel.waitForAny(function() imageAnim(images.lose) end, waitForKey)
return "end"
elseif deadGuys[nou] then
os.queueEvent("tron_complete", "win", isHost, player[nou].name)
scrollToPosition(player[nou].x, player[nou].y)
parallel.waitForAny(function() imageAnim(images.win) end, waitForKey)
return "end"
end
end
end
end
local debugMoveMode = false -- only works if host
local moveTick = function(doSend)
local p
local hasMoved
for i = 1, #player do
p = player[i]
hasMoved = false
if not p.dead then
if isHost then
if debugMoveMode then
if (i == 1 and keysDown[control.left]) or (i == 2 and netKeysDown[control.left]) then
p.x = p.x - 1
hasMoved = true
end
if (i == 1 and keysDown[control.right]) or (i == 2 and netKeysDown[control.right]) then
p.x = p.x + 1
hasMoved = true
end
if (i == 1 and keysDown[control.up]) or (i == 2 and netKeysDown[control.up]) then
p.y = p.y - 1
hasMoved = true
end
if (i == 1 and keysDown[control.down]) or (i == 2 and netKeysDown[control.down]) then
p.y = p.y + 1
hasMoved = true
end
else
p.x = p.x + mathfloor(mathcos(mathrad(p.direction * 90)))
p.y = p.y + mathfloor(mathsin(mathrad(p.direction * 90)))
hasMoved = true
end
if hasMoved and (doesIntersectBorder(p.x, p.y) or getTrail(p.x, p.y)) then
p.dead = true
deadGuys[i] = true
else
if p.putTrail or (p.trailLevel < 1) then
if hasMoved then
putTrail(p)
lastTrails[#lastTrails+1] = {p.x, p.y, p.num}
if #lastTrails > #player then
tableremove(lastTrails, 1)
end
end
if p.putTrail then
p.trailLevel = math.min(p.trailLevel + p.trailRegen, p.trailMax)
else
p.trailLevel = math.max(p.trailLevel - 1, 0)
end
else
p.trailLevel = math.max(p.trailLevel - 1, 0)
end
end
end
for a = 1, #player do
if (a ~= i) and (player[a].x == p.x and player[a].y == p.y) then
p.dead = true
deadGuys[i] = true
if (p.direction + 2) % 4 == player[a].direction % 4 then
player[a].dead = true
deadGuys[a] = true
end
break
end
end
end
end
return deadAnimation(doSend)
end
local setDirection = function(p, checkDir, lastDir)
if (lastDir == control.left) and (checkDir or p.direction) ~= 0 then
p.direction = 2
return true
elseif (lastDir == control.right) and (checkDir or p.direction) ~= 2 then
p.direction = 0
return true
elseif (lastDir == control.up) and (checkDir or p.direction) ~= 1 then
p.direction = -1
return true
elseif (lastDir == control.down) and (checkDir or p.direction) ~= -1 then
p.direction = 1
return true
elseif isPuttingDown == keysDown[control.release] then
return true
else
return false
end
end
local game = function()
local outcome
local p, np, timeoutID, tID, evt, netTime
while true do
netTime = nil
if isHost then
sleep(gameDelay)
else
timeoutID = os.startTimer(3)
repeat
evt, tID = os.pullEvent()
until evt == "move_tick" or (evt == "timer" and tID == timeoutID)
if evt == "timer" then
os.queueEvent("tron_complete", "timeout", isHost, player[nou].name)
parallel.waitForAny(function() imageAnim(images.timeout) end, waitForKey)
return
elseif evt == "move_tick" then
netTime = tID
end
end
p = player[you]
np = player[nou]
if isHost then
setDirection(p, nil, control[lastDirectionPressed])
setDirection(np, nil, control[netLastDirectionPressed])
p.putTrail = not keysDown[control.release]
else
setDirection(p, nil, control[lastDirectionPressed])
isPuttingDown = not keysDown[control.release]
sendInfo(gamename, isHost)
end
if miceDown[3] then
scrollAdjX = scrollAdjX + (miceDown[3][1] - scr_x / 2) / (scr_x / 4)
scrollAdjY = scrollAdjY + (miceDown[3][2] - scr_y / 2) / (scr_y / 2.795)
else
if keysDown[control.lookLeft] then
scrollAdjX = scrollAdjX - 2
end
if keysDown[control.lookRight] then
scrollAdjX = scrollAdjX + 2
end
if keysDown[control.lookUp] then
scrollAdjY = scrollAdjY - 1.25
end
if keysDown[control.lookDown] then
scrollAdjY = scrollAdjY + 1.25
end
end
scrollAdjX = scrollAdjX * 0.8
scrollAdjY = scrollAdjY * 0.8
if isHost then
outcome = moveTick(true)
else
outcome = deadAnimation(false)
end
ageTrails()
if outcome == "end" then
return
else
scrollX = p.x - mathfloor(scr_x / 2)
scrollY = p.y - mathfloor(scr_y / 2)
render(true, (not isHost) and netTime)
end
end
end
local cTime -- current UTC time when looking for game
local networking = function()
local evt, side, channel, repchannel, msg, distance
while true do
if useSkynet then
evt, channel, msg = os.pullEvent("skynet_message")
else
evt, side, channel, repchannel, msg, distance = os.pullEvent("modem_message")
end
if channel == port and type(msg) == "table" then
if type(msg.gameID) == "string" then
if waitingForGame and (type(msg.time) == "number") then
if msg.password == argumentPassword or (argumentPassword == "" and not msg.password) then
-- called while waiting for match
if msg.time < cTime then
isHost = false
you, nou = nou, you
gamename = msg.gameID
gameDelay = tonumber(msg.gameDelay) or gameDelayInit
grid = msg.grid or copyTable(initGrid)
player = msg.player or player
player[you].name = argumentName or player[you].initName
else
isHost = true
end
player[nou].name = msg.name or player[nou].initName
transmit(port, {
player = player,
gameID = gamename,
time = cTime,
name = argumentName,
password = argumentPassword,
grid = initGrid
})
waitingForGame = false
netKeysDown = {}
os.queueEvent("new_game", gameID)
return gameID
end
elseif msg.gameID == gamename then
-- called during gameplay
if not isHost then
if type(msg.player) == "table" then
player[nou].name = msg.name or player[nou].name
player = msg.player
if msg.trail then
for i = 1, #msg.trail do
putTrailXY(table.unpack(msg.trail[i]))
end
end
deadGuys = msg.deadGuys
os.queueEvent("move_tick", msg.time)
end
elseif type(msg.keysDown) == "table" then
netKeysDown = msg.keysDown
netLastDirectionPressed = msg.lastDir
player[nou].putTrail = msg.putTrail
player[nou].name = msg.name or "???" --player[nou].name
end
end
end
end
end
end
local helpScreen = function()
termsetBackgroundColor(colors.black)
termsetTextColor(colors.white)
termclear()
termsetCursorPos(1,2)
print([[
Move your lightcycle with the
arrow keys or by tapping
left click.
Pan the camera with WASD or
by holding middle click.
Release the trail with spacebar
or by holding right click.
If you're P2 (red), a gray circle
will indicate where you'll turn,
to help with Skynet's netlag.
Press any key to go back.
]])
waitForKey(0.25)
end
local startGame = function()
-- reset all info between games
keysDown = {}
miceDown = {}
scrollAdjX = 0
scrollAdjY = 0
trail = {}
deadGuys = {}
lastDirectionPressed = nil
netLastDirectionPressed = nil
gameDelay = gameDelayInit
grid = copyTable(initGrid)
player = resetPlayers()
you, nou = 1, 2
gamename = ""
for i = 1, 32 do
gamename = gamename .. string.char(mathrandom(1,126))
end
waitingForGame = true
cTime = getTime()
transmit(port, {
player = player,
gameID = gamename,
gameDelay = gameDelayInit,
time = cTime,
password = argumentPassword,
name = argumentName,
grid = initGrid
})
rVal = parallel.waitForAny( pleaseWait, networking )
sleep(0.1)
player[you].name = argumentName or player[you].initName
if rVal == 2 then
startCountdown()
parallel.waitForAny( getInput, game, networking )
end
end
local decision
local main = function()
return pcall(function()
local rVal
while true do
mode = "menu"
decision = titleScreen()
lockInput = false
if decision == "start" then
mode = "game"
if useSkynet then
parallel.waitForAny(startGame, skynet.listen)
else
startGame()
end
elseif decision == "help" then
mode = "help"
helpScreen()
elseif decision == "demo" then
mode = "demo"
parallel.waitForAny( getInput, gridDemo )
elseif decision == "exit" then
return cleanExit()
end
end
end)
end
if doGridDemo then
parallel.waitForAny(function()
local step, gsX, gsY = 0, 0, 0
while true do
drawGrid(gsX, gsY, true)
step = step + 1
if mathceil(step / 100) % 2 == 1 then
gsX = gsX + 1
else
gsY = gsY - 1
end
sleep(0.05)
end
end, function()
sleep(0.1)
local evt, key
repeat
evt, key = os.pullEvent("key")
until key == keys.q
sleep(0.1)
end)
else
if useOnce then
term.setCursorBlink(false)
if useSkynet then
parallel.waitForAny(startGame, skynet.listen)
skynet.socket.close()
else
startGame()
end
term.setCursorPos(1, scr_y)
else
main()
if skynet then
skynet.socket.close()
end
end
end