rubygoal-core 1.0.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.
- checksums.yaml +7 -0
- data/LICENSE +18 -0
- data/README.md +85 -0
- data/bin/rubygoal +15 -0
- data/lib/rubygoal.rb +1 -0
- data/lib/rubygoal/ball.rb +60 -0
- data/lib/rubygoal/coach.rb +81 -0
- data/lib/rubygoal/coach_definition.rb +54 -0
- data/lib/rubygoal/coach_loader.rb +55 -0
- data/lib/rubygoal/coaches/coach_definition_away.rb +72 -0
- data/lib/rubygoal/coaches/coach_definition_home.rb +76 -0
- data/lib/rubygoal/configuration.rb +49 -0
- data/lib/rubygoal/coordinate.rb +33 -0
- data/lib/rubygoal/field.rb +134 -0
- data/lib/rubygoal/formation.rb +73 -0
- data/lib/rubygoal/formation/formation_dsl.rb +67 -0
- data/lib/rubygoal/game.rb +162 -0
- data/lib/rubygoal/goal.rb +26 -0
- data/lib/rubygoal/match_data.rb +130 -0
- data/lib/rubygoal/moveable.rb +67 -0
- data/lib/rubygoal/player.rb +87 -0
- data/lib/rubygoal/players/average.rb +16 -0
- data/lib/rubygoal/players/captain.rb +15 -0
- data/lib/rubygoal/players/fast.rb +16 -0
- data/lib/rubygoal/players/goalkeeper.rb +26 -0
- data/lib/rubygoal/players/player_movement.rb +98 -0
- data/lib/rubygoal/recorder.rb +56 -0
- data/lib/rubygoal/simulator.rb +33 -0
- data/lib/rubygoal/team.rb +198 -0
- data/lib/rubygoal/teams/away.rb +15 -0
- data/lib/rubygoal/teams/home.rb +15 -0
- data/lib/rubygoal/util.rb +36 -0
- data/lib/rubygoal/version.rb +3 -0
- data/test/ball_test.rb +56 -0
- data/test/coach_test.rb +49 -0
- data/test/field_test.rb +103 -0
- data/test/fixtures/four_fast_players_coach_definition.rb +27 -0
- data/test/fixtures/less_players_coach_definition.rb +25 -0
- data/test/fixtures/mirror_strategy_coach_definition.rb +42 -0
- data/test/fixtures/more_players_coach_definition.rb +27 -0
- data/test/fixtures/test_away_coach_definition.rb +34 -0
- data/test/fixtures/test_home_coach_definition.rb +34 -0
- data/test/fixtures/two_captains_coach_definition.rb +27 -0
- data/test/fixtures/valid_coach_definition.rb +26 -0
- data/test/formation_test.rb +81 -0
- data/test/game_test.rb +84 -0
- data/test/match_data_test.rb +149 -0
- data/test/player_test.rb +125 -0
- data/test/players/goalkeeper_test.rb +49 -0
- data/test/players/player_movement_test.rb +76 -0
- data/test/recorder_test.rb +118 -0
- data/test/team_test.rb +97 -0
- data/test/test_helper.rb +21 -0
- metadata +158 -0
@@ -0,0 +1,149 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
module Rubygoal
|
4
|
+
class MatchDataTest < Minitest::Test
|
5
|
+
def test_home_team_is_winning
|
6
|
+
match_data = create_match_data(
|
7
|
+
:home,
|
8
|
+
score_home: 2,
|
9
|
+
score_away: 0
|
10
|
+
)
|
11
|
+
|
12
|
+
assert match_data.me.winning?
|
13
|
+
assert match_data.other.losing?
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_home_team_is_losing
|
17
|
+
match_data = create_match_data(
|
18
|
+
:home,
|
19
|
+
score_home: 1,
|
20
|
+
score_away: 3
|
21
|
+
)
|
22
|
+
|
23
|
+
assert match_data.me.losing?
|
24
|
+
assert match_data.other.winning?
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_away_team_is_winning
|
28
|
+
match_data = create_match_data(
|
29
|
+
:away,
|
30
|
+
score_home: 0,
|
31
|
+
score_away: 2
|
32
|
+
)
|
33
|
+
|
34
|
+
assert match_data.me.winning?
|
35
|
+
assert match_data.other.losing?
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_away_team_is_losing
|
39
|
+
match_data = create_match_data(
|
40
|
+
:away,
|
41
|
+
score_home: 3,
|
42
|
+
score_away: 1
|
43
|
+
)
|
44
|
+
|
45
|
+
assert match_data.me.losing?
|
46
|
+
assert match_data.other.winning?
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_home_team_is_a_draw
|
50
|
+
match_data = create_match_data(
|
51
|
+
:home,
|
52
|
+
score_home: 1,
|
53
|
+
score_away: 1
|
54
|
+
)
|
55
|
+
|
56
|
+
assert match_data.me.draw?
|
57
|
+
assert match_data.other.draw?
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_away_team_is_a_draw
|
61
|
+
match_data = create_match_data(
|
62
|
+
:away,
|
63
|
+
score_home: 1,
|
64
|
+
score_away: 1
|
65
|
+
)
|
66
|
+
|
67
|
+
assert match_data.me.draw?
|
68
|
+
assert match_data.other.draw?
|
69
|
+
end
|
70
|
+
|
71
|
+
def test_match_info_includes_time
|
72
|
+
match_data = create_match_data(
|
73
|
+
:away,
|
74
|
+
time: 100
|
75
|
+
)
|
76
|
+
|
77
|
+
assert_equal 100, match_data.time
|
78
|
+
end
|
79
|
+
|
80
|
+
def test_match_info_includes_player_positions
|
81
|
+
match_data = create_match_data(
|
82
|
+
:away,
|
83
|
+
home_players_positions: {
|
84
|
+
name: Position.new(Field::WIDTH, Field::HEIGHT)
|
85
|
+
},
|
86
|
+
away_players_positions: {
|
87
|
+
name: Position.new(Field::WIDTH / 2, 0)
|
88
|
+
}
|
89
|
+
)
|
90
|
+
|
91
|
+
assert_equal({ name: Position.new(50, 0) }, match_data.me.positions)
|
92
|
+
assert_equal({ name: Position.new(100, 100) }, match_data.other.positions)
|
93
|
+
end
|
94
|
+
|
95
|
+
def test_match_info_exclude_goalkeeper_position
|
96
|
+
match_data = create_match_data(
|
97
|
+
:home,
|
98
|
+
home_players_positions: {
|
99
|
+
goalkeeper: Position.new(Field::WIDTH, Field::HEIGHT),
|
100
|
+
name: Position.new(Field::WIDTH, Field::HEIGHT)
|
101
|
+
},
|
102
|
+
)
|
103
|
+
|
104
|
+
assert_equal({ name: Position.new(100, 100) }, match_data.me.positions)
|
105
|
+
end
|
106
|
+
|
107
|
+
def test_home_match_info_includes_ball_position
|
108
|
+
match_data = create_match_data(
|
109
|
+
:home,
|
110
|
+
ball_position: Field.absolute_position(
|
111
|
+
Position.new(Field::WIDTH / 4, Field::HEIGHT / 2),
|
112
|
+
:home
|
113
|
+
)
|
114
|
+
)
|
115
|
+
|
116
|
+
assert_equal Position.new(25, 50), match_data.ball
|
117
|
+
end
|
118
|
+
|
119
|
+
def test_away_match_info_includes_ball_position
|
120
|
+
match_data = create_match_data(
|
121
|
+
:away,
|
122
|
+
ball_position: Field.absolute_position(
|
123
|
+
Position.new(Field::WIDTH / 4, Field::HEIGHT / 2),
|
124
|
+
:away
|
125
|
+
)
|
126
|
+
)
|
127
|
+
|
128
|
+
assert_equal Position.new(25, 50), match_data.ball
|
129
|
+
end
|
130
|
+
|
131
|
+
private
|
132
|
+
|
133
|
+
def create_match_data(side, game_options)
|
134
|
+
MatchData::Factory.new(game_test_double(game_options), side)
|
135
|
+
.create
|
136
|
+
end
|
137
|
+
|
138
|
+
def game_test_double(game_options)
|
139
|
+
OpenStruct.new({
|
140
|
+
time: 0,
|
141
|
+
score_home: 0,
|
142
|
+
score_away: 0,
|
143
|
+
ball_position: Field.center_position,
|
144
|
+
home_players_positions: {},
|
145
|
+
away_players_positions: {}
|
146
|
+
}.merge(game_options))
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
data/test/player_test.rb
ADDED
@@ -0,0 +1,125 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
module Rubygoal
|
4
|
+
class PlayerTest < Minitest::Test
|
5
|
+
def setup
|
6
|
+
home_coach = Coach.new(TestHomeCoachDefinition.new)
|
7
|
+
away_coach = Coach.new(TestAwayCoachDefinition.new)
|
8
|
+
@game = Game.new(home_coach, away_coach)
|
9
|
+
|
10
|
+
@player = game.players.first
|
11
|
+
@player.send(:time_to_kick_again=, 0)
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_player_can_kick_the_ball_in_the_same_position
|
15
|
+
position = Position.new(100, 100)
|
16
|
+
game.ball.position = position
|
17
|
+
player.position = position
|
18
|
+
|
19
|
+
assert player.can_kick?(game.ball)
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_player_can_kick_the_ball_when_is_close
|
23
|
+
game.ball.position = Position.new(100, 100)
|
24
|
+
player.position = Position.new(110, 115)
|
25
|
+
|
26
|
+
assert player.can_kick?(game.ball)
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_player_can_not_kick_the_ball_when_is_far
|
30
|
+
game.ball.position = Position.new(100, 100)
|
31
|
+
player.position = Position.new(200, 200)
|
32
|
+
|
33
|
+
refute player.can_kick?(game.ball)
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_player_can_not_kick_the_ball_again
|
37
|
+
position = Position.new(100, 100)
|
38
|
+
game.ball.position = position
|
39
|
+
player.position = position
|
40
|
+
|
41
|
+
player.kick(game.ball, Position.new(300, 300))
|
42
|
+
|
43
|
+
refute player.can_kick?(game.ball)
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_player_can_kick_the_ball_again_after_time
|
47
|
+
position = Position.new(100, 100)
|
48
|
+
game.ball.position = position
|
49
|
+
player.position = position
|
50
|
+
|
51
|
+
player.kick(game.ball, Position.new(300, 300))
|
52
|
+
player.update(Rubygoal.configuration.kick_again_delay)
|
53
|
+
|
54
|
+
assert player.can_kick?(game.ball)
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_kick_the_ball_to_a_different_place
|
58
|
+
position = Position.new(100, 100)
|
59
|
+
game.ball.position = position
|
60
|
+
game.ball.velocity = Velocity.new(0, 0)
|
61
|
+
player.position = position
|
62
|
+
|
63
|
+
player.kick(game.ball, Position.new(300, 300))
|
64
|
+
|
65
|
+
refute_equal Velocity.new(0, 0), game.ball.velocity
|
66
|
+
end
|
67
|
+
|
68
|
+
def test_kick_direction_range_right
|
69
|
+
# Set little error: < 2 degrees (180 * 0.01 < 2)
|
70
|
+
player.instance_variable_set(:@error, 0.01)
|
71
|
+
|
72
|
+
position = Position.new(100, 100)
|
73
|
+
game.ball.position = position
|
74
|
+
game.ball.velocity = Velocity.new(0, 0)
|
75
|
+
player.position = position
|
76
|
+
|
77
|
+
# 0 degree kick
|
78
|
+
player.kick(game.ball, Position.new(200, 100))
|
79
|
+
|
80
|
+
velocity = game.ball.velocity
|
81
|
+
velocity_angle = Util.angle(0, 0, velocity.x, velocity.y)
|
82
|
+
|
83
|
+
assert_in_delta 0, velocity_angle, 2
|
84
|
+
end
|
85
|
+
|
86
|
+
def test_kick_direction_range_left
|
87
|
+
# Set little error: < 2 degrees (180 * 0.01 < 2)
|
88
|
+
player.instance_variable_set(:@error, 0.01)
|
89
|
+
|
90
|
+
position = Position.new(100, 100)
|
91
|
+
game.ball.position = position
|
92
|
+
game.ball.velocity = Velocity.new(0, 0)
|
93
|
+
player.position = position
|
94
|
+
|
95
|
+
# 180 degree kick
|
96
|
+
player.kick(game.ball, Position.new(0, 100))
|
97
|
+
|
98
|
+
velocity = game.ball.velocity
|
99
|
+
velocity_angle = Util.positive_angle(0, 0, velocity.x, velocity.y)
|
100
|
+
|
101
|
+
assert_in_delta 180, velocity_angle, 2
|
102
|
+
end
|
103
|
+
|
104
|
+
def test_kick_strength
|
105
|
+
# Set little error: distance error = 1 (20 * 0.05 = 1)
|
106
|
+
player.instance_variable_set(:@error, 0.05)
|
107
|
+
|
108
|
+
position = Position.new(100, 100)
|
109
|
+
game.ball.position = position
|
110
|
+
game.ball.velocity = Velocity.new(0, 0)
|
111
|
+
player.position = position
|
112
|
+
|
113
|
+
player.kick(game.ball, Position.new(200, 200))
|
114
|
+
|
115
|
+
velocity = game.ball.velocity
|
116
|
+
velocity_strength = Util.distance(0, 0, velocity.x, velocity.y)
|
117
|
+
|
118
|
+
assert_in_delta 20, velocity_strength, 1
|
119
|
+
end
|
120
|
+
|
121
|
+
private
|
122
|
+
|
123
|
+
attr_reader :game, :player
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
module Rubygoal
|
4
|
+
class GaolKeeperPlayerTest < Minitest::Test
|
5
|
+
def setup
|
6
|
+
home_coach = Coach.new(TestHomeCoachDefinition.new)
|
7
|
+
away_coach = Coach.new(TestAwayCoachDefinition.new)
|
8
|
+
@game = Game.new(home_coach, away_coach)
|
9
|
+
|
10
|
+
@player = game.players.first
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_goalkeeper_is_already_covering_goal
|
14
|
+
goal_pos = Field.goal_position(:home)
|
15
|
+
game.ball.position = Position.new(300, goal_pos.y)
|
16
|
+
|
17
|
+
player.move_to_cover_goal(game.ball)
|
18
|
+
player.update(elapsed_time)
|
19
|
+
|
20
|
+
assert_equal Velocity.new(0, 0), player.velocity
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_goalkeeper_moves_down_to_cover_goal
|
24
|
+
game.ball.position = Position.new(300, 300)
|
25
|
+
|
26
|
+
player.move_to_cover_goal(game.ball)
|
27
|
+
player.update(elapsed_time)
|
28
|
+
|
29
|
+
assert_in_delta Velocity.new(0, -3.5), player.velocity, 0.001
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_goalkeeper_moves_up_to_cover_goal
|
33
|
+
game.ball.position = Position.new(300, 900)
|
34
|
+
|
35
|
+
player.move_to_cover_goal(game.ball)
|
36
|
+
player.update(elapsed_time)
|
37
|
+
|
38
|
+
assert_in_delta Velocity.new(0, 3.5), player.velocity, 0.001
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
attr_reader :game, :player
|
44
|
+
|
45
|
+
def elapsed_time
|
46
|
+
1 / 60.0
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
module Rubygoal
|
4
|
+
class PlayerMovementTest < Minitest::Test
|
5
|
+
def setup
|
6
|
+
home_coach = Coach.new(TestHomeCoachDefinition.new)
|
7
|
+
away_coach = Coach.new(TestAwayCoachDefinition.new)
|
8
|
+
game = Game.new(home_coach, away_coach)
|
9
|
+
home_team = game.team_home
|
10
|
+
|
11
|
+
@player = home_team.players.values[0]
|
12
|
+
@other_player = home_team.players.values[1]
|
13
|
+
|
14
|
+
@player_movement = PlayerMovement.new(game, player)
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_do_not_modify_velocity_if_not_blocker
|
18
|
+
player.position = Position.new(100, 0)
|
19
|
+
other_player.position = Position.new(170, 0)
|
20
|
+
|
21
|
+
player.move_to(Position.new(500, 0))
|
22
|
+
player_movement.update(elapsed_time)
|
23
|
+
|
24
|
+
assert_equal Velocity.new(3.5, 0), player.velocity
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_modify_velocity_if_there_is_a_blocker_very_close
|
28
|
+
player.position = Position.new(100, 0)
|
29
|
+
other_player.position = Position.new(149, 0)
|
30
|
+
|
31
|
+
player.move_to(Position.new(500, 0))
|
32
|
+
player_movement.update(elapsed_time)
|
33
|
+
|
34
|
+
assert_in_delta Velocity.new(2.5, -2.5), player.velocity, 0.1
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_decelerate_if_there_is_a_blocker_a_bit_close
|
38
|
+
player.position = Position.new(100, 0)
|
39
|
+
other_player.position = Position.new(169, 0)
|
40
|
+
|
41
|
+
player.move_to(Position.new(500, 0))
|
42
|
+
player_movement.update(elapsed_time)
|
43
|
+
|
44
|
+
assert_in_delta Velocity.new(3.1, 0), player.velocity, 0.1
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_stop_if_close_to_destination_and_there_is_blocker
|
48
|
+
player.position = Position.new(100, 0)
|
49
|
+
other_player.position = Position.new(149, 0)
|
50
|
+
|
51
|
+
player.move_to(Position.new(160, 0))
|
52
|
+
player_movement.update(elapsed_time)
|
53
|
+
|
54
|
+
assert_equal Velocity.new(0, 0), player.velocity
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_stop_if_blocker_is_ver_close_and_moving
|
58
|
+
player.position = Position.new(100, 0)
|
59
|
+
other_player.position = Position.new(150, 0)
|
60
|
+
|
61
|
+
player.move_to(Position.new(500, 0))
|
62
|
+
other_player.move_to(Position.new(150, 500))
|
63
|
+
player_movement.update(elapsed_time)
|
64
|
+
|
65
|
+
assert_equal Velocity.new(0, 0), player.velocity
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
attr_reader :player_movement, :player, :other_player
|
71
|
+
|
72
|
+
def elapsed_time
|
73
|
+
1 / 60.0
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'timecop'
|
3
|
+
|
4
|
+
require 'rubygoal/recorder'
|
5
|
+
|
6
|
+
module Rubygoal
|
7
|
+
class RecorderTest < Minitest::Test
|
8
|
+
def setup
|
9
|
+
Rubygoal.configuration.record_game = true
|
10
|
+
|
11
|
+
home_coach = Coach.new(TestHomeCoachDefinition.new)
|
12
|
+
away_coach = Coach.new(TestAwayCoachDefinition.new)
|
13
|
+
@game = Game.new(home_coach, away_coach)
|
14
|
+
@recorder = @game.recorder
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_recorded_team_names
|
18
|
+
expected_teams = {
|
19
|
+
home: 'Test Home Team',
|
20
|
+
away: 'Test Away Team'
|
21
|
+
}
|
22
|
+
|
23
|
+
assert_equal expected_teams, recorder.to_hash[:teams]
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_recorded_score
|
27
|
+
expected_score = {
|
28
|
+
home: 0,
|
29
|
+
away: 0
|
30
|
+
}
|
31
|
+
|
32
|
+
assert_equal expected_score, recorder.to_hash[:score]
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_initial_recorded_frames
|
36
|
+
assert_equal [], recorder.to_hash[:frames]
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_recorded_frame_after_first_update
|
40
|
+
@game.update
|
41
|
+
|
42
|
+
ball_position = Field.center_position
|
43
|
+
frames = recorder.to_hash[:frames]
|
44
|
+
|
45
|
+
assert_equal 1, frames.count
|
46
|
+
assert_in_delta 120, frames.first[:time], 0.001
|
47
|
+
assert_equal(
|
48
|
+
{ home: 0, away: 0 },
|
49
|
+
frames.first[:score]
|
50
|
+
)
|
51
|
+
assert_equal(
|
52
|
+
{ x: ball_position.x, y: ball_position.y},
|
53
|
+
frames.first[:ball]
|
54
|
+
)
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_recorded_frame_with_11_values_for_player_info
|
58
|
+
@game.update
|
59
|
+
|
60
|
+
first_frame = recorder.to_hash[:frames].first
|
61
|
+
home_players = first_frame[:home_players]
|
62
|
+
away_players = first_frame[:away_players]
|
63
|
+
|
64
|
+
assert_equal 11, home_players.count
|
65
|
+
assert_equal 11, away_players.count
|
66
|
+
end
|
67
|
+
|
68
|
+
def test_recorded_frame_with_detailed_player_info
|
69
|
+
@game.update
|
70
|
+
|
71
|
+
first_frame = recorder.to_hash[:frames].first
|
72
|
+
home_players = first_frame[:home_players]
|
73
|
+
away_players = first_frame[:away_players]
|
74
|
+
|
75
|
+
goalkeeper_field_pos = Position.new(50, Field::HEIGHT / 2)
|
76
|
+
goalkeeper_pos_home = Field.absolute_position(goalkeeper_field_pos, :home)
|
77
|
+
goalkeeper_pos_away = Field.absolute_position(goalkeeper_field_pos, :away)
|
78
|
+
|
79
|
+
assert_equal(
|
80
|
+
{
|
81
|
+
x: goalkeeper_pos_home.x,
|
82
|
+
y: goalkeeper_pos_home.y,
|
83
|
+
angle: 0,
|
84
|
+
type: :average
|
85
|
+
},
|
86
|
+
home_players.first
|
87
|
+
)
|
88
|
+
assert_equal(
|
89
|
+
{
|
90
|
+
x: goalkeeper_pos_away.x,
|
91
|
+
y: goalkeeper_pos_away.y,
|
92
|
+
angle: 0,
|
93
|
+
type: :average
|
94
|
+
},
|
95
|
+
away_players.first
|
96
|
+
)
|
97
|
+
end
|
98
|
+
|
99
|
+
def test_recorded_frame_some_updates
|
100
|
+
time = Time.now
|
101
|
+
21.times do
|
102
|
+
@game.update
|
103
|
+
time += 0.25
|
104
|
+
Timecop.travel(time)
|
105
|
+
end
|
106
|
+
|
107
|
+
frames = recorder.to_hash[:frames]
|
108
|
+
|
109
|
+
assert_equal 21, frames.count
|
110
|
+
assert_in_delta 120, frames.first[:time], 0.001
|
111
|
+
assert_in_delta 115, frames.last[:time], 0.001
|
112
|
+
end
|
113
|
+
|
114
|
+
private
|
115
|
+
|
116
|
+
attr_reader :game, :recorder
|
117
|
+
end
|
118
|
+
end
|