Last active
August 29, 2015 14:10
-
-
Save fimmtiu/6556c5518e5af1837079 to your computer and use it in GitHub Desktop.
shazbot
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
TICKS_PER_SECOND = 60 | |
# A heading modified with a certain amount of randomness. | |
def heading_with_slop(heading, sloppiness_radians) | |
heading = heading.radians if heading.respond_to?(:radians) | |
left_side = heading - sloppiness_radians / 2 | |
RTanque::Heading.new(left_side + rand * sloppiness_radians) | |
end | |
# A data structore for keeping track of radar contacts. | |
class Contact | |
CONTACT_EXPIRY = 3.0 | |
DISTANCE_FACTOR = 100 | |
extend Forwardable | |
attr_accessor :reflection, :time | |
def_delegators :@reflection, :heading, :distance, :name | |
def initialize(reflection, time) | |
@reflection = reflection | |
@time = time | |
end | |
def radar_heading(current_time) | |
heading_with_slop(heading, radar_slop(current_time)) | |
end | |
# Increase the cone based on the distance to the target and how | |
# long it's been since we spotted it. | |
def radar_slop(current_time) | |
distance_adjustment = distance / DISTANCE_FACTOR | |
Math::PI / (15 - seconds_since_contact(current_time) + distance_adjustment) | |
end | |
def seconds_since_contact(current_time) | |
(current_time - time) / TICKS_PER_SECOND | |
end | |
def expired?(current_time) | |
seconds_since_contact(current_time) > CONTACT_EXPIRY | |
end | |
end | |
class Shazbot < RTanque::Bot::Brain | |
SENSOR_REFRESH_INTERVAL_SECONDS = 0.5 | |
SENSOR_REFRESH_INTERVAL = (SENSOR_REFRESH_INTERVAL_SECONDS * TICKS_PER_SECOND).to_i | |
HEADING_REFRESH_INTERVAL_SECONDS = 1 | |
HEADING_REFRESH_INTERVAL = (HEADING_REFRESH_INTERVAL_SECONDS * TICKS_PER_SECOND).to_i | |
PRECISION_DISTANCE = 450 | |
NAME = 'shazbot' | |
include RTanque::Bot::BrainHelper | |
attr_accessor :contacts, :radar_heading_target, :direction | |
def tick! | |
update_contacts | |
sense_stuff | |
shoot_stuff | |
run_around | |
end | |
def random_heading | |
RTanque::Heading.rand | |
end | |
# If we pick a new heading for the radar too often, it just twitches madly in place. We only want | |
# to do so every half-second or so. | |
def move_radar? | |
radar_heading_target.nil? || closest_contact || sensors.ticks % SENSOR_REFRESH_INTERVAL == 0 | |
end | |
# Every second, there's a 50% change that we'll change the direction of our forward curve. | |
def change_heading? | |
sensors.ticks % HEADING_REFRESH_INTERVAL == 0 && rand(2) == 0 | |
end | |
# Update our internal list of radar contacts. | |
def update_contacts | |
@contacts ||= [] | |
contacts.reject! {|c| c.expired?(sensors.ticks) } | |
sensors.radar.each do |scanned_bot| | |
next if scanned_bot.name == NAME # Ignore friendly tanks | |
contacts.reject! {|c| c.name == scanned_bot.name } | |
contacts << Contact.new(scanned_bot, sensors.ticks) | |
end | |
end | |
def closest_contact | |
contacts.sort_by(&:distance).first | |
end | |
# Returns which edge of the map we're currently touching. | |
def touching_edge | |
return RTanque::Heading::NORTH if sensors.position.y == arena.height | |
return RTanque::Heading::SOUTH if sensors.position.y == 0 | |
return RTanque::Heading::EAST if sensors.position.x == arena.width | |
return RTanque::Heading::WEST if sensors.position.x == 0 | |
nil | |
end | |
def within_90_degrees?(a, b) | |
a.delta(b) < Math::PI / 2 | |
end | |
# We try not to drive or point the radar off the edge of the screen, because that's useless. | |
def crappy_heading?(heading) | |
edge = touching_edge or return false | |
within_90_degrees?(heading, edge) | |
end | |
# We start by sensing in random directions. If we have a contact, we scan in a | |
# PI/(10 - seconds since last seen) cone around them. | |
def sense_stuff | |
if move_radar? | |
if closest_contact | |
self.radar_heading_target = closest_contact.radar_heading(sensors.ticks) | |
else | |
begin | |
self.radar_heading_target = random_heading | |
end while crappy_heading?(radar_heading_target) | |
end | |
end | |
command.radar_heading = radar_heading_target | |
end | |
# Shoot directly at the target, at maximum power. | |
def shoot_precisely | |
command.turret_heading = closest_contact.heading | |
command.fire(31337) | |
end | |
# Spray rapidly in a cone around the target, at minimum power. | |
def shoot_randomly | |
command.turret_heading = radar_heading_target | |
command.fire(1) | |
end | |
def shoot_stuff | |
if closest_contact && closest_contact.distance < PRECISION_DISTANCE | |
shoot_precisely | |
else | |
shoot_randomly | |
end | |
end | |
# Go in a curve, randomly changing directions once in a while. | |
# Immediately come about if you run into the edge of the map. | |
def run_around | |
@direction ||= 1 | |
if change_heading? | |
if crappy_heading?(sensors.heading) | |
command.heading = -sensors.heading | |
else | |
self.direction = -direction | |
end | |
end | |
command.heading = sensors.heading + (Math::PI / 25) * direction | |
command.speed = 11 # This one goes up to 11. | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment