rubygoal-core 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +18 -0
  3. data/README.md +85 -0
  4. data/bin/rubygoal +15 -0
  5. data/lib/rubygoal.rb +1 -0
  6. data/lib/rubygoal/ball.rb +60 -0
  7. data/lib/rubygoal/coach.rb +81 -0
  8. data/lib/rubygoal/coach_definition.rb +54 -0
  9. data/lib/rubygoal/coach_loader.rb +55 -0
  10. data/lib/rubygoal/coaches/coach_definition_away.rb +72 -0
  11. data/lib/rubygoal/coaches/coach_definition_home.rb +76 -0
  12. data/lib/rubygoal/configuration.rb +49 -0
  13. data/lib/rubygoal/coordinate.rb +33 -0
  14. data/lib/rubygoal/field.rb +134 -0
  15. data/lib/rubygoal/formation.rb +73 -0
  16. data/lib/rubygoal/formation/formation_dsl.rb +67 -0
  17. data/lib/rubygoal/game.rb +162 -0
  18. data/lib/rubygoal/goal.rb +26 -0
  19. data/lib/rubygoal/match_data.rb +130 -0
  20. data/lib/rubygoal/moveable.rb +67 -0
  21. data/lib/rubygoal/player.rb +87 -0
  22. data/lib/rubygoal/players/average.rb +16 -0
  23. data/lib/rubygoal/players/captain.rb +15 -0
  24. data/lib/rubygoal/players/fast.rb +16 -0
  25. data/lib/rubygoal/players/goalkeeper.rb +26 -0
  26. data/lib/rubygoal/players/player_movement.rb +98 -0
  27. data/lib/rubygoal/recorder.rb +56 -0
  28. data/lib/rubygoal/simulator.rb +33 -0
  29. data/lib/rubygoal/team.rb +198 -0
  30. data/lib/rubygoal/teams/away.rb +15 -0
  31. data/lib/rubygoal/teams/home.rb +15 -0
  32. data/lib/rubygoal/util.rb +36 -0
  33. data/lib/rubygoal/version.rb +3 -0
  34. data/test/ball_test.rb +56 -0
  35. data/test/coach_test.rb +49 -0
  36. data/test/field_test.rb +103 -0
  37. data/test/fixtures/four_fast_players_coach_definition.rb +27 -0
  38. data/test/fixtures/less_players_coach_definition.rb +25 -0
  39. data/test/fixtures/mirror_strategy_coach_definition.rb +42 -0
  40. data/test/fixtures/more_players_coach_definition.rb +27 -0
  41. data/test/fixtures/test_away_coach_definition.rb +34 -0
  42. data/test/fixtures/test_home_coach_definition.rb +34 -0
  43. data/test/fixtures/two_captains_coach_definition.rb +27 -0
  44. data/test/fixtures/valid_coach_definition.rb +26 -0
  45. data/test/formation_test.rb +81 -0
  46. data/test/game_test.rb +84 -0
  47. data/test/match_data_test.rb +149 -0
  48. data/test/player_test.rb +125 -0
  49. data/test/players/goalkeeper_test.rb +49 -0
  50. data/test/players/player_movement_test.rb +76 -0
  51. data/test/recorder_test.rb +118 -0
  52. data/test/team_test.rb +97 -0
  53. data/test/test_helper.rb +21 -0
  54. 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
@@ -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