rubywarrior 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.rdoc +3 -0
- data/LICENSE +20 -0
- data/README.rdoc +170 -0
- data/Rakefile +21 -0
- data/bin/rubywarrior +5 -0
- data/features/command_options.feature +46 -0
- data/features/levels.feature +51 -0
- data/features/profiles.feature +56 -0
- data/features/step_definitions/common_steps.rb +19 -0
- data/features/step_definitions/interaction_steps.rb +65 -0
- data/features/support/env.rb +12 -0
- data/features/support/mockio.rb +57 -0
- data/features/towers.feature +14 -0
- data/lib/ruby_warrior.rb +44 -0
- data/lib/ruby_warrior/abilities/attack.rb +24 -0
- data/lib/ruby_warrior/abilities/base.rb +36 -0
- data/lib/ruby_warrior/abilities/bind.rb +19 -0
- data/lib/ruby_warrior/abilities/direction_of.rb +13 -0
- data/lib/ruby_warrior/abilities/direction_of_stairs.rb +13 -0
- data/lib/ruby_warrior/abilities/distance.rb +13 -0
- data/lib/ruby_warrior/abilities/explode.rb +16 -0
- data/lib/ruby_warrior/abilities/feel.rb +13 -0
- data/lib/ruby_warrior/abilities/health.rb +13 -0
- data/lib/ruby_warrior/abilities/listen.rb +15 -0
- data/lib/ruby_warrior/abilities/look.rb +15 -0
- data/lib/ruby_warrior/abilities/pivot.rb +16 -0
- data/lib/ruby_warrior/abilities/rescue.rb +23 -0
- data/lib/ruby_warrior/abilities/rest.rb +20 -0
- data/lib/ruby_warrior/abilities/shoot.rb +23 -0
- data/lib/ruby_warrior/abilities/walk.rb +20 -0
- data/lib/ruby_warrior/config.rb +22 -0
- data/lib/ruby_warrior/core_additions.rb +25 -0
- data/lib/ruby_warrior/floor.rb +71 -0
- data/lib/ruby_warrior/game.rb +193 -0
- data/lib/ruby_warrior/level.rb +119 -0
- data/lib/ruby_warrior/level_loader.rb +56 -0
- data/lib/ruby_warrior/player_generator.rb +41 -0
- data/lib/ruby_warrior/position.rb +80 -0
- data/lib/ruby_warrior/profile.rb +104 -0
- data/lib/ruby_warrior/runner.rb +33 -0
- data/lib/ruby_warrior/space.rb +63 -0
- data/lib/ruby_warrior/tower.rb +14 -0
- data/lib/ruby_warrior/turn.rb +38 -0
- data/lib/ruby_warrior/ui.rb +54 -0
- data/lib/ruby_warrior/units/archer.rb +34 -0
- data/lib/ruby_warrior/units/base.rb +95 -0
- data/lib/ruby_warrior/units/captive.rb +30 -0
- data/lib/ruby_warrior/units/sludge.rb +30 -0
- data/lib/ruby_warrior/units/thick_sludge.rb +13 -0
- data/lib/ruby_warrior/units/warrior.rb +58 -0
- data/lib/ruby_warrior/units/wizard.rb +34 -0
- data/spec/fixtures/short-tower/level_001.rb +15 -0
- data/spec/fixtures/short-tower/level_002.rb +15 -0
- data/spec/fixtures/walking_player.rb +5 -0
- data/spec/ruby_warrior/abilities/attack_spec.rb +51 -0
- data/spec/ruby_warrior/abilities/base_spec.rb +24 -0
- data/spec/ruby_warrior/abilities/bind_spec.rb +19 -0
- data/spec/ruby_warrior/abilities/direction_of_spec.rb +13 -0
- data/spec/ruby_warrior/abilities/direction_of_stairs_spec.rb +13 -0
- data/spec/ruby_warrior/abilities/distance_spec.rb +13 -0
- data/spec/ruby_warrior/abilities/explode_spec.rb +21 -0
- data/spec/ruby_warrior/abilities/feel_spec.rb +13 -0
- data/spec/ruby_warrior/abilities/health_spec.rb +13 -0
- data/spec/ruby_warrior/abilities/listen_spec.rb +17 -0
- data/spec/ruby_warrior/abilities/look_spec.rb +15 -0
- data/spec/ruby_warrior/abilities/pivot_spec.rb +18 -0
- data/spec/ruby_warrior/abilities/rescue_spec.rb +40 -0
- data/spec/ruby_warrior/abilities/rest_spec.rb +29 -0
- data/spec/ruby_warrior/abilities/shoot_spec.rb +22 -0
- data/spec/ruby_warrior/abilities/walk_spec.rb +25 -0
- data/spec/ruby_warrior/floor_spec.rb +81 -0
- data/spec/ruby_warrior/game_spec.rb +119 -0
- data/spec/ruby_warrior/level_loader_spec.rb +54 -0
- data/spec/ruby_warrior/level_spec.rb +203 -0
- data/spec/ruby_warrior/player_generator_spec.rb +12 -0
- data/spec/ruby_warrior/position_spec.rb +103 -0
- data/spec/ruby_warrior/profile_spec.rb +144 -0
- data/spec/ruby_warrior/space_spec.rb +165 -0
- data/spec/ruby_warrior/tower_spec.rb +15 -0
- data/spec/ruby_warrior/turn_spec.rb +42 -0
- data/spec/ruby_warrior/ui_spec.rb +93 -0
- data/spec/ruby_warrior/units/archer_spec.rb +23 -0
- data/spec/ruby_warrior/units/base_spec.rb +133 -0
- data/spec/ruby_warrior/units/captive_spec.rb +34 -0
- data/spec/ruby_warrior/units/sludge_spec.rb +27 -0
- data/spec/ruby_warrior/units/thick_sludge_spec.rb +19 -0
- data/spec/ruby_warrior/units/warrior_spec.rb +60 -0
- data/spec/ruby_warrior/units/wizard_spec.rb +23 -0
- data/spec/spec_helper.rb +10 -0
- data/templates/README.erb +20 -0
- data/templates/player.rb +5 -0
- data/towers/beginner/level_001.rb +16 -0
- data/towers/beginner/level_002.rb +18 -0
- data/towers/beginner/level_003.rb +21 -0
- data/towers/beginner/level_004.rb +18 -0
- data/towers/beginner/level_005.rb +22 -0
- data/towers/beginner/level_006.rb +19 -0
- data/towers/beginner/level_007.rb +18 -0
- data/towers/beginner/level_008.rb +21 -0
- data/towers/beginner/level_009.rb +20 -0
- data/towers/intermediate/level_001.rb +18 -0
- data/towers/intermediate/level_002.rb +20 -0
- data/towers/intermediate/level_003.rb +23 -0
- data/towers/intermediate/level_004.rb +24 -0
- data/towers/intermediate/level_005.rb +19 -0
- data/towers/intermediate/level_006.rb +24 -0
- metadata +167 -0
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'fileutils'
|
3
|
+
require 'erb'
|
4
|
+
|
5
|
+
module RubyWarrior
|
6
|
+
class PlayerGenerator
|
7
|
+
def initialize(level)
|
8
|
+
@level = level
|
9
|
+
end
|
10
|
+
|
11
|
+
def level
|
12
|
+
@level
|
13
|
+
end
|
14
|
+
|
15
|
+
def previous_level
|
16
|
+
@previous_level ||= Level.new(level.profile, level.number-1)
|
17
|
+
end
|
18
|
+
|
19
|
+
# TODO refactor and test this method
|
20
|
+
def generate
|
21
|
+
if level.number == 1
|
22
|
+
FileUtils.mkdir_p(level.player_path)
|
23
|
+
FileUtils.cp(templates_path + '/player.rb', level.player_path)
|
24
|
+
end
|
25
|
+
|
26
|
+
File.open(level.player_path + '/README', 'w') do |f|
|
27
|
+
f.write read_template(templates_path + '/README.erb')
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def templates_path
|
32
|
+
File.expand_path(File.dirname(__FILE__) + "/../../templates")
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def read_template(path)
|
38
|
+
ERB.new(File.read(path), nil, '-').result(binding)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module RubyWarrior
|
2
|
+
class Position
|
3
|
+
attr_reader :floor
|
4
|
+
DIRECTIONS = [:north, :east, :south, :west]
|
5
|
+
RELATIVE_DIRECTIONS = [:forward, :right, :backward, :left]
|
6
|
+
|
7
|
+
def initialize(floor, x, y, direction = nil)
|
8
|
+
@floor = floor
|
9
|
+
@x = x
|
10
|
+
@y = y
|
11
|
+
@direction_index = DIRECTIONS.index(direction || :north)
|
12
|
+
end
|
13
|
+
|
14
|
+
def at?(x, y)
|
15
|
+
@x == x && @y == y
|
16
|
+
end
|
17
|
+
|
18
|
+
def direction
|
19
|
+
DIRECTIONS[@direction_index]
|
20
|
+
end
|
21
|
+
|
22
|
+
def rotate(amount)
|
23
|
+
@direction_index += amount
|
24
|
+
@direction_index -= 4 while @direction_index > 3
|
25
|
+
@direction_index += 4 while @direction_index < 0
|
26
|
+
end
|
27
|
+
|
28
|
+
def relative_space(forward, right = 0)
|
29
|
+
@floor.space(*translate_offset(forward, right))
|
30
|
+
end
|
31
|
+
|
32
|
+
def space
|
33
|
+
@floor.space(@x, @y)
|
34
|
+
end
|
35
|
+
|
36
|
+
def move(forward, right = 0)
|
37
|
+
@x, @y = *translate_offset(forward, right)
|
38
|
+
end
|
39
|
+
|
40
|
+
def distance_from_stairs
|
41
|
+
stairs_x, stairs_y = *@floor.stairs_location
|
42
|
+
(@x - stairs_x).abs + (@y - stairs_y).abs
|
43
|
+
end
|
44
|
+
|
45
|
+
def relative_direction_of_stairs
|
46
|
+
relative_direction_of(@floor.stairs_space)
|
47
|
+
end
|
48
|
+
|
49
|
+
def relative_direction_of(space)
|
50
|
+
relative_direction(direction_of(space))
|
51
|
+
end
|
52
|
+
|
53
|
+
def direction_of(space)
|
54
|
+
space_x, space_y = *space.location
|
55
|
+
if (@x - space_x).abs > (@y - space_y).abs
|
56
|
+
space_x > @x ? :east : :west
|
57
|
+
else
|
58
|
+
space_y > @y ? :south : :north
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def relative_direction(direction)
|
63
|
+
offset = DIRECTIONS.index(direction) - @direction_index
|
64
|
+
offset -= 4 while offset > 3
|
65
|
+
offset += 4 while offset < 0
|
66
|
+
RELATIVE_DIRECTIONS[offset]
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def translate_offset(forward, right)
|
72
|
+
case direction
|
73
|
+
when :north then [@x + right, @y - forward]
|
74
|
+
when :east then [@x + forward, @y + right]
|
75
|
+
when :south then [@x - right, @y + forward]
|
76
|
+
when :west then [@x - forward, @y - right]
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
require 'base64'
|
2
|
+
|
3
|
+
module RubyWarrior
|
4
|
+
class Profile
|
5
|
+
attr_accessor :score, :epic_score, :current_epic_score, :average_grade, :current_epic_grades, :abilities, :level_number, :tower_path, :warrior_name, :player_path
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@tower_path = nil
|
9
|
+
@warrior_name = nil
|
10
|
+
@score = 0
|
11
|
+
@current_epic_score = 0
|
12
|
+
@current_epic_grades = {}
|
13
|
+
@epic_score = 0
|
14
|
+
@average_grade = nil
|
15
|
+
@abilities = []
|
16
|
+
@level_number = 0
|
17
|
+
end
|
18
|
+
|
19
|
+
def encode
|
20
|
+
Base64.encode64(Marshal.dump(self))
|
21
|
+
end
|
22
|
+
|
23
|
+
def save
|
24
|
+
update_epic_score
|
25
|
+
@level_number = 0 if epic?
|
26
|
+
File.open(player_path + '/.profile', 'w') { |f| f.write(encode) }
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.decode(str)
|
30
|
+
Marshal.load(Base64.decode64(str))
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.load(path)
|
34
|
+
player = decode(File.read(path))
|
35
|
+
player.player_path = File.dirname(path)
|
36
|
+
player
|
37
|
+
end
|
38
|
+
|
39
|
+
def player_path
|
40
|
+
@player_path || Config.path_prefix + "/rubywarrior/#{directory_name}"
|
41
|
+
end
|
42
|
+
|
43
|
+
def directory_name
|
44
|
+
[warrior_name.downcase.gsub(/[^a-z0-9]+/, '-'), tower.name].join('-')
|
45
|
+
end
|
46
|
+
|
47
|
+
def to_s
|
48
|
+
if epic?
|
49
|
+
[warrior_name, tower.name, "first score #{score}", "epic score #{epic_score_with_grade}"].join(' - ')
|
50
|
+
else
|
51
|
+
[warrior_name, tower.name, "level #{level_number}", "score #{score}"].join(' - ')
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def epic_score_with_grade
|
56
|
+
if average_grade
|
57
|
+
"#{epic_score} (#{Level.grade_letter(average_grade)})"
|
58
|
+
else
|
59
|
+
epic_score
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def tower
|
64
|
+
Tower.new(@tower_path)
|
65
|
+
end
|
66
|
+
|
67
|
+
def current_level
|
68
|
+
Level.new(self, level_number)
|
69
|
+
end
|
70
|
+
|
71
|
+
def next_level
|
72
|
+
Level.new(self, level_number+1)
|
73
|
+
end
|
74
|
+
|
75
|
+
def add_abilities(*abilities)
|
76
|
+
@abilities += abilities
|
77
|
+
@abilities.uniq!
|
78
|
+
end
|
79
|
+
|
80
|
+
def enable_epic_mode
|
81
|
+
@epic = true
|
82
|
+
@epic_score ||= 0
|
83
|
+
@current_epic_score ||= 0
|
84
|
+
end
|
85
|
+
|
86
|
+
def epic?
|
87
|
+
@epic
|
88
|
+
end
|
89
|
+
|
90
|
+
def update_epic_score
|
91
|
+
if @current_epic_score > @epic_score
|
92
|
+
@epic_score = @current_epic_score
|
93
|
+
@average_grade = calculate_average_grade
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def calculate_average_grade
|
98
|
+
if @current_epic_grades.size > 0
|
99
|
+
sum = @current_epic_grades.values.inject(0) { |sum, value| sum + value }
|
100
|
+
sum / @current_epic_grades.size
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
|
3
|
+
module RubyWarrior
|
4
|
+
class Runner
|
5
|
+
def initialize(arguments, stdin, stdout)
|
6
|
+
@arguments = arguments
|
7
|
+
@stdin = stdin
|
8
|
+
@stdout = stdout
|
9
|
+
@game = RubyWarrior::Game.new
|
10
|
+
end
|
11
|
+
|
12
|
+
def run
|
13
|
+
Config.in_stream = @stdin
|
14
|
+
Config.out_stream = @stdout
|
15
|
+
Config.delay = 0.8
|
16
|
+
parse_options
|
17
|
+
@game.start
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def parse_options
|
23
|
+
options = OptionParser.new
|
24
|
+
options.banner = "Usage: rubywarrior [options]"
|
25
|
+
options.on('-d', '--directory DIR', "Run under given directory") { |dir| Config.path_prefix = dir }
|
26
|
+
options.on('-l', '--level LEVEL', "Practice level on epic") { |level| Config.practice_level = level.to_i }
|
27
|
+
options.on('-s', '--skip', "Skip user input") { Config.skip_input = true }
|
28
|
+
options.on('-t', '--time SECONDS', "Delay each turn by seconds") { |seconds| Config.delay = seconds.to_f }
|
29
|
+
options.on('-h', '--help', "Show this message") { puts(options); exit }
|
30
|
+
options.parse!(@arguments)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module RubyWarrior
|
2
|
+
class Space
|
3
|
+
def initialize(floor, x, y)
|
4
|
+
@floor, @x, @y = floor, x, y
|
5
|
+
end
|
6
|
+
|
7
|
+
def wall?
|
8
|
+
@floor.out_of_bounds? @x, @y
|
9
|
+
end
|
10
|
+
|
11
|
+
def warrior?
|
12
|
+
unit.kind_of? Units::Warrior
|
13
|
+
end
|
14
|
+
|
15
|
+
def enemy?
|
16
|
+
unit && !warrior? && !captive?
|
17
|
+
end
|
18
|
+
|
19
|
+
def captive?
|
20
|
+
unit && unit.bound?
|
21
|
+
end
|
22
|
+
|
23
|
+
def empty?
|
24
|
+
unit.nil? && !wall?
|
25
|
+
end
|
26
|
+
|
27
|
+
def stairs?
|
28
|
+
@floor.stairs_location == location
|
29
|
+
end
|
30
|
+
|
31
|
+
def ticking?
|
32
|
+
unit.respond_to?(:bomb_time) && unit.bomb_time
|
33
|
+
end
|
34
|
+
|
35
|
+
def unit
|
36
|
+
@floor.get(@x, @y)
|
37
|
+
end
|
38
|
+
|
39
|
+
def location
|
40
|
+
[@x, @y]
|
41
|
+
end
|
42
|
+
|
43
|
+
def character
|
44
|
+
if unit
|
45
|
+
unit.character
|
46
|
+
elsif stairs?
|
47
|
+
">"
|
48
|
+
else
|
49
|
+
" "
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def to_s
|
54
|
+
if unit
|
55
|
+
unit.to_s
|
56
|
+
elsif wall?
|
57
|
+
'wall'
|
58
|
+
else
|
59
|
+
'nothing'
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module RubyWarrior
|
2
|
+
class Turn
|
3
|
+
attr_reader :action
|
4
|
+
|
5
|
+
def initialize(abilities)
|
6
|
+
@action = nil
|
7
|
+
@senses = {}
|
8
|
+
|
9
|
+
abilities.each do |name, sense|
|
10
|
+
if name.to_s =~ /\!$/
|
11
|
+
add_action(name)
|
12
|
+
else
|
13
|
+
add_sense(name, sense)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def add_action(action)
|
21
|
+
instance_eval <<-EOS
|
22
|
+
def #{action}(*args)
|
23
|
+
raise "Only one action can be performed per turn." if @action
|
24
|
+
@action = [:#{action}, *args]
|
25
|
+
end
|
26
|
+
EOS
|
27
|
+
end
|
28
|
+
|
29
|
+
def add_sense(name, sense)
|
30
|
+
instance_eval <<-EOS
|
31
|
+
def #{name}(*args)
|
32
|
+
@senses[:#{name}].perform(*args)
|
33
|
+
end
|
34
|
+
EOS
|
35
|
+
@senses[name] = sense
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module RubyWarrior
|
2
|
+
class UI
|
3
|
+
class << self
|
4
|
+
def puts(msg)
|
5
|
+
Config.out_stream.puts(msg) if Config.out_stream
|
6
|
+
end
|
7
|
+
|
8
|
+
def puts_with_delay(msg)
|
9
|
+
result = puts(msg)
|
10
|
+
sleep(Config.delay) if Config.delay
|
11
|
+
result
|
12
|
+
end
|
13
|
+
|
14
|
+
def print(msg)
|
15
|
+
Config.out_stream.print(msg) if Config.out_stream
|
16
|
+
end
|
17
|
+
|
18
|
+
def gets
|
19
|
+
Config.in_stream ? Config.in_stream.gets : ''
|
20
|
+
end
|
21
|
+
|
22
|
+
def request(msg)
|
23
|
+
print(msg)
|
24
|
+
gets.chomp
|
25
|
+
end
|
26
|
+
|
27
|
+
def ask(msg)
|
28
|
+
request("#{msg} [yn] ") == 'y'
|
29
|
+
end
|
30
|
+
|
31
|
+
# REFACTORME
|
32
|
+
def choose(item, options)
|
33
|
+
if options.length == 1
|
34
|
+
response = options.first
|
35
|
+
else
|
36
|
+
options.each_with_index do |option, i|
|
37
|
+
if option.kind_of? Array
|
38
|
+
puts "[#{i+1}] #{option.last}"
|
39
|
+
else
|
40
|
+
puts "[#{i+1}] #{option}"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
choice = request("Choose #{item} by typing the number: ")
|
44
|
+
response = options[choice.to_i-1]
|
45
|
+
end
|
46
|
+
if response.kind_of? Array
|
47
|
+
response.first
|
48
|
+
else
|
49
|
+
response
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module RubyWarrior
|
2
|
+
module Units
|
3
|
+
class Archer < Base
|
4
|
+
def initialize
|
5
|
+
add_abilities :shoot!, :look
|
6
|
+
end
|
7
|
+
|
8
|
+
def play_turn(turn)
|
9
|
+
[:forward, :left, :right].each do |direction|
|
10
|
+
turn.look(direction).each do |space|
|
11
|
+
if space.warrior?
|
12
|
+
turn.shoot!(direction)
|
13
|
+
return
|
14
|
+
elsif !space.empty?
|
15
|
+
break
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def shoot_power
|
22
|
+
3
|
23
|
+
end
|
24
|
+
|
25
|
+
def max_health
|
26
|
+
7
|
27
|
+
end
|
28
|
+
|
29
|
+
def character
|
30
|
+
"a"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|