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.
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