Last active
December 17, 2015 14:29
-
-
Save chingor13/5624572 to your computer and use it in GitHub Desktop.
RTanque
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
class TooMuchCode < RTanque::Bot::Brain | |
NAME = 'too_much_code' | |
include RTanque::Bot::BrainHelper | |
def tick! | |
@game_mode ||= :one_on_one | |
game_mode_detect! | |
damage_detection! | |
if game_mode_known? | |
if melee? | |
melee_tick! | |
else | |
one_on_one_tick! | |
end | |
else | |
determine_game_mode! | |
end | |
face_yell! | |
end | |
def melee_tick! | |
acquire_target! | |
follow_target! | |
record_target_position! | |
avoid_center! | |
predictive_targetting! | |
safe_firing! | |
# default to spin | |
command.radar_heading ||= sensors.radar_heading - RTanque::Heading::EIGHTH_ANGLE | |
command.turret_heading ||= command.radar_heading | |
end | |
def one_on_one_tick! | |
# radar | |
acquire_target! | |
follow_target! | |
record_target_position! | |
random_location! | |
# turret / gun | |
predictive_targetting! | |
naive_firing! | |
# default to spin | |
command.radar_heading ||= sensors.radar_heading - RTanque::Heading::EIGHTH_ANGLE | |
command.turret_heading ||= command.radar_heading | |
end | |
protected | |
def log(message) | |
#"#{self.class.const_get('NAME')} (#{sensors.ticks}): #{message}" | |
end | |
def bound_value(val, min, max) | |
[[max, val].min, min].max | |
end | |
def bot_count | |
count = 0 | |
sensors.radar.each do |bot| | |
count += 1 | |
end | |
count | |
end | |
def near_edge?(distance = 100) | |
near_top?(distance) || | |
near_bottom?(distance) || | |
near_left?(distance) || | |
near_right?(distance) | |
end | |
def near_top?(distance = 100) | |
sensors.position.y >= arena.height - distance | |
end | |
def near_bottom?(distance = 100) | |
sensors.position.y <= distance | |
end | |
def near_right?(distance = 100) | |
sensors.position.x >= arena.width - distance | |
end | |
def near_left?(distance = 100) | |
sensors.position.x <= distance | |
end | |
def northish?(heading) | |
Math.sin(heading) < 0 | |
end | |
def southish?(heading) | |
Math.sin(heading) > 0 | |
end | |
def westish?(heading) | |
Math.cos(heading) > 0 | |
end | |
def eastish?(heading) | |
Math.cos(heading) < 0 | |
end | |
def calculate_position(start_position, heading, distance, heading_delta = 0, ticks = 1) | |
pos = start_position | |
ticks.times do | |
pos = RTanque::Point.new( | |
pos.x + Math.sin(heading) * distance, | |
pos.y + Math.cos(heading) * distance, | |
self.arena | |
) | |
heading += heading_delta | |
end | |
pos | |
end | |
def nearest_corner(offset = 0) | |
if sensors.position.x > arena.width / 2 | |
x = arena.width - offset | |
else | |
x = offset | |
end | |
if sensors.position.y > arena.height / 2 | |
y = arena.height - offset | |
else | |
y = offset | |
end | |
RTanque::Point.new(x, y, arena) | |
end | |
def my_name | |
self.class.const_get("NAME") | |
end | |
def nearest_enemy | |
sensors.radar.sort_by(&:distance).reject{|bot| bot.name == my_name}.first | |
end | |
BULLET_SPEED = { | |
1 => 4.65, | |
2 => 9.23, | |
3 => 13.79, | |
4 => 18.18, | |
5 => 22.64 | |
} | |
def calculate_ticks_until_hit(distance, fire_power) | |
(distance / BULLET_SPEED[bound_value(fire_power, 0, 5)]).floor | |
end | |
module ClassMethods | |
def send(*args, &block) | |
#puts "#{caller[0].split(":").first} is trying to hack me!!!" | |
end | |
end | |
def self.included(klass) | |
klass.extend(ClassMethods) | |
end | |
STORAGE_SIZE = 20 | |
def store!(type, value, tick = nil) | |
tick ||= sensors.ticks | |
@storage ||= {} | |
@storage[type] ||= Array.new(STORAGE_SIZE) | |
@storage[type][tick % STORAGE_SIZE] = value | |
end | |
def clear_store!(type) | |
@storage ||= {} | |
@storage[type] = Array.new(STORAGE_SIZE) | |
end | |
def fetch(type, tick = nil) | |
tick ||= sensors.ticks | |
@storage ||= {} | |
@storage[type] ||= Array.new(STORAGE_SIZE) | |
@storage[type][tick % STORAGE_SIZE] | |
end | |
def fetch_all(type) | |
@storage ||= {} | |
@storage[type] ||= Array.new(STORAGE_SIZE) | |
end | |
def fetch_relative(type, relative) | |
fetch(type, sensors.ticks + relative) | |
end | |
def every_x_ticks(x, &block) | |
if sensors.ticks % x.to_i == 0 | |
yield(block) | |
end | |
end | |
def random_location! | |
pick_spot! | |
turn_to_spot! | |
end | |
def at_spot? | |
return false if @spot.nil? | |
sensors.position.within_radius?(@spot, 10) | |
end | |
def time_for_new_spot? | |
return false if @tick_count.nil? | |
@tick_count > 60 && rand(20) <= 1 | |
end | |
def pick_spot! | |
if at_spot? || time_for_new_spot? | |
@spot = nil | |
end | |
if @spot.nil? | |
@tick_count = 0 | |
@spot = RTanque::Point.new(rand(arena.width), rand(arena.height), arena) | |
end | |
@tick_count += 1 | |
end | |
def turn_to_spot! | |
command.heading ||= sensors.position.heading(@spot) | |
distance_to_spot = sensors.position.distance(@spot) | |
if distance_to_spot < 25 | |
command.speed = 1 | |
elsif distance_to_spot < 50 | |
command.speed = 2 | |
else | |
command.speed = 3 | |
end | |
end | |
def random_heading! | |
pick_heading! | |
turn_to_heading! | |
end | |
def pick_heading! | |
if time_for_new_heading? | |
@heading = nil | |
end | |
if @heading.nil? | |
#log "picking new heading" | |
@heading_tick_count = 0 | |
random_change = RTanque::Heading.new_from_degrees(rand(60) + 30) # 30-90 degrees | |
@heading = sensors.heading + ([true,false].sample ? random_change : -1 * random_change).to_f | |
end | |
@heading_tick_count += 1 | |
end | |
def turn_to_heading! | |
command.heading = @heading | |
command.speed = 3 | |
end | |
def time_for_new_heading? | |
return false if @heading_tick_count.nil? | |
@heading_tick_count > 60 && rand(20) <= 1 | |
end | |
def target | |
@target | |
end | |
def last_target_name | |
@last_target_name | |
end | |
def nearest_target | |
sensors.radar.select{|t| t.name != self.class.const_get('NAME')}.sort_by(&:distance).first | |
end | |
def nearest_target_named(name) | |
sensors.radar.select{|t| t.name == name}.sort_by(&:distance).first | |
end | |
def acquire_target! | |
@target = nearest_target | |
@last_target_name = @target.name if @target | |
end | |
def sticky_target! | |
@target = nearest_target_named(last_target_name) || | |
nearest_target | |
@last_target_name = @target.name if @target | |
end | |
def follow_target! | |
if target | |
command.radar_heading = target.heading | |
command.turret_heading = target.heading | |
end | |
end | |
def predictive_targetting! | |
return unless target | |
if target_heading | |
ticks_until_bullet_hits = calculate_ticks_until_hit(target.distance, 5) | |
target_expected_location = calculate_position( | |
target_position, | |
target_heading, | |
target_speed, | |
target_heading_delta, | |
ticks_until_bullet_hits | |
) | |
store!(:expected_location, target_expected_location) | |
# figure out where the target will be next tick, and where we will be next tick | |
my_expected_location = calculate_position(self.sensors.position, | |
self.command.heading || self.sensors.heading, | |
self.command.speed || self.sensors.speed) | |
self.command.turret_heading = my_expected_location.heading(target_expected_location) | |
end | |
end | |
def naive_firing! | |
fire_when_ready! | |
end | |
def fire_when_ready! | |
if target && command.turret_heading | |
if (sensors.turret_heading.to_degrees - command.turret_heading.to_degrees).abs < 3 | |
if target.distance < 75 | |
command.fire(1) | |
else | |
# control your firepower | |
command.fire(5) | |
end | |
elsif sensors.gun_energy >= 5 | |
command.fire(1) | |
end | |
end | |
end | |
def run_away! | |
if target | |
#log "target is northish" if northish?(target.heading) | |
#log "target is southish" if southish?(target.heading) | |
#log "target is westish" if westish?(target.heading) | |
#log "target is eastish" if eastish?(target.heading) | |
command.heading ||= target.heading - RTanque::Heading::HALF_ANGLE | |
end | |
command.speed ||= 3 | |
end | |
def strafe! | |
if target | |
if near_top? | |
if westish?(target.heading) | |
strafe_left! | |
else | |
strafe_right! | |
end | |
elsif near_bottom? | |
if westish?(target.heading) | |
strafe_right! | |
else | |
strafe_left! | |
end | |
elsif near_left? | |
if northish?(target.heading) | |
strafe_right! | |
else | |
strafe_left! | |
end | |
elsif near_right? | |
if northish?(target.heading) | |
strafe_left! | |
else | |
strafe_right! | |
end | |
else | |
strafe_left! | |
end | |
end | |
command.speed = 3 | |
end | |
def strafe_left!(offset = 0) | |
command.heading = target.heading - (Math::PI/2 + offset) | |
end | |
def strafe_right!(offset = 0) | |
command.heading = target.heading + (Math::PI/2 + offset) | |
end | |
def charge! | |
if target | |
self.command.heading = target.heading | |
end | |
end | |
def damage_detection! | |
@previous_health ||= 100 | |
if sensors.health < @previous_health | |
# log("damage taken: #{@previous_health - sensors.health}") | |
damage_taken << [sensors.ticks, @previous_health - sensors.health, sensors.health] | |
end | |
@previous_health = sensors.health | |
end | |
def damage_taken? | |
damage_taken.last && damage_taken.last.first == sensors.ticks | |
end | |
def damage_taken | |
@damage_taken ||= [] | |
end | |
def game_mode_detect! | |
if target.nil? | |
@bots_seen = {} | |
@game_mode = nil | |
end | |
@bots_seen ||= {} | |
sensors.radar.each do |reflection| | |
@bots_seen[reflection.name] = true | |
end | |
if @bots_seen.size > 1 | |
@game_mode = :melee | |
end | |
# after x ticks, default to one_on_one | |
@game_mode ||= :one_on_one if sensors.ticks > 60 | |
end | |
def determine_game_mode! | |
# if undetermined game type, figure it out | |
unless game_mode_known? | |
# find nearest corner, go there | |
@nearest_corner ||= nearest_corner(100) | |
@starting_radar_heading ||= sensors.radar_heading | |
command.heading = RTanque::Heading.new_between_points(sensors.position, @nearest_corner) | |
command.speed = 3 | |
command.radar_heading = sensors.radar_heading - RTanque::Heading::EIGHTH_ANGLE | |
if nearest = nearest_enemy | |
command.turret_heading = nearest.heading | |
end | |
command.turret_heading ||= command.radar_heading | |
end | |
end | |
def melee? | |
@game_mode == :melee | |
end | |
def one_on_one? | |
@game_mode == :one_on_one | |
end | |
def game_mode_known? | |
!!@game_mode | |
end | |
def reset_game_mode! | |
@game_mode = nil | |
end | |
def record_target_position! | |
if target | |
if @target_name != target.name | |
# new target | |
clear_store!(:target_distance) | |
clear_store!(:target_position) | |
clear_store!(:target_heading) | |
clear_store!(:target_speed) | |
clear_store!(:target_heading_delta) | |
clear_store!(:target_speed_delta) | |
@target_name = target.name | |
end | |
store!(:target_distance, target.distance) | |
store!(:target_position, calculate_position(sensors.position, target.heading, target.distance)) | |
if target_last_position | |
store!(:target_heading, RTanque::Heading.new_between_points(target_last_position, target_position)) | |
store!(:target_speed, target_last_position.distance(target_position)) | |
if target_last_heading | |
store!(:target_heading_delta, target_last_heading.delta(target_heading)) | |
store!(:target_speed_delta, target_last_speed - target_speed) | |
end | |
end | |
else | |
# lost target | |
if target_last_position | |
if sensors.heading.delta(RTanque::Heading.new_between_points(sensors.position, target_last_position)) > 0 | |
command.heading = sensors.heading + Math::PI/4 | |
else | |
command.heading = sensors.heading - Math::PI/4 | |
end | |
end | |
end | |
end | |
def target_position | |
fetch(:target_position) | |
end | |
def target_last_position | |
fetch_relative(:target_position, -1) | |
end | |
def target_heading | |
fetch(:target_heading) | |
end | |
def target_last_heading | |
fetch_relative(:target_heading, -1) | |
end | |
def target_speed | |
fetch(:target_speed) | |
end | |
def target_last_speed | |
fetch_relative(:target_speed, -1) | |
end | |
def target_heading_delta | |
fetch(:target_heading_delta) | |
end | |
def target_last_heading_delta | |
fetch_relative(:target_heading_delta, -1) | |
end | |
def target_speed_delta | |
fetch(:target_speed_delta) | |
end | |
def target_last_speed_delta | |
fetch_relative(:target_speed_delta, -1) | |
end | |
def movement_type | |
if moving_linearly? | |
return :linear | |
elsif moving_circularly? | |
return :circular | |
else | |
return :erratic | |
end | |
end | |
def moving_linearly? | |
target_heading_delta && target_heading_delta < RTanque::Heading::ONE_DEGREE | |
end | |
def moving_circularly? | |
if target_heading_delta && target_last_heading_delta | |
(target_heading_delta - target_last_heading_delta).abs < 0.001 | |
end | |
end | |
def safe_firing! | |
sensors.radar.sort_by(&:distance).each do |bot| | |
if bot.name == my_name | |
return if will_hit?(bot) | |
else | |
naive_firing! | |
end | |
end | |
end | |
def will_hit?(reflection, power = 5) | |
reflection.heading.delta(sensors.turret_heading) < RTanque::Heading::ONE_DEGREE | |
end | |
def avoid_center! | |
if in_center? | |
log "in center" | |
turn_to_corner! | |
else | |
clear_corner! | |
pick_spot_in_margin! | |
turn_to_spot! | |
end | |
end | |
def turn_to_corner! | |
@corner ||= nearest_corner | |
command.heading = RTanque::Heading.new_between_points(sensors.position, @corner) | |
command.speed = 3 | |
end | |
def clear_corner! | |
@corner = nil | |
end | |
def pick_spot_in_margin!(percent = 25) | |
@spot ||= begin | |
log "picking spot in margin" | |
margin_width = arena.width / 100 * percent | |
margin_height = arena.width / 100 * percent | |
if sensors.position.x < margin_width | |
x = rand(margin_width) | |
else | |
x = arena.width - rand(margin_width) | |
end | |
if sensors.position.y < margin_height | |
y = rand(margin_height) | |
else | |
y = arena.height - rand(margin_width) | |
end | |
RTanque::Point.new(x, y, arena) | |
end | |
end | |
def in_center?(percent = 25) | |
x_margin = arena.width / 100.0 * percent | |
y_margin = arena.height / 100.0 * percent | |
sensors.position.x > x_margin && | |
sensors.position.x < arena.width - x_margin && | |
sensors.position.y > y_margin && | |
sensors.position.y < arena.height - y_margin | |
end | |
def face_yell! | |
if target | |
all = true | |
fetch_all(:target_distance).each{|dist| all &&= (dist && dist < 100)} | |
log "GET OUT OF MY FACE #{target.name}!!!!!!!" if all | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment