--[[ TRON Light Cycle Game programmed by LDDestroier wget https://raw.githubusercontent.com/LDDestroier/CC/master/tron --]] local port = 701 local scr_x, scr_y = term.getSize() local 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 local gamename = "" local isHost local squareGrid = true local waitingForGame = true local deepCopy deepCopy = function(tbl, ...) local output = {} for k,v in pairs(tbl) do if type(v) == "table" then output[k] = deepCopy(v) else output[k] = v end end for i = 1, #arg do output[#output+1] = arg[i] end return output end local initGrid = { x1 = -100, y1 = -100, x2 = 100, y2 = 100, border = "#", voidcol = "f", forecol = "8", backcol = "7", edgecol = "0" } grid = deepCopy(initGrid) local you = 1 local nou = 2 local keysDown = {} local netKeysDown = {} -- 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 gameDelayInit = 0.1 -- lower value = faster game. I'd reccommend 0.1 for SMP play. local player local resetPlayers = function() player = { [1] = { num = 1, x = -2, 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, putTrail = true }, [2] = { num = 2, x = 2, 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, putTrail = true } } end resetPlayers() local images = { logo = { { " •ƒƒƒƒƒƒƒƒƒ•—ƒƒƒƒƒƒƒ‹‹ ‡‡ƒƒƒ‹‹ Ÿ‹ •ƒƒ•", " •ƒƒƒ”€—ƒƒƒ•‚ƒƒƒƒƒ‹€€€Š —€Ÿƒƒƒ€” •‚‚ •€€•", " •€• ‚‚ƒƒ•€€—€€€”€€••€€‹‹ •€€•", " •€• —ƒ”‹“ƒƒ‹€€€•€€•€€€•€€••€•ˆƒ€€•", " •€• •€• ‚‹€€‹€Š€‹‡€Ÿ…•€• ‚‚€•", " •€• •€• ‹€‚‹ ‹‹€€€Ÿ‡‡ •€• ‹‹•", "   Š ‚‹‡  ‚…", }, { " f7777777777777777777f f77777f 7f f777", " f99979999979999999999f 799999799 77f7 f997", " 799 79999f997ffff9977997f f997", " 799 7797777fffff997ffff9977997797997", " 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 f7777799ffff799f99997 799f", " 997 997f9997fff799ffff799f997ff7999f", " 997 997 f7999fff999777997f997 f799f", " 997 997 f9997 f7999977f 997 f7f", " fff fff ffff fffffff fff ff", } }, win = { { "€•€€€€€€€••€€€€€€€€Š€€€€•€€€•", "€•€€€€€€€•‚€€•€ƒ€€€‚€€•€€€•", "€•€€‡€€€•€€€€•€€€€•‹€‹€•€€€•", "€•ŸŸ€‹€€•€€€€•€€€€•€‚‚…€€€•", "€‚€‡€‚‚€•Ÿ€€•€€€•€€€‹€€€", "€Ÿ€€€€‹€••€€€€€€€€•€€€€•€€€•", }, { "55ffffff55f555555f5ffffff5f55", "55ffffff5555f55f5f55f5fff5f55", "55fff5ff55fff55fff5555fff5f55", "55ff55ff55fff55fff55f5fff5f55", "5f55f5ff55f5f55fff55fff555ff5", "555ffff555f555555f55fffff5f55", }, { "5fffffff5f5555555f55ffff55f5f", "5fffffff5ffff5ffff555fff55f5f", "5fff5fff5ffff5ffff5ff55f55f5f", "5f55f55f5ffff5ffff5fff5555f5f", "555fff555f5ff5ff5f5fffff55f5f", "5fffffff5f5555555f5fffff55f5f", } }, lose = { { "€•€€€€€€Ÿ€€€‚€€€€€€‚€€€€€€€€", "€•€€€€€€€Ÿ€‚€€€—€€€‚ƒ€€•€€€‚ƒ", "€•€€€€€€€•€€€•€€€€ƒƒƒƒ‹€€‚ƒƒƒ”€", "€•€€€€€€€•€€€•€€‚ƒƒƒƒ€€€—ƒƒƒ€", "€•€€€€€€€‚€Ÿ€€€€€…€€€•€€€Ÿ", "€€€€€€€€‚€€€Ÿ€€€€€€Ÿ€€€€€€€€", }, { "eeffffffffeeefeffeeeeeffeeeeeee", "eeffffffeeefefefeefffeefeefffee", "eeffffffeeffffefeeffffffeffffef", "eeffffffeeffffefeeeeefefeeeeeef", "eeffffffefefffeffeffffefeefffff", "eeeeeeefefeeeeeffeeeeeefeeeeeee", }, { "efffffffeeeeeeffeeeeeeefeeeeeee", "efffffffeffffeefefffffffeffffff", "efffffffeffffeefeeeeeeefeeeeeff", "efffffffeffffeeffffffeefeffffff", "efffffffeeffeeefeffffeefeffffee", "eeeeeeeffeeeefffeeeeeeffeeeeeee", } }, tie = { { "€€€€€€€••€€€€€€€€€€€€€€€", "€€€€•€€€‚€•€€€ƒ€€•€€€€ƒ", "€€€€•€€€€€€•€€€€€€‚ƒƒƒ”€", "€€€€•€€€€€€•€€€€€€—ƒƒƒ€", "€€€€•€€€Ÿ€•€€€€€•€€€€", "€€€€•€€€•€€€€€€€€€€€€€€€", }, { "77888800f0000000f0888877", "fff88fff00ff0ff0f08ffff7", "fff88fffffff0ffff0ffff7f", "fff88fffffff0ffff088887f", "fff88ffff0ff0ffff08fffff", "fff88ffff0000000f0888877", }, { "7788880f00000000f0888877", "fff8fffffff00ffff0ffffff", "fff8fffffff00ffff08888ff", "fff8fffffff00ffff0ffffff", "fff8ffff0ff00ff0f0fffff7", "fff8ffff00000000f0888877", }, } } for k,v in pairs(images) do v.x = #v[1][1] v.y = #v[1] for i = 2, #v do for line = 1, #v[i] do images[k][i][line] = v[i][line]:gsub("f", " ") end end end local tocolors 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" } for k,v in pairs(toblit) do tocolors[v] = k end local drawImage = function(im, x, y) local cx, cy = term.getCursorPos() term.setBackgroundColor(tocolors[initGrid.voidcol]) term.setTextColor(tocolors[initGrid.voidcol]) for iy = 1, #im[1] do for ix = 1, #im[1][iy] do term.setCursorPos(x+(ix-1),y+(iy-1)) if not (im[2][iy]:sub(ix,ix) == " " and im[3][iy]:sub(ix,ix) == " ") then term.blit(im[1][iy]:sub(ix,ix), im[2][iy]:sub(ix,ix), im[3][iy]:sub(ix,ix)) end end end term.setCursorPos(cx,cy) end local deadGuys = {} local trail = {} local lastTrails = {} local putTrailXY = function(x, y, p) trail[y] = trail[y] or {} trail[y][x] = { player = 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 if doAge then trail[y][x].age = trail[y][x].age + 1 end return trail[y][x].player.char, 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 = { 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 } -- keeps track of where you are local gamemode = "" local gridFore, gridBack if squareGrid then gridFore = { "+-------", "| ", "| ", "| ", "| " } gridBack = { "+------------", "| ", "| ", "| ", "| ", "| ", "| ", "| " } else gridFore = { " / ", " / ", " / ", " / ", "/__________" } gridBack = { " / ", " / ", " / ", " / ", " / ", " / ", " / ", "/_______________" } end local dirArrow = { [-1] = "^", [0] = ">", [1] = "V", [2] = "<" } local doesIntersectBorder = function(x, y) return x == grid.x1 or x == grid.x2 or y == grid.y1 or y == grid.y2 end --draws grid and background at scroll 'x' and 'y', along with trails and players local drawGrid = function(x, y, onlyDrawGrid) x, y = math.floor(x + 0.5), math.floor(y + 0.5) local bg = {{},{},{}} local foreX, foreY local backX, backY local adjX, adjY local trailChar, trailColor, trailAge, isPlayer 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 + math.floor(sx + (x / 2)) % #gridBack[1] backY = 1 + math.floor(sy + (y / 2)) % #gridBack trailChar, trailColor, trailAge = getTrail(adjX, adjY) isPlayer = false if not onlyDrawGrid then for i = 1, #player do if player[i].x == adjX and player[i].y == adjY then isPlayer = i break end end end if isPlayer 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 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 term.setCursorPos(1,sy) term.blit(bg[1][sy], bg[2][sy], bg[3][sy]) end end local render = function() local p = player[you] drawGrid(scrollX + scrollAdjX, scrollY + scrollAdjY) term.setCursorPos(1,1) term.setTextColor(player[you].color[1]) term.write("P" .. you) end local pleaseWait = function() local periods = 1 local maxPeriods = 5 term.setBackgroundColor(colors.black) term.setTextColor(colors.gray) term.clear() local tID = os.startTimer(0.2) local evt local txt = "Waiting for game" while true do term.setCursorPos(math.floor(scr_x / 2 - (#txt + maxPeriods) / 2), scr_y - 2) term.write(txt .. ("."):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] == "new_game" then return evt[2] 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 - math.floor(scr_x / 2) scrollY = player[you].y - math.floor(scr_y / 2) for i = 3, 1, -1 do render() term.setCursorPos(math.floor(scr_x / 2 - (#cMessage + #cName) / 2), math.floor(scr_y / 2) + 2) term.setTextColor(colors.white) term.write(cMessage) term.setTextColor(col) term.write(cName) term.setTextColor(colors.white) term.setCursorPos(math.floor(scr_x / 2 - 2), math.floor(scr_y / 2) + 4) term.write(i .. "...") sleep(1) end end local makeMenu = function(x, y, options, doAnimate) local cpos = 1 local cursor = "> " local gsX, gsY = 0, 0 local step = 0 local lastPos = cpos if not doAnimate then drawImage(images.logo, math.ceil(scr_x / 2 - images.logo.x / 2), 2) end local rend = function() if doAnimate then drawImage(images.logo, math.ceil(scr_x / 2 - images.logo.x / 2), 2) end for i = 1, #options do if i == cpos then term.setCursorPos(x, y + (i - 1)) term.setTextColor(colors.white) term.write(cursor .. options[i]) else if i == lastPos then term.setCursorPos(x, y + (i - 1)) term.write((" "):rep(#cursor)) lastPos = nil else term.setCursorPos(x + #cursor, y + (i - 1)) end term.setTextColor(colors.gray) term.write(options[i]) end end end local gstID, evt = math.random(1,65535) if doAnimate then os.queueEvent("timer", gstID) end while true do rend() 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 end elseif evt[1] == "timer" and evt[2] == gstID then gstID = os.startTimer(0.05) drawGrid(gsX, gsY, true) step = step + 1 if math.ceil(step / 100) % 2 == 1 then gsX = gsX + 1 else gsY = gsY - 1 end end end end local titleScreen = function() term.clear() local choice = makeMenu(2, scr_y - 4, { "Start Game", "How to Play", "Grid Demo", "Exit" }, true) if choice == 1 then return "start" elseif choice == 2 then return "help" elseif choice == 3 then return "demo" elseif choice == 4 then return "exit" end end local cleanExit = function() term.setBackgroundColor(colors.black) term.setTextColor(colors.white) term.clear() term.setCursorPos(1,1) print("Thanks for playing!") end local getInput = function() local evt while true do evt = {os.pullEvent()} if lockInput then keysDown = {} else if evt[1] == "key" then keysDown[evt[2]] = true elseif evt[1] == "key_up" then keysDown[evt[2]] = 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() sleep(0.05) end end local gridDemo = function() keysDown = {} 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) ageTrails() sleep(gameDelay) end end local sendInfo = function(gameID) modem.transmit(port, port, { player = player, gameID = gameID, keysDown = keysDown, trail = isHost and lastTrails or {}, deadGuys = isHost and deadGuys or {}, }) end local waitForKey = function(time) sleep(time or 0.1) os.pullEvent("key") 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 term.setTextColor(colors.white) if deadGuys[you] and deadGuys[nou] then scrollToPosition(player[nou].x, player[nou].y) scrollToPosition(player[you].x, player[you].y) drawImage(images.tie, math.ceil(scr_x / 2 - images.tie.x / 2), math.floor(scr_y / 2 - images.tie.y / 2)) waitForKey(1) return "end" else if deadGuys[you] then scrollX, scrollY = player[nou].x - scr_x / 2, player[nou].y - scr_y / 2 scrollToPosition(player[you].x, player[you].y) drawImage(images.lose, math.ceil(scr_x / 2 - images.lose.x / 2), math.floor(scr_y / 2 - images.lose.y / 2)) waitForKey(1) return "end" elseif deadGuys[nou] then scrollToPosition(player[nou].x, player[nou].y) drawImage(images.win, math.ceil(scr_x / 2 - images.win.x / 2), math.floor(scr_y / 2 - images.win.y / 2)) waitForKey(1) 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 + math.floor(math.cos(math.rad(p.direction * 90))) p.y = p.y + math.floor(math.sin(math.rad(p.direction * 90))) if doesIntersectBorder(p.x, p.y) or getTrail(p.x, p.y) then p.dead = true deadGuys[i] = true elseif p.putTrail then putTrail(p) lastTrails[#lastTrails+1] = {p.x, p.y, p.num} if #lastTrails > #player then table.remove(lastTrails, 1) 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(keylist, p, checkDir) p.putTrail = not keylist[control.release] if keylist[control.left] and (checkDir or p.direction) ~= 0 then p.direction = 2 elseif keylist[control.right] and (checkDir or p.direction) ~= 2 then p.direction = 0 elseif keylist[control.up] and (checkDir or p.direction) ~= 1 then p.direction = -1 elseif keylist[control.down] and (checkDir or p.direction) ~= -1 then p.direction = 1 end end local game = function() local outcome local p, np while true do if not isHost then os.pullEvent("move_tick") end p = player[you] np = player[nou] if isHost then setDirection(keysDown, p) setDirection(netKeysDown, np) else setDirection(keysDown, p) end 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.5 end if keysDown[control.lookDown] then scrollAdjY = scrollAdjY + 1.5 end scrollAdjX = scrollAdjX * 0.8 scrollAdjY = scrollAdjY * 0.8 if isHost then outcome = moveTick(true) else outcome = deadAnimation(true) end ageTrails() if outcome == "end" then return else scrollX = p.x - math.floor(scr_x / 2) scrollY = p.y - math.floor(scr_y / 2) render() if isHost then sleep(gameDelay) end end end end local decision local networking = function() local evt, side, channel, repchannel, msg, distance while true do evt, side, channel, repchannel, msg, distance = os.pullEvent("modem_message") if channel == port and repchannel == port and type(msg) == "table" then if type(msg.player) == "table" and type(msg.gameID) == "string" then if waitingForGame and (type(msg.new) == "number") then if msg.new < os.time() then gamename = msg.gameID isHost = false gameDelay = tonumber(msg.gameDelay) or 0.05 grid = msg.grid or deepCopy(initGrid) else you, nou = nou, you isHost = true end you, nou = nou, you modem.transmit(port, port, { player = player, gameID = gamename, new = isHost and (-math.huge) or (math.huge), grid = initGrid }) waitingForGame = false netKeysDown = {} os.queueEvent("new_game", gameID) elseif msg.gameID == gamename then if not isHost then player = msg.player for i = 1, #msg.trail do putTrailXY(unpack(msg.trail[i])) end deadGuys = msg.deadGuys os.queueEvent("move_tick") elseif type(msg.keysDown) == "table" then netKeysDown = msg.keysDown end end end end end end local helpScreen = function() term.setBackgroundColor(colors.black) term.setTextColor(colors.white) term.clear() term.setCursorPos(1,2) print([[ Move with arrow keys. Pan the camera with WASD. Hold SPACE to create gaps. That's basically it. Press any key to go back. ]]) waitForKey(0.25) end while true do decision = titleScreen() lockInput = false if decision == "start" then trail = {} deadGuys = {} gameDelay = gameDelayInit grid = deepCopy(initGrid) resetPlayers() you, nou = 1, 2 gamename = "" for i = 1, 32 do gamename = gamename .. string.char(math.random(1,126)) end waitingForGame = true modem.transmit(port, port, { player = player, gameID = gamename, new = os.time(), gameDelay = gameDelayInit, grid = initGrid }) parallel.waitForAny(pleaseWait, networking) sleep(0.1) startCountdown() parallel.waitForAny(getInput, game, networking) elseif decision == "help" then helpScreen() elseif decision == "demo" then parallel.waitForAny(getInput, gridDemo) elseif decision == "exit" then return cleanExit() end end