--[[ TRON Light Cycle Game programmed by LDDestroier wget https://raw.githubusercontent.com/LDDestroier/CC/master/tron.lua --]] local port = 701 local kioskMode = false local useLegacyMouseControl = false local scr_x, scr_y = term.getSize() local scr_mx, scr_my = scr_x / 2, scr_y / 2 local isColor = term.isColor() -- lower value = faster game. I'd reccommend 0.1 for SMP play. local gameDelayInit = 0.1 -- draws the names of players onscreen local doDrawPlayerNames = true -- if doDrawPlayerNames, also draws your own name local doRenderOwnName = false -- whether or not to use term.current().setVisible, which speeds things up at the cost of multishell local useSetVisible = false -- determines which grid is used local gridID = 3 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] = { { "+-------", "| ", "| ", "| ", "| " }, { "+------------", "| ", "| ", "| ", "| ", "| ", "| ", "| " } }, [2] = { { " / ", " / ", " / ", " / ", "/__________" }, { " / ", " / ", " / ", " / ", " / ", " / ", " / ", "/_______________" } }, [3] = { { "+- -+------", "| | ", " | ", ". | ", "+------+- --", "| | ", "| ", "| . ", }, { "+- -+--------", "| | ", " | ", " | ", " | ", "| | ", "+--------+- -", "| | ", "| ", "| ", "| ", "| | ", } }, [4] = { { " /\\ ", " / \\ ", " / \\ ", "/ \\", "\\ /", " \\ / ", " \\ / ", " \\/ ", }, { " /\\ ", " / \\ ", " / \\ ", " / \\ ", " / \\ ", "/ \\", "\\ /", " \\ / ", " \\ / ", " \\ / ", " \\ / ", " \\/ ", } } } 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 skynetPath = "skynet" 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 modem, skynet if not doGridDemo then if useSkynet then if fs.exists(skynetPath) then skynet = dofile(skynetPath) skynet.open(port) else local prog = http.get(skynetURL) if prog then local file = fs.open(skynetPath, "w") file.write(prog.readAll()) file.close() skynet = dofile(skynetPath) 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 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 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) / 10^places end -- 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", }, } } 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] -- remove all that white bullshit that artifacts on cc:tweaked 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) .. "f" .. 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) .. "f" .. 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), im[3][iy]:sub(ix,ix) ) 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 cwrite = function(text, y, xdiff, wordPosCheck) wordPosCheck = wordPosCheck or #text termsetCursorPos(mathfloor(scr_x / 2 - (#text + (xdiff or 0)) / 2), y or (scr_y - 2)) term.write(text) return (scr_x / 2) - (#text / 2) + wordPosCheck end 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 + (scrollX + scrollAdjX), adjY + (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, y, options, doAnimate, scrollInfo, _cpos) local cpos = _cpos or 1 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 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 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 for i = 1, #options do if i == cpos then termsetCursorPos(x, y + (i - 1)) termsetTextColor(colors.white) termwrite(cursor .. options[i]) else if i == lastPos then termsetCursorPos(x, y + (i - 1)) termwrite((" "):rep(#cursor)) lastPos = nil else termsetCursorPos(x + #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() 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) rend() end end elseif evt[1] == "timer" and 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 rend() end if lastPos ~= cpos then rend() 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, ["3d6"] = colors.lime, ["lyqyd"] = colors.red, ["squiddev"] = colors.cyan, ["oeed"] = colors.green, ["dog"] = colors.purple, ["nothy"] = colors.lightGray, ["kepler"] = colors.cyan, ["crazed"] = colors.lightBlue, ["ape"] = colors.brown, ["everyos"] = 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, ["kingdaro"] = colors.blue, ["valithor"] = colors.orange, ["logandark"] = colors.lightGray } 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 options = { "Grid Demo", "Change Name", "Change Grid", "Back..." } while true do choice, scrollInfo = makeMenu(2, scr_y - #menuOptions, menuOptions, true, scrollInfo) if choice == 1 then return "start" elseif choice == 2 then return "help" elseif choice == 3 then local _cpos while true do choice, scrollInfo = makeMenu(14, scr_y - #menuOptions, options, true, scrollInfo, _cpos) _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 break end end elseif choice == 4 then return "exit" end end end local cleanExit = function() termsetBackgroundColor(colors.black) termsetTextColor(colors.white) termclear() termsetCursorPos(1,1) print("Thanks for playing!") end local parseMouseInput = function(button, x, y, direction) local output = false local cx = x - scr_mx local cy = y - scr_my if useLegacyMouseControl then -- outdated mouse input 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(gameDelay) end end local sendInfo = function(gameID) transmit(port, { player = isHost and player or nil, name = player[you].name, putTrail = isPuttingDown, gameID = gameID, time = 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) 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 moveTick = function(doSend) local p for i = 1, #player do p = player[i] if not p.dead then if isHost then p.x = p.x + mathfloor(mathcos(mathrad(p.direction * 90))) p.y = p.y + mathfloor(mathsin(mathrad(p.direction * 90))) if 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 putTrail(p) lastTrails[#lastTrails+1] = {p.x, p.y, p.num} if #lastTrails > #player then tableremove(lastTrails, 1) 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) 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 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.new) == "number") then -- called while waiting for match if msg.new < os.time() 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, new = isHost and (-math.huge) or (math.huge), time = os.epoch("utc"), 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(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 with arrow keys. Pan the camera with WASD. Hold SPACE to create gaps. This takes fuel, which will recharge over time.. If you're P2 (red), a gray circle will indicate where you'll turn, to help with Skynet's netlag. That's basically it. 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 transmit(port, { player = player, gameID = gamename, new = os.time(), gameDelay = gameDelayInit, time = os.epoch("utc"), 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() local rVal while true do decision = titleScreen() lockInput = false if decision == "start" then startGame() elseif decision == "help" then helpScreen() elseif decision == "demo" then parallel.waitForAny( getInput, gridDemo ) elseif decision == "exit" then return cleanExit() end end 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) 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 if useSkynet then parallel.waitForAny(main, skynet.listen) skynet.socket.close() else main() end end end