MoonlightOwl

UT2 Bot

Apr 30th, 2017
237
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. -- TOTORO TERMINATORS TEAM
  2. -- Version: 0.2.0 - Fire
  3. -- Special for UT2 event
  4. -- MIT, 2017 (c) Totoro
  5.  
  6. local robot = require('robot')
  7. local event = require('event')
  8. local com = require('component')
  9. local sides = require('sides')
  10. local serial = require('serialization')
  11. local plasma = com.plasma
  12. local geo = com.geolyzer
  13. local nav = com.navigation
  14. local modem = com.modem
  15. local colors = com.colors
  16.  
  17. -- initialize random numbers generator with modem address
  18. math.randomseed(string.byte(modem.address, 1) * 1000000 + string.byte(modem.address, 2) * 10000 +
  19.   string.byte(modem.address, 3) * 100 + string.byte(modem.address, 4));
  20.  
  21. -- constants
  22. local TEAM = "green"
  23. local TEAMSIZE = 8
  24. local CODENAME = string.char(65 + math.random(0, 25)) .. string.char(65 + math.random(0, 25))
  25.  
  26. local COLORS = { red = 0xFF0000, green = 0x00FF00, blue = 0x0000FF, yellow = 0xFFFF00 }
  27. local OUTSIDE, ENEMY = -1, 2                     -- density values
  28.  
  29. local SERVERPORT, INTERNALPORT = 12345, 99
  30. local SERVERADDRESS = nil
  31. local MODEMSTRENGTH = 50
  32.  
  33. local BOUNDS = { X = -23, Y = 64, Z = -23, W = 47, H = 6, D = 47 }
  34. local GEOMETRY = {
  35.   "###############################################",
  36.   "####################### #######################",
  37.   "######################   ######################",
  38.   "#####################     #####################",
  39.   "####################       ####################",
  40.   "####################       ####################",
  41.   "####################       ####################",
  42.   "####################  # #  ####################",
  43.   "####################       ####################",
  44.   "####################  # #  ####################",
  45.   "##########                           ##########",
  46.   "##########                           ##########",
  47.   "##########                           ##########",
  48.   "##########                           ##########",
  49.   "##########                           ##########",
  50.   "##########                           ##########",
  51.   "##########                           ##########",
  52.   "##########                           ##########",
  53.   "##########                           ##########",
  54.   "##########                           ##########",
  55.   "####                                       ####",
  56.   "###                                         ###",
  57.   "##     # #                           # #     ##",
  58.   "#                                             #",
  59.   "##     # #                           # #     ##",
  60.   "###                                         ###",
  61.   "####                                       ####",
  62.   "##########                           ##########",
  63.   "##########                           ##########",
  64.   "##########                           ##########",
  65.   "##########                           ##########",
  66.   "##########                           ##########",
  67.   "##########                           ##########",
  68.   "##########                           ##########",
  69.   "##########                           ##########",
  70.   "##########                           ##########",
  71.   "##########                           ##########",
  72.   "####################  # #  ####################",
  73.   "####################       ####################",
  74.   "####################  # #  ####################",
  75.   "####################       ####################",
  76.   "####################       ####################",
  77.   "####################       ####################",
  78.   "#####################     #####################",
  79.   "######################   ######################",
  80.   "####################### #######################",
  81.   "###############################################",
  82. }
  83.  
  84. -- variables
  85. local map = {}
  86. local chunks, unscanned = {}, 49  -- information about explored map chunks
  87. local pos = { changed = true, facing = 0, x = 0, y = 0, z = 0  }
  88. local mates = {}
  89.  
  90. -- functions
  91. -- navigation
  92. function fetchPosition()
  93.   pos.facing = nav.getFacing()
  94.   local x, y, z = nav.getPosition()
  95.   pos.x, pos.y, pos.z = math.floor(x), math.floor(y), math.floor(z)
  96. end
  97.  
  98. local _tt = {
  99.   left = { left = 0, front = 1, right = 2, back = -1 },
  100.   front = { left = -1, front = 0, right = 1, back = 2 },
  101.   right = { left = 2, front = -1, right = 0, back = 1 },
  102.   back = { left = 1, front = 2, right = -1, back = 0 }
  103. }
  104. function turn(facing)
  105.   if facing ~= pos.facing then
  106.     local diff = _tt[sides[pos.facing]][sides[facing]]
  107.     if diff == -1 then robot.turnLeft()
  108.     elseif diff == 2 then robot.turnAround()
  109.     elseif diff == 1 then robot.turnRight() end
  110.     pos.facing = facing
  111.   end
  112. end
  113. function turnLeft()
  114.   robot.turnLeft()
  115.   if pos.facing == sides.left then pos.facing = sides.back
  116.   elseif pos.facing == sides.front then pos.facing = sides.left
  117.   elseif pos.facing == sides.right then pos.facing = sides.front
  118.   else pos.facing = sides.right end
  119. end
  120. function turnRight()
  121.   robot.turnRight()
  122.   if pos.facing == sides.left then pos.facing = sides.front
  123.   elseif pos.facing == sides.front then pos.facing = sides.right
  124.   elseif pos.facing == sides.right then pos.facing = sides.back
  125.   else pos.facing = sides.left end
  126. end
  127. function turnAround()
  128.   robot.turnRight()
  129.   if pos.facing == sides.left then pos.facing = sides.right
  130.   elseif pos.facing == sides.front then pos.facing = sides.back
  131.   elseif pos.facing == sides.right then pos.facing = sides.left
  132.   else pos.facing = sides.front end
  133. end
  134.  
  135. function up()
  136.   if robot.up() then pos.y = pos.y + 1; pos.changed = true; return true end
  137.   return false
  138. end
  139. function down()
  140.   if robot.down() then pos.y = pos.y - 1; pos.changed = true; return true end
  141.   return false
  142. end
  143. function forward()
  144.   if robot.forward() then
  145.     if pos.facing == sides.north then pos.z = pos.z - 1
  146.     elseif pos.facing == sides.south then pos.z = pos.z + 1
  147.     elseif pos.facing == sides.west then pos.x = pos.x - 1
  148.     else pos.x = pos.x + 1 end
  149.     pos.changed = true; return true
  150.   end
  151.   return false
  152. end
  153. function back()
  154.   if robot.back() then
  155.     if pos.facing == sides.north then pos.z = pos.z + 1
  156.     elseif pos.facing == sides.south then pos.z = pos.z - 1
  157.     elseif pos.facing == sides.west then pos.x = pos.x + 1
  158.     else pos.x = pos.x - 1 end
  159.     pos.changed = true; return true
  160.   end
  161.   return false
  162. end
  163.  
  164. -- mapping
  165. function set(x, y, z, value)
  166.   local mx, my, mz = math.floor(x) - BOUNDS.X, math.floor(y) - BOUNDS.Y, math.floor(z) - BOUNDS.Y
  167.   map[ my * BOUNDS.W * BOUNDS.D + mz * BOUNDS.W + mx ] = value
  168. end
  169. function get(x, y, z)
  170.   local mx, my, mz = math.floor(x) - BOUNDS.X, math.floor(y) - BOUNDS.Y, math.floor(z) - BOUNDS.Y
  171.   local cell = map[ my * BOUNDS.W * BOUNDS.D + mz * BOUNDS.W + mx ]
  172.   if cell == nil then return OUTSIDE
  173.   else return cell end
  174. end
  175.  
  176. function initializeMap()
  177.   for x = 1, 47 do
  178.     for y = 2, 5 do
  179.       for z = 1, 47 do
  180.         if GEOMETRY[x]:sub(z, z) == " " then
  181.           set(BOUNDS.X + x - 1, BOUNDS.Y + y - 1, BOUNDS.Z + z - 1, 0)
  182.         end
  183.       end
  184.     end
  185.   end
  186. end
  187.  
  188. -- spacial calculations
  189. function aim(x, y, z)
  190.   local facing, yaw, pitch
  191.   local quadrant = (math.deg(math.atan2(x - pos.x, z - pos.z)) + 360) % 360
  192.   if quadrant < 45 or quadrant >= 315 then facing = sides.south
  193.   elseif quadrant >= 45 and quadrant < 135 then facing = sides.east
  194.   elseif quadrant >= 135 and quadrant < 225 then facing = sides.north
  195.   else facing = sides.west end
  196.   if facing == sides.west then
  197.     yaw = math.deg(math.atan2(z - pos.z + 0.5, pos.x - x))
  198.     pitch = math.deg(math.atan2(y - pos.y, pos.x - x))
  199.   elseif facing == sides.east then
  200.     yaw = math.deg(math.atan2(pos.z + 0.5 - z, x - pos.x))
  201.     pitch = math.deg(math.atan2(y - pos.y, x - pos.x))
  202.   elseif facing == sides.south then
  203.     yaw = math.deg(math.atan2(x - pos.x + 0.5, z - pos.z))
  204.     pitch = math.deg(math.atan2(y - pos.y, z - pos.z))
  205.   else
  206.     yaw = math.deg(math.atan2(pos.x + 0.5 - x, pos.z - z))
  207.     pitch = math.deg(math.atan2(y - pos.y, pos.z - z))
  208.   end
  209.   return facing, yaw, pitch
  210. end
  211.  
  212. function raytrace(x0, y0, z0, x1, y1, z1)
  213.   local ray = {}
  214.  
  215.   local dx = math.abs(x1 - x0)
  216.   local dy = math.abs(y1 - y0)
  217.   local dz = math.abs(z1 - z0)
  218.  
  219.   local x = x0 --math.floor(x0)
  220.   local y = y0 --math.floor(y0)
  221.   local z = z0 --math.floor(z0)
  222.  
  223.   local dt_dx = 1.0 / dx
  224.   local dt_dy = 1.0 / dy
  225.   local dt_dz = 1.0 / dz
  226.  
  227.   local t = 0
  228.  
  229.   local n = 1
  230.   local x_inc, y_inc, z_inc = 0, 0, 0
  231.   local t_next_vertical, t_next_horizontal, t_next_abyssal = 0, 0, 0
  232.  
  233.   if dx == 0 then
  234.     x_inc = 0
  235.     t_next_horizontal = dt_dx -- infinity
  236.   elseif x1 > x0 then
  237.     x_inc = 1
  238.     n = n + math.floor(x1) - math.floor(x)
  239.     t_next_horizontal = (math.floor(x0) + 1 - x0) * dt_dx
  240.   else
  241.     x_inc = -1
  242.     n = n + math.floor(x) - math.floor(x1)
  243.     t_next_horizontal = (x0 - math.floor(x0)) * dt_dx
  244.   end
  245.  
  246.   if dy == 0 then
  247.     y_inc = 0
  248.     t_next_vertical = dt_dy; -- infinity
  249.   elseif y1 > y0 then
  250.     y_inc = 1
  251.     n = n + math.floor(y1) - math.floor(y)
  252.     t_next_vertical = (math.floor(y0) + 1 - y0) * dt_dy
  253.   else
  254.     y_inc = -1
  255.     n = n + math.floor(y) - math.floor(y1)
  256.     t_next_vertical = (y0 - math.floor(y0)) * dt_dy
  257.   end
  258.  
  259.   if dz == 0 then
  260.     z_inc = 0
  261.     t_next_abyssal = dt_dz; -- infinity
  262.   elseif z1 > z0 then
  263.     z_inc = 1
  264.     n = n + math.floor(z1) - math.floor(z)
  265.     t_next_abyssal = (math.floor(z0) + 1 - z0) * dt_dz
  266.   else
  267.     z_inc = -1
  268.     n = n + math.floor(z) - math.floor(z1)
  269.     t_next_abyssal = (z0 - math.floor(z0)) * dt_dz
  270.   end
  271.  
  272.   while n > 0 do
  273.     table.insert(ray, { x = math.floor(x), y = math.floor(y), z = math.floor(z) })
  274.  
  275.     if t_next_vertical < t_next_horizontal then
  276.       if t_next_vertical < t_next_abyssal then
  277.         y = y + y_inc
  278.         t = t_next_vertical
  279.         t_next_vertical = t_next_vertical + dt_dy
  280.       else
  281.         z = z + z_inc
  282.         t = t_next_abyssal
  283.         t_next_abyssal = t_next_abyssal + dt_dz
  284.       end
  285.     else
  286.       if t_next_horizontal < t_next_abyssal then
  287.         x = x + x_inc
  288.         t = t_next_horizontal
  289.         t_next_horizontal = t_next_horizontal + dt_dx
  290.       else
  291.         z = z + z_inc
  292.         t = t_next_abyssal
  293.         t_next_abyssal = t_next_abyssal + dt_dz
  294.       end
  295.     end
  296.  
  297.     n = n - 1
  298.   end
  299.  
  300.   return ray
  301. end
  302.  
  303. function friendlyFire(x, y, z)
  304.   for k, v in pairs(mates) do
  305.     if v.x == x and v.y == y and v.z == z then return true end
  306.   end
  307.   return false
  308. end
  309.  
  310. function detectEnemies()
  311.   local res = {}
  312.   for x = BOUNDS.X + 1, BOUNDS.X + BOUNDS.W - 2 do
  313.     for y = BOUNDS.Y + 1, BOUNDS.Y + BOUNDS.H - 2 do
  314.       for z = BOUNDS.Z + 1, BOUNDS.Z + BOUNDS.D - 2 do
  315.         if x ~= pos.x or y ~= pos.y or z ~= pos.z then
  316.           if get(x, y, z) == ENEMY then
  317.             if not friendlyFire(x, y, z) then
  318.               table.insert(res,
  319.                 { x = x, y = y, z = z,
  320.                   d = math.sqrt(math.pow(pos.x - x, 2) + math.pow(pos.y - y, 2) + math.pow(pos.z - z, 2))
  321.                 })
  322.             end
  323.           end
  324.         end
  325.       end
  326.     end
  327.   end
  328.   table.sort(res, function(a, b) return a.d < b.d end)
  329.   return res
  330. end
  331.  
  332. function targetIsClear(x, y, z, facing, yaw, pitch)
  333.   -- adjust coordinates
  334.   local offx, offy, offz = 0, 0.2, 0
  335.   if facing == sides.south then offx = -0.35
  336.   elseif facing == sides.north then offx = 0.35; yaw = yaw + 180
  337.   elseif facing == sides.east then offz = 0.35; yaw = yaw + 90
  338.   else offz = -0.35; yaw = yaw + 270 end
  339.   offx = offx + math.sin(math.rad(yaw)) * math.cos(math.rad(pitch))
  340.   offy = offy + math.sin(math.rad(pitch))
  341.   offz = offz + math.cos(math.rad(yaw)) * math.cos(math.rad(pitch))
  342.   -- trace
  343.   local ray = raytrace(pos.x + offx, pos.y + offy, pos.z + offz, x, y, z)
  344.   for k, v in pairs(ray) do
  345.     local block = get(v.x, v.y, v.z)
  346.     if friendlyFire(v.x, v.y, v.z) or (block ~= 0 and block ~= ENEMY) then return false end
  347.   end
  348.   return true
  349. end
  350.  
  351. function fire(x, y, z)
  352.   local facing, yaw, pitch = aim(x, y, z)
  353.   if targetIsClear(x, y, z, facing, yaw, pitch) then
  354.     if math.abs(yaw) <= 20 and math.abs(pitch) <= 90 then
  355.       turn(facing)
  356.       plasma.turn(yaw, pitch)
  357.       plasma.fire()
  358.       set(x, y, z, 0)
  359.       --modem.broadcast(INTERNALPORT, "hit", x, y, z)
  360.       return true
  361.     end
  362.   end
  363.   return false
  364. end
  365.  
  366.  
  367. function fillChunk(x, z, chunk, num)
  368.   local index = 1
  369.   for dy = 1, 4 do
  370.     for dz = 0, 3 do
  371.       for dx = 0, 3 do
  372.         set(x + dx, BOUNDS.Y + dy, z + dz, chunk[index])
  373.         index = index + 1
  374.       end
  375.     end
  376.   end
  377.   if chunks[num] == nil then unscanned = unscanned - 1 end
  378.   chunks[num] = true
  379. end
  380. function scanChunk(chunk)
  381.   local x, z, num = BOUNDS.X + 10 + chunk.x * 4, BOUNDS.Z + 10 + chunk.z * 4, chunk.z * 7 + chunk.x
  382.   -- check if we need to scan this chunk
  383.   if chunks[num] == nil then
  384.     -- check if the chunk can be scanned at all
  385.     local offx, offz, offy = x - pos.x, z - pos.z, BOUNDS.Y - pos.y + 1
  386.     if math.abs(offx) <= 32 and math.abs(offz) <= 32 then
  387.       -- perform scanning
  388.       local data = geo.scan(offx, offz, offy, 4, 4, 4)
  389.       -- fill in the data
  390.       fillChunk(x, z, data, num)
  391.       -- send scanned chunk to teammates
  392.       modem.broadcast(INTERNALPORT, "chunk", x, z, serial.serialize(data), num)
  393.     end
  394.   end
  395. end
  396. function unscannedChunk()
  397.   local selector = math.random(1, unscanned)
  398.   for z = 0, 6 do
  399.     for x = 0, 6 do
  400.       if chunks[z * 7 + x] == nil then selector = selector - 1 end
  401.       if selector <= 0 then return { x = x, z = z } end
  402.     end
  403.   end
  404.   return nil
  405. end
  406.  
  407. -- movement
  408. function randomMove()
  409.   local selector = math.random(1, 6)
  410.   if selector == 1 then up()
  411.   elseif selector == 2 then down()
  412.   elseif selector == 3 then forward()
  413.   elseif selector == 4 then back()
  414.   elseif selector == 5 then turnLeft(); forward()
  415.   else turnRight(); forward() end
  416. end
  417.  
  418. function checkBlock(x, y, z)
  419.   local block = get(x, y, z)
  420.   if block == ENEMY then return fire(x, y, z)
  421.   else return block == 0 end
  422. end
  423. function move(side)
  424.   -- check location first
  425.   local check = false
  426.   if side == sides.up then check = checkBlock(pos.x, pos.y + 1, pos.z)
  427.   elseif side == sides.down then check = checkBlock(pos.x, pos.y - 1, pos.z)
  428.   elseif side == sides.south then check = checkBlock(pos.x, pos.y, pos.z + 1)
  429.   elseif side == sides.north then check = checkBlock(pos.x, pos.y, pos.z - 1)
  430.   elseif side == sides.west then check = checkBlock(pos.x - 1, pos.y, pos.z)
  431.   else check = checkBlock(pos.x + 1, pos.y, pos.z) end
  432.   -- move!
  433.   if check then
  434.     if side ~= sides.up and side ~= sides.down then
  435.       turn(side)
  436.     end
  437.     if side == sides.up then return up()
  438.     elseif side == sides.down then return down()
  439.     else return forward() end
  440.   end
  441. end
  442.  
  443. local point = nil
  444. function pointMove()
  445.   -- generate new point
  446.   if point == nil or (point.x == pos.x and point.y == pos.y and point.z == pos.z) then
  447.     while true do
  448.       local x = math.random(BOUNDS.X + 1, BOUNDS.X + BOUNDS.W - 2)
  449.       local y = math.random(BOUNDS.Y + 1, BOUNDS.Y + BOUNDS.H - 2)
  450.       local z = math.random(BOUNDS.Z + 1, BOUNDS.Z + BOUNDS.D - 2)
  451.       if get(x, y, z) == 0 then point = { x = x, y = y, z = z }; break end
  452.     end
  453.   end
  454.   -- move to the point
  455.   local moved = false
  456.   if point.x < pos.x then moved = move(sides.west) end
  457.   if point.x > pos.x then moved = move(sides.east) end
  458.   if point.y < pos.y then moved = move(sides.down) end
  459.   if point.y > pos.y then moved = move(sides.up) end
  460.   if point.z < pos.z then moved = move(sides.north) end
  461.   if point.z > pos.z then moved = move(sides.south) end
  462.   -- if unreachable then change the point
  463.   if not moved then point = nil end
  464. end
  465.  
  466.  
  467. -- initialization
  468. local game, timeout = false, 2.0
  469. local byMyself, byTeammates = 0, 0
  470. -- set team colors
  471. plasma.setColor(COLORS[TEAM])
  472. colors.setColor(COLORS[TEAM])
  473. -- check current position
  474. fetchPosition()
  475. -- generate map by scheme
  476. initializeMap()
  477. -- open ports
  478. modem.setStrength(MODEMSTRENGTH)
  479. modem.open(SERVERPORT)
  480. modem.open(INTERNALPORT)
  481. -- subscribe to events
  482. event.listen("modem_message", function(name, address, sender, port, _, a, b, c, d, e)
  483.   if port == SERVERPORT then
  484.     if not game and a == "gamestart" then
  485.       SERVERADDRESS = sender; game = true; timeout = 0.01
  486.       print("Game started!")
  487.     end
  488.     if sender == SERVERADDRESS then
  489.       if a == "gamestop" then
  490.         game = false; timeout = 2.0
  491.         print("Game stopped!")
  492.         print("SQUAD: " .. TEAM .. "-" .. TEAMSIZE)
  493.         print("CODENAME: " .. CODENAME)
  494.         print("Waiting...")
  495.       elseif a == "whoalive" then
  496.         modem.send(SERVERADDRESS, SERVERPORT, "i'm alive", TEAM, pos.x, pos.y, pos.z)
  497.       end
  498.     end
  499.   elseif port == INTERNALPORT then
  500.     -- get info about teammate position
  501.     if a == "position" then
  502.       if mates[b] == nil then mates[b] = {} end
  503.       mates[b].x = c
  504.       mates[b].y = d
  505.       mates[b].z = e
  506.       print("Mate moved: ", c, d, e)
  507.     -- get new chunk of map
  508.     elseif a == "chunk" then
  509.       fillChunk(b, c, serial.unserialize(d), e)
  510.       byTeammates = byTeammates + 1
  511.       print("Got chunk #" .. e .. " from teammate.")
  512.       print("(Left " .. unscanned .. "/49, scanned: " .. byMyself .. "m, " .. byTeammates .. "t)")
  513.     -- teammates have destroyed one target
  514.     elseif a == "hit" then
  515.       set(b, c, d, 0)
  516.     end
  517.   end
  518. end)
  519.  
  520.  
  521. -- let's go!
  522. print("SQUAD: " .. TEAM .. "-" .. TEAMSIZE)
  523. print("CODENAME: " .. CODENAME)
  524. print("Waiting...")
  525.  
  526. while true do
  527.   -- send my position to teammates
  528.   if pos.changed then
  529.     modem.broadcast(INTERNALPORT, "position", CODENAME, pos.x, pos.y, pos.z)
  530.     pos.changed = false
  531.   end
  532.   -- process event queue
  533.   os.sleep(timeout)
  534.   -- do something else
  535.   if game then
  536.     -- select next action
  537.     local selector = math.random(1, 6)
  538.     -- move to new position
  539.     if selector == 1 then
  540.       pointMove()
  541.     -- scan chunk of map
  542.     elseif selector == 2 and unscanned > 0 then
  543.       local chunk = unscannedChunk()
  544.       scanChunk(chunk)
  545.       byMyself = byMyself + 1
  546.       print("Scanned chunk #" .. (chunk.z * 7 + chunk.x) .. ".")
  547.       print("(Left " .. unscanned .. "/49, scanned: " .. byMyself .. "m, " .. byTeammates .. "t)")
  548.     -- fire!
  549.     elseif selector == 3 then
  550.       local targets = detectEnemies()
  551.       local shooted = false
  552.       if #targets > 0 then
  553.         -- try to shoot one of targets
  554.         for k, v in pairs(targets) do
  555.           if fire(v.x, v.y, v.z) then shooted = true; break end
  556.         end
  557.       end
  558.       if not shooted then
  559.         -- take some random shots
  560.         while true do
  561.           local x = math.random(BOUNDS.X + 1, BOUNDS.X + BOUNDS.W - 2)
  562.           local y = math.random(BOUNDS.Y + 1, BOUNDS.Y + BOUNDS.H - 2)
  563.           local z = math.random(BOUNDS.Z + 1, BOUNDS.Z + BOUNDS.D - 2)
  564.           if fire(x, y, z) then break end
  565.         end
  566.       end
  567.     end
  568.   end
  569. end
  570.  
  571. -- finalize
  572. modem.close()
  573. print("Game over.")
RAW Paste Data