--[[ 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] 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://raw.githubusercontent.com/osmarks/skynet/master/client.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 = {} 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 = { logo = { { " •ƒƒƒƒƒƒƒƒƒ•—ƒƒƒƒƒƒƒ‹‹ ‡‡ƒƒƒ‹‹ Ÿ‹ •ƒƒ•", " •ƒƒƒ”€—ƒƒƒ•‚ƒƒƒƒƒ‹€€€Š —€Ÿƒƒƒ€” •‚‚ •€€•", " •€• ‚‚ƒƒ•€€—€€€”€€••€€‹‹ •€€•", " •€• —ƒ”‹“ƒƒ‹€€€•€€•€€€•€€••€•ˆƒ€€•", " •€• •€• ‚‹€€‹€Š€‹‡€Ÿ…•€• ‚‚€•", " •€• •€• ‹€‚‹ ‹‹€€€Ÿ‡‡ •€• ‹‹•", "   Š ‚‹‡  ‚…", }, { " 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 = { { "€•€€€€€€€••€€€€€€€€Š€€€€•€€€•", "€•€€€€€€€•‚€€•€ƒ€€€‚€€•€€€•", "€•€€‡€€€•€€€€•€€€€•‹€‹€•€€€•", "€•ŸŸ€‹€€•€€€€•€€€€•€‚‚…€€€•", "€‚€‡€‚‚€•Ÿ€€•€€€•€€€‹€€€", "€Ÿ€€€€‹€••€€€€€€€€•€€€€•€€€•", }, { "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 = { { "€•€€€€€€Ÿ€€€‚€€€€€€‚€€€€€€€€", "€•€€€€€€€Ÿ€‚€€€—€€€‚ƒ€€•€€€‚ƒ", "€•€€€€€€€•€€€•€€€€ƒƒƒƒ‹€€‚ƒƒƒ”€", "€•€€€€€€€•€€€•€€‚ƒƒƒƒ€€€—ƒƒƒ€", "€•€€€€€€€‚€Ÿ€€€€€…€€€•€€€Ÿ", "€€€€€€€€‚€€€Ÿ€€€€€€Ÿ€€€€€€€€", }, { "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 = { { "€€€€€€€••€€€€€€€€€€€€€€€", "€€€€•€€€‚€•€€€ƒ€€•€€€€ƒ", "€€€€•€€€€€€•€€€€€€‚ƒƒƒ”€", "€€€€•€€€€€€•€€€€€€—ƒƒƒ€", "€€€€•€€€Ÿ€•€€€€€•€€€€", "€€€€•€€€•€€€€€€€€€€€€€€€", }, { "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 = { { "—ƒƒƒƒ•—ƒƒƒƒ•—ƒ›Ÿ†ƒ•—ƒƒƒ”", "“€—Œ…“€—Œ…•€€€€••€ŒŒž", " •€•€€…€••€˜•€••€ˆŒŽ", " •€•€€•€€€€••€•€•€••€€€•", " ‚ƒƒ€€ƒƒƒƒƒƒƒ€ƒƒƒƒƒƒƒ", " —ƒƒƒƒ••ƒ”•ƒ”•ƒƒƒƒ”", " •€œ”€••€••€•ŠŒ”€œŽ", " •€Š•€••€•…€• €•€•", " •€€€€••€€€€• €•€•", " ƒƒƒƒƒ‚ƒƒƒƒƒ€€ƒƒ", }, { "00000000000000ff0000000f", "0fff000fff000ff0ff00f000", "0ffffffffff00f000f00ffff", " fffff0ffff00f0f0f00ffff", " 000ff000000000f00000000", " 000000f0ff0ff0000f", " 0f00f0ffffff000f00", " 0ff0f0ffffff7f0f0", " 0ffff0ffffff7f0f0", " 000000000000ff000", }, { "ffffffffffffff00fffffff0", " 0f0fff0f0ffffffffffffff", " 0f0ff00f00ffffffffff000", " 0f0fffffffffffffffffff0", " fffffffffffffffffffffff", " ffffff0f00f00ffff0", " ffffff0f00f0ffffff", " ff0fff0f00f0fffff", " ffffff0ffff0fffff", " fffffffffffffffff", }, }, ldd = { { " ", " ƒŒ‹—”—”  —œ“€ƒ‚•ˆŒ‡ŒŒŸŸœ“ƒ€ƒ•ˆŒƒŒ‹", " €ƒ‚ ”— € ••••€Ÿ•Šƒ‚ •• €Œˆ••••€•Š€ƒ‚", " ƒƒ ‚ ŒŒ‚ƒƒ ‚ƒ Š…   ƒƒ ƒ ƒ", }, { " ", " 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", }, } } 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 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 = (os.epoch("utc") - 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 local gstID, evt = mathrandom(1,65535) if doAnimate then os.queueEvent("timer", gstID) 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] == gstID then gstID = os.startTimer(gameDelayInit) drawGrid(gsX, gsY, true) step = step + 1 if mathceil(step / 100) % 2 == 1 then gsX = gsX + 1 else gsY = gsY - 1 end doRend = true elseif evt[2] == tID then doRend = true if x > fromX then xmod = math.min(xmod + 1, x - fromX) tID = os.startTimer(0.05) else xmod = math.max(xmod - 1, x - fromX) tID = os.startTimer(0.05) end end end if lastPos ~= cpos or doRend then rend() doRend = false end end end local nameChange = function(scrollInfo) local gsX, gsY = (scrollInfo or {})[2] or 0, (scrollInfo or {})[3] or 0 local step = (scrollInfo or {})[1] or 0 local tID = os.startTimer(gameDelayInit) local buff = {} local cpos = 1 local maxSize = 15 local evt -- 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 } local prevName = argumentName or player[you].initName for x = 1, #prevName do buff[x] = prevName:sub(x, x) cpos = cpos + 1 end term.setCursorBlink(true) local rend = function() if table.concat(buff):upper() == "GASTER" then os.reboot() -- lol end drawGrid(gsX, gsY, true) term.setTextColor(colors.white) cwrite("Enter your name.", scr_y - 5) term.setTextColor(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(gameDelayInit) 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 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", (useSkynet and "Disable" or "Enable") .. " Skynet", "Back..." } choice, scrollInfo = makeMenu(6, currentX, scr_y - #options, options, true, scrollInfo, _cpos) currentX = 6 _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 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 == 5 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 os.epoch("utc"), 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 -- 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, grid = initGrid }) waitingForGame = false netKeysDown = {} os.queueEvent("new_game", gameID) return gameID 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 WASD or by tapping left click. Pan the camera with arrows 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 = os.epoch("utc") transmit(port, { player = player, gameID = gamename, gameDelay = gameDelayInit, time = cTime, 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