rubygoal-core 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|