rubywarrior 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. data/CHANGELOG.rdoc +3 -0
  2. data/LICENSE +20 -0
  3. data/README.rdoc +170 -0
  4. data/Rakefile +21 -0
  5. data/bin/rubywarrior +5 -0
  6. data/features/command_options.feature +46 -0
  7. data/features/levels.feature +51 -0
  8. data/features/profiles.feature +56 -0
  9. data/features/step_definitions/common_steps.rb +19 -0
  10. data/features/step_definitions/interaction_steps.rb +65 -0
  11. data/features/support/env.rb +12 -0
  12. data/features/support/mockio.rb +57 -0
  13. data/features/towers.feature +14 -0
  14. data/lib/ruby_warrior.rb +44 -0
  15. data/lib/ruby_warrior/abilities/attack.rb +24 -0
  16. data/lib/ruby_warrior/abilities/base.rb +36 -0
  17. data/lib/ruby_warrior/abilities/bind.rb +19 -0
  18. data/lib/ruby_warrior/abilities/direction_of.rb +13 -0
  19. data/lib/ruby_warrior/abilities/direction_of_stairs.rb +13 -0
  20. data/lib/ruby_warrior/abilities/distance.rb +13 -0
  21. data/lib/ruby_warrior/abilities/explode.rb +16 -0
  22. data/lib/ruby_warrior/abilities/feel.rb +13 -0
  23. data/lib/ruby_warrior/abilities/health.rb +13 -0
  24. data/lib/ruby_warrior/abilities/listen.rb +15 -0
  25. data/lib/ruby_warrior/abilities/look.rb +15 -0
  26. data/lib/ruby_warrior/abilities/pivot.rb +16 -0
  27. data/lib/ruby_warrior/abilities/rescue.rb +23 -0
  28. data/lib/ruby_warrior/abilities/rest.rb +20 -0
  29. data/lib/ruby_warrior/abilities/shoot.rb +23 -0
  30. data/lib/ruby_warrior/abilities/walk.rb +20 -0
  31. data/lib/ruby_warrior/config.rb +22 -0
  32. data/lib/ruby_warrior/core_additions.rb +25 -0
  33. data/lib/ruby_warrior/floor.rb +71 -0
  34. data/lib/ruby_warrior/game.rb +193 -0
  35. data/lib/ruby_warrior/level.rb +119 -0
  36. data/lib/ruby_warrior/level_loader.rb +56 -0
  37. data/lib/ruby_warrior/player_generator.rb +41 -0
  38. data/lib/ruby_warrior/position.rb +80 -0
  39. data/lib/ruby_warrior/profile.rb +104 -0
  40. data/lib/ruby_warrior/runner.rb +33 -0
  41. data/lib/ruby_warrior/space.rb +63 -0
  42. data/lib/ruby_warrior/tower.rb +14 -0
  43. data/lib/ruby_warrior/turn.rb +38 -0
  44. data/lib/ruby_warrior/ui.rb +54 -0
  45. data/lib/ruby_warrior/units/archer.rb +34 -0
  46. data/lib/ruby_warrior/units/base.rb +95 -0
  47. data/lib/ruby_warrior/units/captive.rb +30 -0
  48. data/lib/ruby_warrior/units/sludge.rb +30 -0
  49. data/lib/ruby_warrior/units/thick_sludge.rb +13 -0
  50. data/lib/ruby_warrior/units/warrior.rb +58 -0
  51. data/lib/ruby_warrior/units/wizard.rb +34 -0
  52. data/spec/fixtures/short-tower/level_001.rb +15 -0
  53. data/spec/fixtures/short-tower/level_002.rb +15 -0
  54. data/spec/fixtures/walking_player.rb +5 -0
  55. data/spec/ruby_warrior/abilities/attack_spec.rb +51 -0
  56. data/spec/ruby_warrior/abilities/base_spec.rb +24 -0
  57. data/spec/ruby_warrior/abilities/bind_spec.rb +19 -0
  58. data/spec/ruby_warrior/abilities/direction_of_spec.rb +13 -0
  59. data/spec/ruby_warrior/abilities/direction_of_stairs_spec.rb +13 -0
  60. data/spec/ruby_warrior/abilities/distance_spec.rb +13 -0
  61. data/spec/ruby_warrior/abilities/explode_spec.rb +21 -0
  62. data/spec/ruby_warrior/abilities/feel_spec.rb +13 -0
  63. data/spec/ruby_warrior/abilities/health_spec.rb +13 -0
  64. data/spec/ruby_warrior/abilities/listen_spec.rb +17 -0
  65. data/spec/ruby_warrior/abilities/look_spec.rb +15 -0
  66. data/spec/ruby_warrior/abilities/pivot_spec.rb +18 -0
  67. data/spec/ruby_warrior/abilities/rescue_spec.rb +40 -0
  68. data/spec/ruby_warrior/abilities/rest_spec.rb +29 -0
  69. data/spec/ruby_warrior/abilities/shoot_spec.rb +22 -0
  70. data/spec/ruby_warrior/abilities/walk_spec.rb +25 -0
  71. data/spec/ruby_warrior/floor_spec.rb +81 -0
  72. data/spec/ruby_warrior/game_spec.rb +119 -0
  73. data/spec/ruby_warrior/level_loader_spec.rb +54 -0
  74. data/spec/ruby_warrior/level_spec.rb +203 -0
  75. data/spec/ruby_warrior/player_generator_spec.rb +12 -0
  76. data/spec/ruby_warrior/position_spec.rb +103 -0
  77. data/spec/ruby_warrior/profile_spec.rb +144 -0
  78. data/spec/ruby_warrior/space_spec.rb +165 -0
  79. data/spec/ruby_warrior/tower_spec.rb +15 -0
  80. data/spec/ruby_warrior/turn_spec.rb +42 -0
  81. data/spec/ruby_warrior/ui_spec.rb +93 -0
  82. data/spec/ruby_warrior/units/archer_spec.rb +23 -0
  83. data/spec/ruby_warrior/units/base_spec.rb +133 -0
  84. data/spec/ruby_warrior/units/captive_spec.rb +34 -0
  85. data/spec/ruby_warrior/units/sludge_spec.rb +27 -0
  86. data/spec/ruby_warrior/units/thick_sludge_spec.rb +19 -0
  87. data/spec/ruby_warrior/units/warrior_spec.rb +60 -0
  88. data/spec/ruby_warrior/units/wizard_spec.rb +23 -0
  89. data/spec/spec_helper.rb +10 -0
  90. data/templates/README.erb +20 -0
  91. data/templates/player.rb +5 -0
  92. data/towers/beginner/level_001.rb +16 -0
  93. data/towers/beginner/level_002.rb +18 -0
  94. data/towers/beginner/level_003.rb +21 -0
  95. data/towers/beginner/level_004.rb +18 -0
  96. data/towers/beginner/level_005.rb +22 -0
  97. data/towers/beginner/level_006.rb +19 -0
  98. data/towers/beginner/level_007.rb +18 -0
  99. data/towers/beginner/level_008.rb +21 -0
  100. data/towers/beginner/level_009.rb +20 -0
  101. data/towers/intermediate/level_001.rb +18 -0
  102. data/towers/intermediate/level_002.rb +20 -0
  103. data/towers/intermediate/level_003.rb +23 -0
  104. data/towers/intermediate/level_004.rb +24 -0
  105. data/towers/intermediate/level_005.rb +19 -0
  106. data/towers/intermediate/level_006.rb +24 -0
  107. 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,14 @@
1
+ module RubyWarrior
2
+ class Tower
3
+ attr_reader :path
4
+
5
+ def initialize(path)
6
+ @path = path
7
+ end
8
+
9
+ def name
10
+ File.basename(path)
11
+ end
12
+ alias_method :to_s, :name
13
+ end
14
+ 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