LaserGallery

From BerryBots Wiki
Jump to: navigation, search
lasergallery.png
BerryBots
See a replay of LaserGallery in action: click here to watch

A sample stage packaged with BerryBots as sample/lasergallery.lua. The player ship is stationary with only its laser gun. It faces five movement-only ships, one at a time, with increasingly difficult to hit movements. The goal is to destroy all five ships as quickly as possible.

BasicBattler is the only sample ship that can hit all 5 movements.

Top scores at LaserGallery/Scores.

Source code

lasergallery.lua

-- A single player targeting challenge. The player ship is stationary and can
-- only shoot its laser gun. A series of ships with different movement styles
-- appear and move around until they are destroyed. The goal is to destroy all
-- ships as quickly as possible.
--
-- BasicBattler is the only sample ship that can hit all five movements.
 
require "samplestage"
 
PLAYER_LOCATION = {x = 100, y = 400}
MAX_TIME = 100000
 
function configure(stageBuilder)
  stageBuilder:setSize(1200, 800)
  stageBuilder:addStart(PLAYER_LOCATION.x, PLAYER_LOCATION.y)
  stageBuilder:addShip("gallerybots/paces.lua")
  stageBuilder:addShip("gallerybots/coasty.lua")
  stageBuilder:addShip("gallerybots/wallsy.lua")
  stageBuilder:addShip("gallerybots/lines.lua")
  stageBuilder:addShip("gallerybots/swirls.lua")
end
 
ships = nil
userShip = nil
world = nil
admin = nil
stageShips = { }
targetShip = nil
stageShipIndex = 1
numGalleryShips = 0
printShipTimer = 90
 
function init(shipsArg, worldArg, adminArg)
  ships = shipsArg
  world = worldArg
  admin = adminArg
 
  for i, ship in ipairs(shipsArg) do
    if (ship:isStageShip()) then
      table.insert(stageShips, ship)
      admin:setShipEnergyEnabled(ship, true)
      admin:setShipShowName(ship, false)
      numGalleryShips = numGalleryShips + 1
    elseif (userShip == nil) then
      userShip = ship
    end
  end
 
  admin:setShipLaserEnabled(userShip, true)
  admin:setShipThrusterEnabled(userShip, false)
  admin:setShipTorpedoEnabled(userShip, false)
  admin:setShipEnergyEnabled(userShip, false)
 
  targetShip = stageShips[1]
  admin:reviveShip(targetShip)
  admin:setShipShowName(targetShip, true)
  local targetLocation = targetStartLocation()
  admin:moveShip(targetShip, targetLocation.x, targetLocation.y)
end
 
function run(stageSensors)
  samplestage.checkSinglePlayer(ships, admin)
 
  admin:drawText("Time: " .. world:time(), 20, 15)
  if (printShipTimer > 0) then
    admin:drawText(targetShip:name(), 525, 30, 25)
    printShipTimer = printShipTimer - 1
  end
  admin:moveShip(userShip, PLAYER_LOCATION.x, PLAYER_LOCATION.y)
  if (not targetShip:alive()) then
    stageShipIndex = stageShipIndex + 1
    if (stageShipIndex > numGalleryShips) then
      admin:setWinner(userShip)
      admin:gameOver()
      print("Time: " .. world:time())
      admin:setStatistic(userShip, "Time", world:time())
    else
      admin:setShipShowName(targetShip, false)
      targetShip = stageShips[stageShipIndex]
      admin:roundOver()
      admin:reviveShip(targetShip)
      admin:setShipShowName(targetShip, true)
      local targetLocation = targetStartLocation()
      admin:moveShip(targetShip, targetLocation.x, targetLocation.y)
      printShipTimer = 90
    end
  end
  if (world:time() > MAX_TIME) then
    admin:gameOver()
  end
end
 
function targetStartLocation()
  return {x = 950 + (math.random() * 100), y = 16 + (math.random() * 768)}
end

paces.lua

-- A target ship for the sample.lasergallery targeting challenge stage.
-- Moves back and forth in a small range.
 
ship = nil
world = nil
angle = math.random() * 2 * math.pi
 
