diff --git a/lib/game/input.rb b/lib/game/input.rb index 73b3763..dba0605 100644 --- a/lib/game/input.rb +++ b/lib/game/input.rb @@ -13,10 +13,10 @@ def read end def bind_keys - bind_key(:w) { @player.move(:up) } - bind_key(:s) { @player.move(:down) } - bind_key(:a) { @player.move(:left) } - bind_key(:d) { @player.move(:right) } + bind_key(:w) { @player.move(Game::Map::NORTH) } + bind_key(:s) { @player.move(Game::Map::SOUTH) } + bind_key(:a) { @player.move(Game::Map::EAST) } + bind_key(:d) { @player.move(Game::Map::WEST) } bind_key(:' ') { @player.take_action } diff --git a/lib/game/map.rb b/lib/game/map.rb index 9a98b7e..9fb2e4d 100644 --- a/lib/game/map.rb +++ b/lib/game/map.rb @@ -1,5 +1,10 @@ class Game class Map + NORTH = :north + SOUTH = :south + EAST = :east + WEST = :west + def self.load_map(filename) new(filename) end diff --git a/lib/game/object.rb b/lib/game/object.rb index 74ffa49..70083d3 100644 --- a/lib/game/object.rb +++ b/lib/game/object.rb @@ -14,6 +14,9 @@ class Game class Object + IDLE = :idle + ACTIVE = :active + class << self def instance(name, options={}) unless const_defined?(name) @@ -37,6 +40,22 @@ def store(instance) def objects @objects ||= [] end + + def engine_objects(level) + @game_objects ||= {} + @game_objects[level] ||= {Game::Object::IDLE => [], + Game::Object::ACTIVE => []} + @game_objects[level] + end + + def add(level, status, obj) + engine_objects(level)[status] << obj + end + + def clear + @game_objects = {} + @objects = [] + end end end end diff --git a/lib/game/object/default.rb b/lib/game/object/default.rb index 38244cc..d81d26d 100644 --- a/lib/game/object/default.rb +++ b/lib/game/object/default.rb @@ -6,6 +6,7 @@ def self.included(base) end def initialize(options) + super @options = options end diff --git a/lib/game/object/enemy.rb b/lib/game/object/enemy.rb index 1022edf..2d30056 100644 --- a/lib/game/object/enemy.rb +++ b/lib/game/object/enemy.rb @@ -1,15 +1,63 @@ class Game class Object module Enemy - IDLE = :idle + def self.included(base) + base.send :attr_reader, :direction + end + + def initialize(*args) + super + @direction = Game::Map::NORTH + Game::Object.add(Game::Engine.instance.map.name, status, self) + end def passible? false end + def range + @options['range'] || 4 + end + def status - IDLE + @status ||= Game::Object::IDLE + end + + def active_turn + raise 'This should not happen.. only call when status is active..' unless status == Game::Object::ACTIVE + @direction = player_direction + + if player_in_front? + Game::Player.instance.damage(attack) + elsif !player_in_range? + Game::Object.remove(Game::Engine.instance.map.name, status, self) + @status = Game::Object::IDLE + Game::Object.add(Game::Engine.instance.map.name, status, self) + elsif (tile = @tile.at(@direction)).passible? + move_to(tile) + end + end + + def move_to(tile) + @tile.remove(self) + @tile = tile + @tile.add(self) + end + + def player_in_range? + @tile.in_range?(Game::Player.instance.tile, range) + end + private :player_in_range? + + def player_in_front? + @tile.at(@direction).has_object?(Game::Player) + end + private :player_in_front? + + def player_direction + @tile.direction_to(Game::Player.instance.tile) end + private :player_direction end end end diff --git a/lib/game/tile/movement.rb b/lib/game/tile/movement.rb index e528354..85d6b7b 100644 --- a/lib/game/tile/movement.rb +++ b/lib/game/tile/movement.rb @@ -2,14 +2,32 @@ class Game class Tile module Movement def at(direction) - tile = Game::Tile.at(@x - 1, @y) if direction == :up - tile = Game::Tile.at(@x + 1, @y) if direction == :down - tile = Game::Tile.at(@x, @y - 1) if direction == :left - tile = Game::Tile.at(@x, @y + 1) if direction == :right + tile = Game::Tile.at(@x - 1, @y) if direction == Game::Map::NORTH + tile = Game::Tile.at(@x + 1, @y) if direction == Game::Map::SOUTH + tile = Game::Tile.at(@x, @y - 1) if direction == Game::Map::EAST + tile = Game::Tile.at(@x, @y + 1) if direction == Game::Map::WEST tile.try(:end_point) || Game::Tile::Edge.instance end + def direction_to(tile) + diff_x = x - tile.x + diff_y = y - tile.y + + if diff_x.abs > diff_y.abs + diff_x > 0 ? Game::Map::NORTH : Game::Map::SOUTH + else + diff_y > 0 ? Game::Map::WEST : Game::Map::EAST + end + end + + def in_range?(tile, max=10) + diff_x = x - tile.x + diff_y = y - tile.y + + ((diff_x * diff_x) + (diff_y * diff_y)) <= (max * max) + end + def end_point if has_object?(Game::Object::TileModifier) Game::Tile.at(*get_object(Game::Object::TileModifier).end_point) diff --git a/lib/game/tile/passible.rb b/lib/game/tile/passible.rb index 7923c76..135f3b9 100644 --- a/lib/game/tile/passible.rb +++ b/lib/game/tile/passible.rb @@ -1,7 +1,7 @@ class Game class Tile module Passible - def passible?(player_objects) + def passible?(player_objects=[]) objects.all?{|obj| !obj.respond_to?(:passible?) || obj.passible? } end end diff --git a/lib/render/console/draw_tile.rb b/lib/render/console/draw_tile.rb index 170b24a..d36cb0e 100644 --- a/lib/render/console/draw_tile.rb +++ b/lib/render/console/draw_tile.rb @@ -20,13 +20,13 @@ def draw_player(base) return base unless tile.has_object?(Game::Player) base[0] + case Game::Player.instance.direction - when :up + when Game::Map::NORTH '^' - when :down + when Game::Map::SOUTH 'v' - when :left + when Game::Map::EAST '<' - when :right + when Game::Map::WEST '>' else '*' diff --git a/spec/game/object/enemy_spec.rb b/spec/game/object/enemy_spec.rb index 3140380..91cbe9f 100644 --- a/spec/game/object/enemy_spec.rb +++ b/spec/game/object/enemy_spec.rb @@ -1,7 +1,12 @@ require 'spec_helper' describe Game::Object::Enemy do - subject { Game::Object.instance('TestEnemy', 'modules' => ['Enemy'])} + subject { Game::Object.instance('TestEnemy', 'modules' => ['Enemy'], 'attack' => 10)} + let(:engine) { mock(:engine, :map => map) } + let(:map) { mock(:map, :name => 'map_name') } + before do + Game::Engine.stub(:instance => engine) + end describe '#passible?' do it 'is false' do @@ -11,7 +16,89 @@ describe '#status' do it 'is idle when left alone' do - subject.status.should == Game::Object::Enemy::IDLE + subject.status.should == Game::Object::IDLE + end + end + + describe '#initialization' do + it 'added as an idle object' do + Game::Object.should_receive(:add).with('map_name', Game::Object::IDLE, subject) + subject + end + end + + describe '#active_turn' do + let(:front_tile) { Game::Tile.build(0, 0, 1) } + let(:tile) { Game::Tile.build(0, 1, 1) } + let(:back_tile) { Game::Tile.build(0, 2, 1) } + let(:distant_tile) { Game::Tile.build(0, 3, 1) } + let(:range_tile) { Game::Tile.build(0, 11, 1) } + let(:player) { Game::Player.new } + before do + tile.add(subject) + front_tile + back_tile + distant_tile + range_tile + subject.instance_variable_set(:@status, Game::Object::ACTIVE) + end + + it 'return raise an eror is status is not active' do + subject.instance_variable_set(:@status, Game::Object::IDLE) + expect { subject.active_turn }.to raise_error + end + + context 'player directly in front' do + before do + tile.stub(:direction_to => Game::Map::NORTH, :in_range => true) + end + + it 'attacks' do + front_tile.add(player) + player.should_receive(:damage).with(10) + subject.active_turn + end + end + + context 'player out of active distance' do + before do + range_tile.add(player) + tile.stub(:in_range? => false) + tile.stub(:direction_to => Game::Map::NORTH, :range => true) + end + + it 'sets the enemy unit to idle' do + tile.should_receive(:direction_to).with(range_tile) + Game::Object.should_receive(:remove).with('map_name', Game::Object::ACTIVE, subject) + Game::Object.should_receive(:add).with('map_name', Game::Object::IDLE, subject) + subject.active_turn + end + end + + context 'player not in front' do + before do + tile.stub(:direction_to => Game::Map::SOUTH, :in_range? => true) + end + + it 'turn to face the player' do + back_tile.add(player) + tile.should_receive(:direction_to).with(back_tile) + subject.active_turn + subject.direction.should == Game::Map::SOUTH + end + + it 'attack the player if in the new direction' do + back_tile.add(player) + player.should_receive(:damage).with(10) + subject.active_turn + end + + it 'move in the next direction if player is not directly there' do + distant_tile.add(player) + tile.should_receive(:remove).with(subject) + back_tile.should_receive(:add).with(subject) + subject.active_turn + end end end end \ No newline at end of file diff --git a/spec/game/object_spec.rb b/spec/game/object_spec.rb index 4ed447f..7ae8cb9 100644 --- a/spec/game/object_spec.rb +++ b/spec/game/object_spec.rb @@ -27,5 +27,15 @@ Game::Object.objects.should include(passage) end end + + describe '#add' do + before { Game::Object.clear } + + it 'adds the object to the list of engine run objects' do + Game::Object.add('test_map', Game::Object::IDLE, :object_here) + + Game::Object.engine_objects('test_map').should == {Game::Object::IDLE => [:object_here], Game::Object::ACTIVE => []} + end + end end end \ No newline at end of file diff --git a/spec/game/player/movements_spec.rb b/spec/game/player/movements_spec.rb index 0a0ab85..616b6bf 100644 --- a/spec/game/player/movements_spec.rb +++ b/spec/game/player/movements_spec.rb @@ -25,25 +25,25 @@ end it 'delegates the movement to the current tile' do - tile.should_receive(:at).with(:up) - subject.move(:up) + tile.should_receive(:at).with(Game::Map::NORTH) + subject.move(Game::Map::NORTH) end context 'the new tile is passible' do it 'moves the player' do - subject.move(:up) + subject.move(Game::Map::NORTH) subject.tile.should == passible_tile end it 'move the player object from the old tile to the new tile' do tile.should_receive(:remove).with(subject) passible_tile.should_receive(:add).with(subject) - subject.move(:up) + subject.move(Game::Map::NORTH) end it 'process and automatic actions' do subject.should_receive(:take_auto_action) - subject.move(:up) + subject.move(Game::Map::NORTH) end end @@ -55,13 +55,13 @@ it 'does not move the player' do subject.stub(:print => true) - subject.move(:up) + subject.move(Game::Map::NORTH) subject.tile.should == tile end it 'plays a beep' do subject.should_receive(:print).with("\a") - subject.move(:up) + subject.move(Game::Map::NORTH) end end end diff --git a/spec/game/tile/movement_spec.rb b/spec/game/tile/movement_spec.rb index 2ad147f..3a62079 100644 --- a/spec/game/tile/movement_spec.rb +++ b/spec/game/tile/movement_spec.rb @@ -7,22 +7,22 @@ context 'retrieves the tile via the class at method' do it 'for up' do Game::Tile.should_receive(:at).with(0, 1) - subject.at(:up) + subject.at(Game::Map::NORTH) end it 'for down' do Game::Tile.should_receive(:at).with(2, 1) - subject.at(:down) + subject.at(Game::Map::SOUTH) end it 'for left' do Game::Tile.should_receive(:at).with(1, 0) - subject.at(:left) + subject.at(Game::Map::EAST) end it 'for right' do Game::Tile.should_receive(:at).with(1, 2) - subject.at(:right) + subject.at(Game::Map::WEST) end end @@ -36,17 +36,66 @@ end_point_tile = Game::Tile.build(0, 2, 2) up_tile = Game::Tile.build(0, 0, 1) up_tile.add(Game::Object.instance('Transport', "end_point" => [2, 2], 'modules' => ['TileModifier'])) - subject.at(:up).should == end_point_tile + subject.at(Game::Map::NORTH).should == end_point_tile end end context 'if no tile effecting object exists' do it 'returns the tile' do up_tile = Game::Tile.build(0, 0, 1) - subject.at(:up).should == up_tile + subject.at(Game::Map::NORTH).should == up_tile end end end end + describe '#direction_to' do + let(:origin) { Game::Tile.build(0, 2, 2) } + + let(:north) { Game::Tile.build(0, 0, 2) } + let(:east) { Game::Tile.build(0, 2, 4) } + let(:south) { Game::Tile.build(0, 4, 2) } + let(:west) { Game::Tile.build(0, 2, 0) } + + it 'when in an north direction' do + origin.direction_to(north).should == Game::Map::NORTH + end + + it 'when in an east direction' do + origin.direction_to(east).should == Game::Map::EAST + end + + it 'when in an south direction' do + origin.direction_to(south).should == Game::Map::SOUTH + end + + it 'when in an west direction' do + origin.direction_to(west).should == Game::Map::WEST + end + end + + describe '#in_range?' do + let(:origin) { Game::Tile.build(0, 0, 0) } + + let(:close_straight) { Game::Tile.build(0, 10, 0) } + let(:far_straight) { Game::Tile.build(0, 12, 0) } + let(:close_angle) { Game::Tile.build(0, 7, 7) } + let(:far_angle) { Game::Tile.build(0, 5, 9) } + + it 'when distance less is than max in a straight line' do + origin.should be_in_range(close_straight) + end + + it 'when distance greater is than max in a straight line' do + origin.should_not be_in_range(far_straight) + end + + it 'when distance less is than max on a diagonal line' do + origin.should be_in_range(close_angle) + end + + it 'when distance greater is than max on a diagonal line' do + origin.should_not be_in_range(far_angle) + end + end end \ No newline at end of file diff --git a/spec/game/tile/wall_spec.rb b/spec/game/tile/wall_spec.rb index f80660c..75a9ecb 100644 --- a/spec/game/tile/wall_spec.rb +++ b/spec/game/tile/wall_spec.rb @@ -56,16 +56,16 @@ it 'allows player movement through a door with a key' do player.add(key) player.load_map(map) - player.move(:down) + player.move(Game::Map::SOUTH) player.tile.should == wall - player.move(:down) + player.move(Game::Map::SOUTH) player.tile.should == stop end it 'passing the wall drops the key' do player.add(key) player.load_map(map) - player.move(:down) + player.move(Game::Map::SOUTH) player.tile.should == wall player.should_not have_object(Game::Object::FeatureKey) end @@ -73,8 +73,8 @@ it 'passing the wall leaves the door open' do player.add(key) player.load_map(map) - player.move(:down) - player.move(:down) + player.move(Game::Map::SOUTH) + player.move(Game::Map::SOUTH) wall.should be_passible([]) end end