rtanque 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +22 -0
- data/.rspec +2 -0
- data/.rvmrc +34 -0
- data/.travis.yml +11 -0
- data/.yardopts +4 -0
- data/Gemfile +4 -0
- data/Gemfile.ci +6 -0
- data/LICENSE.txt +22 -0
- data/README.md +120 -0
- data/Rakefile +1 -0
- data/bin/rtanque +108 -0
- data/lib/rtanque.rb +40 -0
- data/lib/rtanque/arena.rb +8 -0
- data/lib/rtanque/bot.rb +117 -0
- data/lib/rtanque/bot/brain.rb +50 -0
- data/lib/rtanque/bot/brain_helper.rb +23 -0
- data/lib/rtanque/bot/command.rb +23 -0
- data/lib/rtanque/bot/radar.rb +54 -0
- data/lib/rtanque/bot/sensors.rb +33 -0
- data/lib/rtanque/bot/turret.rb +14 -0
- data/lib/rtanque/configuration.rb +46 -0
- data/lib/rtanque/explosion.rb +23 -0
- data/lib/rtanque/gui.rb +24 -0
- data/lib/rtanque/gui/bot.rb +42 -0
- data/lib/rtanque/gui/draw_group.rb +30 -0
- data/lib/rtanque/gui/explosion.rb +25 -0
- data/lib/rtanque/gui/shell.rb +20 -0
- data/lib/rtanque/gui/window.rb +51 -0
- data/lib/rtanque/heading.rb +172 -0
- data/lib/rtanque/match.rb +67 -0
- data/lib/rtanque/match/tick_group.rb +50 -0
- data/lib/rtanque/movable.rb +51 -0
- data/lib/rtanque/normalized_attr.rb +69 -0
- data/lib/rtanque/point.rb +140 -0
- data/lib/rtanque/runner.rb +88 -0
- data/lib/rtanque/shell.rb +40 -0
- data/lib/rtanque/version.rb +3 -0
- data/resources/images/body.png +0 -0
- data/resources/images/bullet.png +0 -0
- data/resources/images/explosions/explosion2-1.png +0 -0
- data/resources/images/explosions/explosion2-10.png +0 -0
- data/resources/images/explosions/explosion2-11.png +0 -0
- data/resources/images/explosions/explosion2-12.png +0 -0
- data/resources/images/explosions/explosion2-13.png +0 -0
- data/resources/images/explosions/explosion2-14.png +0 -0
- data/resources/images/explosions/explosion2-15.png +0 -0
- data/resources/images/explosions/explosion2-16.png +0 -0
- data/resources/images/explosions/explosion2-17.png +0 -0
- data/resources/images/explosions/explosion2-18.png +0 -0
- data/resources/images/explosions/explosion2-19.png +0 -0
- data/resources/images/explosions/explosion2-2.png +0 -0
- data/resources/images/explosions/explosion2-20.png +0 -0
- data/resources/images/explosions/explosion2-21.png +0 -0
- data/resources/images/explosions/explosion2-22.png +0 -0
- data/resources/images/explosions/explosion2-23.png +0 -0
- data/resources/images/explosions/explosion2-24.png +0 -0
- data/resources/images/explosions/explosion2-25.png +0 -0
- data/resources/images/explosions/explosion2-26.png +0 -0
- data/resources/images/explosions/explosion2-27.png +0 -0
- data/resources/images/explosions/explosion2-28.png +0 -0
- data/resources/images/explosions/explosion2-29.png +0 -0
- data/resources/images/explosions/explosion2-3.png +0 -0
- data/resources/images/explosions/explosion2-30.png +0 -0
- data/resources/images/explosions/explosion2-31.png +0 -0
- data/resources/images/explosions/explosion2-32.png +0 -0
- data/resources/images/explosions/explosion2-33.png +0 -0
- data/resources/images/explosions/explosion2-34.png +0 -0
- data/resources/images/explosions/explosion2-35.png +0 -0
- data/resources/images/explosions/explosion2-36.png +0 -0
- data/resources/images/explosions/explosion2-37.png +0 -0
- data/resources/images/explosions/explosion2-38.png +0 -0
- data/resources/images/explosions/explosion2-39.png +0 -0
- data/resources/images/explosions/explosion2-4.png +0 -0
- data/resources/images/explosions/explosion2-40.png +0 -0
- data/resources/images/explosions/explosion2-41.png +0 -0
- data/resources/images/explosions/explosion2-42.png +0 -0
- data/resources/images/explosions/explosion2-43.png +0 -0
- data/resources/images/explosions/explosion2-44.png +0 -0
- data/resources/images/explosions/explosion2-45.png +0 -0
- data/resources/images/explosions/explosion2-46.png +0 -0
- data/resources/images/explosions/explosion2-47.png +0 -0
- data/resources/images/explosions/explosion2-48.png +0 -0
- data/resources/images/explosions/explosion2-49.png +0 -0
- data/resources/images/explosions/explosion2-5.png +0 -0
- data/resources/images/explosions/explosion2-50.png +0 -0
- data/resources/images/explosions/explosion2-51.png +0 -0
- data/resources/images/explosions/explosion2-52.png +0 -0
- data/resources/images/explosions/explosion2-53.png +0 -0
- data/resources/images/explosions/explosion2-54.png +0 -0
- data/resources/images/explosions/explosion2-55.png +0 -0
- data/resources/images/explosions/explosion2-56.png +0 -0
- data/resources/images/explosions/explosion2-57.png +0 -0
- data/resources/images/explosions/explosion2-58.png +0 -0
- data/resources/images/explosions/explosion2-59.png +0 -0
- data/resources/images/explosions/explosion2-6.png +0 -0
- data/resources/images/explosions/explosion2-60.png +0 -0
- data/resources/images/explosions/explosion2-61.png +0 -0
- data/resources/images/explosions/explosion2-62.png +0 -0
- data/resources/images/explosions/explosion2-63.png +0 -0
- data/resources/images/explosions/explosion2-64.png +0 -0
- data/resources/images/explosions/explosion2-65.png +0 -0
- data/resources/images/explosions/explosion2-66.png +0 -0
- data/resources/images/explosions/explosion2-67.png +0 -0
- data/resources/images/explosions/explosion2-68.png +0 -0
- data/resources/images/explosions/explosion2-69.png +0 -0
- data/resources/images/explosions/explosion2-7.png +0 -0
- data/resources/images/explosions/explosion2-70.png +0 -0
- data/resources/images/explosions/explosion2-71.png +0 -0
- data/resources/images/explosions/explosion2-8.png +0 -0
- data/resources/images/explosions/explosion2-9.png +0 -0
- data/resources/images/grass.png +0 -0
- data/resources/images/radar.png +0 -0
- data/resources/images/turret.png +0 -0
- data/rtanque.gemspec +33 -0
- data/sample_bots/camper.rb +79 -0
- data/sample_bots/keyboard.rb +50 -0
- data/sample_bots/seek_and_destroy.rb +51 -0
- data/screenshots/battle_1.png +0 -0
- data/screenshots/battle_2.png +0 -0
- data/spec/rtanque/bot_spec.rb +239 -0
- data/spec/rtanque/heading_spec.rb +279 -0
- data/spec/rtanque/match_spec.rb +36 -0
- data/spec/rtanque/normalized_attr_spec.rb +90 -0
- data/spec/rtanque/point_spec.rb +196 -0
- data/spec/rtanque/radar_spec.rb +87 -0
- data/spec/rtanque/shell_spec.rb +35 -0
- data/spec/rtanque_spec.rb +6 -0
- data/spec/spec_helper.rb +51 -0
- data/templates/bot.erb +11 -0
- metadata +310 -0
@@ -0,0 +1,88 @@
|
|
1
|
+
# Add the working directory so that loading of bots works as expected
|
2
|
+
$LOAD_PATH << Dir.pwd
|
3
|
+
# Add the gem root dir so that sample_bots can be loaded
|
4
|
+
$LOAD_PATH << File.expand_path('../../../', __FILE__)
|
5
|
+
|
6
|
+
module RTanque
|
7
|
+
# Runner manages running an {RTanque::Match}
|
8
|
+
class Runner
|
9
|
+
LoadError = Class.new(::LoadError)
|
10
|
+
attr_reader :match
|
11
|
+
|
12
|
+
# @param [Integer] width
|
13
|
+
# @param [Integer] height
|
14
|
+
# @param [*match_args] args provided to {RTanque::Match#initialize}
|
15
|
+
def initialize(width, height, *match_args)
|
16
|
+
@match = RTanque::Match.new(RTanque::Arena.new(width, height), *match_args)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Attempts to load given {RTanque::Bot::Brain} given its path
|
20
|
+
# @param [String] brain_path
|
21
|
+
# @raise [RTanque::Runner::LoadError] if brain could not be loaded
|
22
|
+
def add_brain_path(brain_path)
|
23
|
+
parsed_path = self.parse_brain_path(brain_path)
|
24
|
+
bots = parsed_path.multiplier.times.map { self.new_bots_from_brain_path(parsed_path.path) }.flatten
|
25
|
+
self.match.add_bots(bots)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Starts the match
|
29
|
+
# @param [Boolean] gui if false, runs headless match
|
30
|
+
def start(gui = true)
|
31
|
+
if gui
|
32
|
+
require 'rtanque/gui'
|
33
|
+
window = RTanque::Gui::Window.new(self.match)
|
34
|
+
trap(:INT) { window.close }
|
35
|
+
window.show
|
36
|
+
else
|
37
|
+
trap(:INT) { self.match.stop }
|
38
|
+
self.match.start
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
protected
|
43
|
+
|
44
|
+
def new_bots_from_brain_path(brain_path)
|
45
|
+
self.fetch_brain_klasses(brain_path).map do |brain_klass|
|
46
|
+
RTanque::Bot.new_random_location(self.match.arena, brain_klass)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def fetch_brain_klasses(brain_path)
|
51
|
+
@load_brain_klass_cache ||= Hash.new do |cache, path|
|
52
|
+
cache[path] = self.get_diff_in_object_space(RTanque::Bot::Brain) {
|
53
|
+
begin
|
54
|
+
require(path)
|
55
|
+
rescue ::LoadError => e
|
56
|
+
raise LoadError, e.message
|
57
|
+
end
|
58
|
+
}
|
59
|
+
raise LoadError, "No class of type #{RTanque::Bot::Brain} found in #{path}" if cache[path].empty?
|
60
|
+
cache[path]
|
61
|
+
end
|
62
|
+
@load_brain_klass_cache[brain_path]
|
63
|
+
end
|
64
|
+
|
65
|
+
def get_diff_in_object_space(klass)
|
66
|
+
current_object_space = self.get_descendants_of_class(klass)
|
67
|
+
yield
|
68
|
+
self.get_descendants_of_class(klass) - current_object_space
|
69
|
+
end
|
70
|
+
|
71
|
+
def get_descendants_of_class(klass)
|
72
|
+
::ObjectSpace.each_object(::Class).select {|k| k < klass }
|
73
|
+
end
|
74
|
+
|
75
|
+
BRAIN_PATH_PARSER = /\A(.+?)\:[x|X](\d+)\z/
|
76
|
+
# @!visibility private
|
77
|
+
ParsedBrainPath = Struct.new(:path, :multiplier)
|
78
|
+
def parse_brain_path(brain_path)
|
79
|
+
path = brain_path.gsub('\.rb$', '')
|
80
|
+
multiplier = 1
|
81
|
+
brain_path.match(BRAIN_PATH_PARSER) { |m|
|
82
|
+
path = m[1]
|
83
|
+
multiplier = m[2].to_i
|
84
|
+
}
|
85
|
+
ParsedBrainPath.new(path, multiplier)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module RTanque
|
2
|
+
class Shell
|
3
|
+
include Movable
|
4
|
+
RATIO = Configuration.shell.ratio
|
5
|
+
SHELL_SPEED_FACTOR = Configuration.shell.speed_factor
|
6
|
+
attr_reader :bot, :arena, :fire_power
|
7
|
+
|
8
|
+
def initialize(bot, position, heading, fire_power)
|
9
|
+
@bot = bot
|
10
|
+
@arena = bot.arena
|
11
|
+
@fire_power = fire_power
|
12
|
+
self.position = position
|
13
|
+
self.heading = heading
|
14
|
+
self.speed = (fire_power * SHELL_SPEED_FACTOR) # TODO: add bot's relative speed in this heading
|
15
|
+
@dead = false
|
16
|
+
end
|
17
|
+
|
18
|
+
def bound_to_arena
|
19
|
+
false
|
20
|
+
end
|
21
|
+
|
22
|
+
def dead?
|
23
|
+
@dead ||= self.position.outside_arena?
|
24
|
+
end
|
25
|
+
|
26
|
+
def dead!
|
27
|
+
@dead = true
|
28
|
+
end
|
29
|
+
|
30
|
+
def hits(bots, &on_hit)
|
31
|
+
bots.each do |hit_bot|
|
32
|
+
if hit_bot.position.within_radius?(self.position, Bot::RADIUS)
|
33
|
+
self.dead!
|
34
|
+
on_hit.call(self.bot, hit_bot) if on_hit
|
35
|
+
break
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
data/rtanque.gemspec
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'rtanque/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "rtanque"
|
8
|
+
gem.version = RTanque::VERSION
|
9
|
+
gem.authors = ["Adam Williams"]
|
10
|
+
gem.email = ["pwnfactory@gmail.com"]
|
11
|
+
gem.summary = %q{RTanque is a game for programmers. Players program the brain of a tank and then send their tank into battle against other tanks.}
|
12
|
+
gem.description = %q{RTanque is a game for programmers. Players program the brain of a tank and then send their tank+brain into battle with other tanks. All tanks are otherwise equal.
|
13
|
+
|
14
|
+
Rules of the game are simple: Last bot standing wins. Gameplay is also pretty simple. Each tank has a base, turret and radar, each of which rotate independently. The base moves the tank, the turret has a gun mounted to it which can fire at other tanks, and the radar detects other tanks in its field of vision.
|
15
|
+
|
16
|
+
Have fun competing against friends' tanks or the sample ones included. Maybe you'll start a small league at your local Ruby meetup.}
|
17
|
+
gem.homepage = "https://github.com/awilliams/RTanque"
|
18
|
+
gem.license = 'MIT'
|
19
|
+
|
20
|
+
gem.files = `git ls-files`.split($/)
|
21
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
22
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
23
|
+
gem.require_paths = ["lib"]
|
24
|
+
|
25
|
+
gem.add_dependency 'gosu', '~> 0.7.45'
|
26
|
+
gem.add_dependency 'configuration', '~> 1.3.2'
|
27
|
+
gem.add_dependency 'octokit', '~> 1.23.0'
|
28
|
+
gem.add_dependency 'thor', '~> 0.17.0'
|
29
|
+
|
30
|
+
gem.add_development_dependency 'pry'
|
31
|
+
gem.add_development_dependency 'rspec', '~> 2.13.0'
|
32
|
+
gem.add_development_dependency 'rspec-mocks', '~> 2.13.0'
|
33
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# Camper: Sample Bot
|
2
|
+
#
|
3
|
+
# Enjoys sitting in the corners and firing powerful shots
|
4
|
+
class Camper < RTanque::Bot::Brain
|
5
|
+
NAME = 'Camper'
|
6
|
+
include RTanque::Bot::BrainHelper
|
7
|
+
|
8
|
+
CORNERS = [:NW, :NE, :SE, :SW]
|
9
|
+
TURRET_FIRE_RANGE = RTanque::Heading::ONE_DEGREE * 1.0
|
10
|
+
SWITCH_CORNER_TICK_RANGE = (600..1000)
|
11
|
+
|
12
|
+
def tick!
|
13
|
+
self.hide_in_corners
|
14
|
+
if (target = self.nearest_target)
|
15
|
+
self.fire_upon(target)
|
16
|
+
else
|
17
|
+
self.detect_targets
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def fire_upon(target)
|
22
|
+
self.command.radar_heading = target.heading
|
23
|
+
self.command.turret_heading = target.heading
|
24
|
+
if self.sensors.turret_heading.delta(target.heading).abs < TURRET_FIRE_RANGE
|
25
|
+
self.command.fire(MAX_FIRE_POWER)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def nearest_target
|
30
|
+
self.sensors.radar.min { |a,b| a.distance <=> b.distance }
|
31
|
+
end
|
32
|
+
|
33
|
+
def detect_targets
|
34
|
+
self.command.radar_heading = self.sensors.radar_heading + MAX_RADAR_ROTATION
|
35
|
+
self.command.turret_heading = self.sensors.heading + RTanque::Heading::HALF_ANGLE
|
36
|
+
end
|
37
|
+
|
38
|
+
def hide_in_corners
|
39
|
+
@corner_cycle ||= CORNERS.shuffle.cycle
|
40
|
+
self.at_tick_interval(self.camp_interval) {
|
41
|
+
self.corner = @corner_cycle.next
|
42
|
+
self.reset_camp_interval
|
43
|
+
}
|
44
|
+
self.corner ||= @corner_cycle.next
|
45
|
+
self.move_to_corner
|
46
|
+
end
|
47
|
+
|
48
|
+
def move_to_corner
|
49
|
+
if self.corner
|
50
|
+
command.heading = self.sensors.position.heading(RTanque::Point.new(*self.corner, self.arena))
|
51
|
+
command.speed = MAX_BOT_SPEED
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def corner=(corner_name)
|
56
|
+
@corner = case corner_name
|
57
|
+
when :NE
|
58
|
+
[self.arena.width, self.arena.height]
|
59
|
+
when :SE
|
60
|
+
[self.arena.width, 0]
|
61
|
+
when :SW
|
62
|
+
[0, 0]
|
63
|
+
else
|
64
|
+
[0, self.arena.height]
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def corner
|
69
|
+
@corner
|
70
|
+
end
|
71
|
+
|
72
|
+
def camp_interval
|
73
|
+
@camp_interval ||= self.reset_camp_interval
|
74
|
+
end
|
75
|
+
|
76
|
+
def reset_camp_interval
|
77
|
+
@camp_interval = rand(SWITCH_CORNER_TICK_RANGE.max - SWITCH_CORNER_TICK_RANGE.min) + SWITCH_CORNER_TICK_RANGE.min
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
# Keyboard: Sample Bot
|
3
|
+
#
|
4
|
+
# Special bot controlled with the keyboard
|
5
|
+
#
|
6
|
+
# Controls:
|
7
|
+
# w drive forward
|
8
|
+
# s drive backwards
|
9
|
+
# a rotate left
|
10
|
+
# d rotate right
|
11
|
+
#
|
12
|
+
# ↑ fire powerful shot
|
13
|
+
# ↓ fire week shot
|
14
|
+
# ← rotate turret left
|
15
|
+
# → rotate turret right
|
16
|
+
class Keyboard < RTanque::Bot::Brain
|
17
|
+
include RTanque::Bot::BrainHelper
|
18
|
+
|
19
|
+
NAME = 'Keyboard'
|
20
|
+
|
21
|
+
def tick!
|
22
|
+
command.radar_heading = sensors.radar_heading + (RTanque::Heading::ONE_DEGREE * 30)
|
23
|
+
|
24
|
+
if sensors.button_down?(Gosu::KbLeft)
|
25
|
+
command.turret_heading = sensors.turret_heading - (RTanque::Heading::ONE_DEGREE * 10)
|
26
|
+
elsif sensors.button_down?(Gosu::KbRight)
|
27
|
+
command.turret_heading = sensors.turret_heading + (RTanque::Heading::ONE_DEGREE * 10)
|
28
|
+
end
|
29
|
+
|
30
|
+
if sensors.button_down?('a')
|
31
|
+
command.heading = sensors.heading - (RTanque::Heading::ONE_DEGREE * 10)
|
32
|
+
elsif sensors.button_down?('d')
|
33
|
+
command.heading = sensors.heading + (RTanque::Heading::ONE_DEGREE * 10)
|
34
|
+
end
|
35
|
+
|
36
|
+
if sensors.button_down?('w')
|
37
|
+
command.speed = 10
|
38
|
+
elsif sensors.button_down?('s')
|
39
|
+
command.speed = -10
|
40
|
+
else
|
41
|
+
command.speed = 0
|
42
|
+
end
|
43
|
+
|
44
|
+
if sensors.button_down?(Gosu::KbUp)
|
45
|
+
command.fire_power = 5
|
46
|
+
elsif sensors.button_down?(Gosu::KbDown)
|
47
|
+
command.fire_power = 1
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|