function init(shipArg, worldArg)
  ship = shipArg
  world = worldArg
  ship:setName("Paces")
  ship:setShipColor(255, 255, 255)
  ship:setLaserColor(255, 255, 255)
  ship:setThrusterColor(255, 255, 255)
end
 
function run(enemyShips)
  time = world:time()
 
  if (time % 20 == 0) then
    angle = (angle + math.pi) % (2 * math.pi)
  end
  if (time % 20 < 10) then
    ship:fireThruster(ship:heading() + math.pi, ship:speed())
  else
    ship:fireThruster(angle, 1)
  end
end

coasty.lua

-- A target ship for the sample.lasergallery targeting challenge stage.
-- Moves up and down at constant speed, reversing direction when it hits a wall.
 
MAX_SPEED = 10
 
ship = nil
world = nil
constants = nil
direction = math.pi / 2
 
function init(shipArg, worldArg)
  ship = shipArg
  world = worldArg
  constants = world:constants()
  ship:setName("Coasty")
  ship:setShipColor(255, 255, 35)
  ship:setLaserColor(255, 255, 35)
  ship:setThrusterColor(255, 255, 35)
end
 
function run(enemyShips, sensors)
  if (# sensors:hitWallEvents() > 0) then
    direction = (direction + math.pi) % (2 * math.pi)
  end
  ship:fireThruster(direction, MAX_SPEED - ship:speed())
end

wallsy.lua

-- A target ship for the sample.lasergallery targeting challenge stage.
-- Moves along walls without getting too close.
 
MAX_SPEED = 12
 
EAST = 0
NORTH = 1
WEST = 2
SOUTH = 3
 
ship = nil
world = nil
constants = nil
orientation = 1
targetWall = NORTH
 
function init(shipsArg, worldArg)
  ship = shipsArg
  world = worldArg
  constants = world:constants()
  ship:setName("Wallsy")
  ship:setShipColor(25, 255, 25)
  ship:setLaserColor(25, 255, 25)
  ship:setThrusterColor(25, 255, 25)
 
  if (math.random() < 0.5) then
    orientation = -1
  end
  if (math.random() < 0.5) then
    targetWall = SOUTH
  end
end
 
function run(enemyShips, sensors)
  if (atWall(targetWall)) then
    if (targetWall == WEST) then
      print("Reverse from " .. orientation)
      orientation = orientation * -1
    end
    targetWall = nextWall(targetWall)
  else
    moveToWall(targetWall)
  end
end
 
function nextWall(wall)
  if (wall == WEST) then
    return EAST
  else
    if (wall == EAST) then
      if (orientation == 1) then
        return SOUTH
      else
        return NORTH
      end
    elseif ((wall == NORTH and orientation == -1)
            or (wall == SOUTH and orientation == 1)) then
      return WEST
    elseif ((wall == SOUTH and orientation == -1)
            or (wall == NORTH and orientation == 1)) then
      return EAST
    else
      print("Don't understand nextWall: " .. wall .. " / " .. orientation)
      return EAST
    end
  end
end
 
function atWall(wall)
  if (ship:speed() < 0.0001) then
    if ((wall == NORTH and ship:y() > world:height() - 20)
        or (wall == EAST and ship:x() > world:width() - 20)
        or (wall == SOUTH and ship:y() < 20)
        or (wall == WEST and ship:x() < 670)) then
      return true
    end
  end
  return false
end
 
function moveToWall(wall)
  if (atWall(wall)) then
    wall = nextWall
  else
    local accelDistance = minDistanceForSpeed(math.max(0, ship:speed() + 1))
    local coastDistance = minDistanceForSpeed(math.max(0, ship:speed()))
    local wallDistance = distanceToWall(wall)
    if (accelDistance < wallDistance) then
      -- accelerate
      ship:fireThruster(wallBearing(wall), MAX_SPEED - ship:speed())
    elseif (coastDistance < wallDistance) then
      -- coast
    else
      -- stop
      ship:fireThruster(ship:heading() + math.pi, ship:speed())
    end
  end
end
 
function distanceToWall(wall)
  if (wall == NORTH) then
    return world:height() - constants.SHIP_RADIUS - ship:y()
  elseif (wall == EAST) then
    return world:width() - constants.SHIP_RADIUS - ship:x()
  elseif (wall == SOUTH) then
    return ship:y() - constants.SHIP_RADIUS
  elseif (wall == WEST) then
    return ship:x() - constants.SHIP_RADIUS - 650
  else
    print("Don't understand wall: " .. wall)
    return 0
  end
end
 
function wallBearing(wall)
  return (wall * math.pi / 2)
end
 
function minDistanceForSpeed(initialSpeed)
  local minTicks = math.ceil(initialSpeed)
  return minTicks * ((initialSpeed + 1) / 2)
end

lines.lua

-- A target ship for the sample.lasergallery targeting challenge stage.
-- Moves randomly in long straight lines.
 
MAX_SPEED = 15
 
ship = nil
world = nil
destination = nil
 
function init(shipsArg, worldArg)
  ship = shipsArg
  world = worldArg
 
  ship:setName("Lines")
  ship:setShipColor(150, 0, 175)
  ship:setLaserColor(150, 0, 175)
  ship:setThrusterColor(150, 0, 175)
end
 
function run(enemyShips, sensors)
  if (ship:speed() < 0.0001 or destination == nil) then
    destination = {x = 650 + (math.random() * 484),
                   y = 16 + (math.random() * 768)}
  end
 
  local distanceToDestination = math.sqrt(square(destination.x - ship:x())
      + square(destination.y - ship:y()))
  local maxSpeed = math.min(MAX_SPEED, getMaxVelocity(distanceToDestination))
  local bearingToDestination =
      math.atan2(destination.y - ship:y(), destination.x - ship:x())
 
  if (distanceToDestination < 1) then
    ship:fireThruster(ship:heading() + math.pi, ship:speed())
  else
    local goBearing
    local force
    if (ship:speed() > maxSpeed) then
      goBearing = bearingToDestination + math.pi
      force = ship:speed() - maxSpeed
    else
      goBearing = bearingToDestination
      force = maxSpeed - ship:speed()
    end
    ship:fireThruster(goBearing, force)
  end
end
 
function getMaxVelocity(distance)
  local decelTime = decelTime(distance)
  local decelDist = (decelTime / 2.0) * (decelTime - 1) -- sum of 0..(decelTime-1)
  return (decelTime - 1) + ((distance - decelDist) / decelTime)
end
 
function decelTime(distance)
  local x = 1
  while (true) do
    -- (square(x) + x) / 2) = 1, 3, 6, 10, 15...
    if (distance <= ((square(x) + x) / 2)) then
      return x
    end
    x = x + 1
  end
  return -1
end
 
function square(x)
  return x * x
end

swirls.lua

-- A target ship for the sample.lasergallery targeting challenge stage.
-- Moves in big arcs without getting too close.
 
MAX_SPEED = 12
 
ship = nil
world = nil
constants = nil
orientation = 1
 
function init(shipArg, worldArg)
  ship = shipArg
  world = worldArg
  constants = world:constants()
  ship:setName("Swirls")
  ship:setShipColor(80, 80, 80)
  ship:setLaserColor(80, 80, 80)
  ship:setThrusterColor(80, 80, 80)
end
 
function run(enemyShips, sensors)
  if (ship:hitWall() and math.random() < 0.5) then
    orientation = orientation * -1
  end
  if (ship:x() < 650) then
    ship:fireThruster(0, 1)
    if (math.random() < 0.5) then
      orientation = orientation * -1
    end
  elseif (ship:speed() < 0.0001) then
    ship:fireThruster(math.random() * 2 * math.pi, 1)
  elseif (ship:speed() < MAX_SPEED - 0.5) then
    ship:fireThruster(ship:heading(), MAX_SPEED - ship:speed())
  else
    ship:fireThruster(ship:heading() + (math.pi * orientation * .5), 1)
  end
end
Personal tools
Namespaces

Variants
Actions
Navigation
Toolbox