rubygoal 0.0.1

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.
@@ -0,0 +1,84 @@
1
+ require 'forwardable'
2
+ require 'gosu'
3
+
4
+ require 'rubygoal/ball'
5
+ require 'rubygoal/coach_loader'
6
+ require 'rubygoal/team'
7
+ require 'rubygoal/field_metrics'
8
+ require 'rubygoal/match'
9
+ require 'rubygoal/coaches/coach_home'
10
+ require 'rubygoal/coaches/coach_away'
11
+
12
+ module Rubygoal
13
+ class Field
14
+ attr_reader :ball, :team_home, :team_away
15
+
16
+ extend Forwardable
17
+ def_delegators :ball, :goal?
18
+
19
+ def initialize(game_window)
20
+ @game_window = game_window
21
+ @background_image = Gosu::Image.new(game_window, Config.field.background_image_file, true)
22
+
23
+ @ball = Ball.new(game_window, FieldMetrics.center_position)
24
+
25
+ coach_home = CoachLoader.get(CoachHome)
26
+ coach_away = CoachLoader.get(CoachAway)
27
+
28
+ puts "Home coach: #{coach_home.class.name}"
29
+ puts "Away coach: #{coach_away.class.name}"
30
+
31
+ @team_home = Team.new(game_window, :home, coach_home)
32
+ @team_away = Team.new(game_window, :away, coach_away)
33
+ end
34
+
35
+ def reinitialize
36
+ team_home.players_to_initial_position
37
+ team_away.players_to_initial_position
38
+ ball.position = FieldMetrics.center_position
39
+ end
40
+
41
+ def team_names
42
+ {
43
+ home: team_home.name,
44
+ away: team_away.name
45
+ }
46
+ end
47
+
48
+ def update
49
+ team_home.update(game_data(:home))
50
+ team_away.update(game_data(:away))
51
+ ball.update
52
+ end
53
+
54
+ def draw
55
+ background_image.draw(0, 0, 0);
56
+ ball.draw
57
+ team_home.draw
58
+ team_away.draw
59
+ end
60
+
61
+ private
62
+
63
+ def game_data(side)
64
+ case side
65
+ when :home
66
+ Match.new(
67
+ game_window.score_home,
68
+ game_window.score_away,
69
+ game_window.time,
70
+ team_away.formation
71
+ )
72
+ when :away
73
+ Match.new(
74
+ game_window.score_away,
75
+ game_window.score_home,
76
+ game_window.time,
77
+ team_home.formation
78
+ )
79
+ end
80
+ end
81
+
82
+ attr_reader :background_image, :game_window
83
+ end
84
+ end
@@ -0,0 +1,85 @@
1
+ require 'rubygoal/config'
2
+ require 'rubygoal/coordinate'
3
+ require 'rubygoal/field_metrics'
4
+
5
+ module Rubygoal
6
+ module FieldMetrics
7
+ class << self
8
+ def center_position
9
+ center = Position.new(
10
+ Config.field.width / 2,
11
+ Config.field.height / 2
12
+ )
13
+ Config.field.offset.add(center)
14
+ end
15
+
16
+ def goal_position(side)
17
+ position = center_position
18
+ case side
19
+ when :home
20
+ position.x = Config.field.offset.x
21
+ when :away
22
+ position.x = Config.field.offset.x + Config.field.width
23
+ end
24
+ position
25
+ end
26
+
27
+ def initial_player_positions(side)
28
+ Config.team.initial_player_positions.map do |pos|
29
+ case side
30
+ when :home
31
+ Position.new(
32
+ Config.field.offset.x + pos[0],
33
+ Config.field.offset.y + pos[1]
34
+ )
35
+ when :away
36
+ Position.new(
37
+ Config.field.offset.x + Config.field.width - pos[0],
38
+ Config.field.offset.y + Config.field.height - pos[1]
39
+ )
40
+ end
41
+ end
42
+ end
43
+
44
+ def goalkeeper_position(side)
45
+ initial_player_positions(side).first
46
+ end
47
+
48
+ def position_side(position)
49
+ if position.x < FieldMetrics.center_position.x
50
+ :home
51
+ else
52
+ :away
53
+ end
54
+ end
55
+
56
+ def out_of_bounds_width?(position)
57
+ lower_limit = Config.field.offset.x
58
+ upper_limit = Config.field.offset.x + Config.field.width
59
+ !(lower_limit..upper_limit).include?(position.x)
60
+ end
61
+
62
+ def out_of_bounds_height?(position)
63
+ lower_limit = Config.field.offset.y
64
+ upper_limit = Config.field.offset.y + Config.field.height
65
+ !(lower_limit..upper_limit).include?(position.y)
66
+ end
67
+
68
+ def goal?(position)
69
+ if out_of_bounds_width?(position)
70
+ lower_limit = center_position.y - Config.field.goal_height / 2
71
+ upper_limit = center_position.y + Config.field.goal_height / 2
72
+
73
+ (lower_limit..upper_limit).include?(position.y)
74
+ else
75
+ false
76
+ end
77
+ end
78
+
79
+ def close_to_goal?(position, side)
80
+ goal_position = FieldMetrics.goal_position(side)
81
+ goal_position.distance(position) < Config.field.close_to_goal_distance
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,69 @@
1
+ module Rubygoal
2
+ class Formation
3
+ attr_accessor :lineup
4
+
5
+ def initialize
6
+ @lineup = [
7
+ [:none, :none, :none, :none, :none],
8
+ [:none, :none, :none, :none, :none],
9
+ [:none, :none, :none, :none, :none],
10
+ [:none, :none, :none, :none, :none],
11
+ [:none, :none, :none, :none, :none],
12
+ ]
13
+ end
14
+
15
+ def defenders
16
+ column(0)
17
+ end
18
+
19
+ def midfielders
20
+ column(2)
21
+ end
22
+
23
+ def attackers
24
+ column(4)
25
+ end
26
+
27
+ def column(i)
28
+ lineup.map { |row| row[i] }
29
+ end
30
+
31
+ def defenders=(f)
32
+ assign_column(0, f)
33
+ end
34
+
35
+ def midfielders=(f)
36
+ assign_column(2, f)
37
+ end
38
+
39
+ def attackers=(f)
40
+ assign_column(4, f)
41
+ end
42
+
43
+ def assign_column(index, column)
44
+ column.each_with_index do |player, row_index|
45
+ lineup[row_index][index] = player
46
+ end
47
+ end
48
+
49
+ def errors
50
+ errors = {}
51
+
52
+ unified = lineup.flatten
53
+ captains = unified.count(:captain)
54
+ fast = unified.count(:fast)
55
+ average = unified.count(:average)
56
+ total = captains + fast + average
57
+
58
+ errors[:captains] = "La cantidad de capitanes es #{captains}" if captains != 1
59
+ errors[:fast] = "La cantidad de rapidos es #{fast}" if fast != 3
60
+ errors[:total] = "La cantidad total de jugadores es #{total}" if total != 10
61
+
62
+ errors
63
+ end
64
+
65
+ def valid?
66
+ errors.empty?
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,179 @@
1
+ require 'gosu'
2
+ require 'forwardable'
3
+
4
+ require 'rubygoal/coordinate'
5
+ require 'rubygoal/field'
6
+ require 'rubygoal/field_metrics'
7
+ require 'rubygoal/goal'
8
+
9
+ module Rubygoal
10
+ class Game < Gosu::Window
11
+ attr_reader :field, :time, :score_home, :score_away
12
+
13
+ extend Forwardable
14
+ def_delegators :field, :ball, :close_to_goal?
15
+
16
+ def initialize
17
+ super(1920, 1080, true)
18
+ self.caption = "Ruby Goal"
19
+
20
+ @field = Field.new(self)
21
+ @goal = Goal.new(self)
22
+
23
+ @state = :playing
24
+
25
+ @time = Config.game.time
26
+ @score_home = 0
27
+ @score_away = 0
28
+
29
+ @font = Gosu::Font.new(
30
+ self,
31
+ Gosu.default_font_name,
32
+ Config.game.default_font_size
33
+ )
34
+
35
+ @home_team_label = create_label_image(name_home)
36
+ @away_team_label = create_label_image(name_away)
37
+ end
38
+
39
+ def update
40
+ return if state == :ended
41
+
42
+ update_elapsed_time
43
+
44
+ if field.goal?
45
+ update_goal
46
+ else
47
+ update_remaining_time
48
+ field.update
49
+ end
50
+
51
+ end_match! if time <= 0
52
+ end
53
+
54
+ def draw
55
+ field.draw
56
+ draw_scoreboard
57
+ draw_team_labels
58
+ goal.draw if field.goal?
59
+ end
60
+
61
+ def button_down(id)
62
+ if id == Gosu::KbEscape
63
+ close
64
+ end
65
+ end
66
+
67
+ protected
68
+
69
+ attr_writer :time, :score_home, :score_away
70
+ attr_accessor :state, :last_time, :elapsed_time
71
+
72
+ private
73
+
74
+ attr_reader :font, :goal, :home_team_label, :away_team_label
75
+
76
+ def create_label_image(name)
77
+ name = truncate_label(name, Config.game.max_name_length)
78
+ Gosu::Image.from_text(
79
+ self,
80
+ name,
81
+ Gosu.default_font_name,
82
+ Config.game.team_label_font_size,
83
+ 1,
84
+ Config.game.team_label_width,
85
+ :center
86
+ )
87
+ end
88
+
89
+ def truncate_label(name, limit)
90
+ words = name.split
91
+
92
+ left = limit
93
+ truncated = []
94
+ words.each do |word|
95
+ break unless left > 0
96
+
97
+ truncated << word
98
+ left -= word.length + 1
99
+ end
100
+
101
+ truncated.join(' ')[0..limit]
102
+ end
103
+
104
+ def update_elapsed_time
105
+ self.last_time ||= Time.now
106
+
107
+ self.elapsed_time = Time.now - last_time
108
+ self.last_time = Time.now
109
+ end
110
+
111
+ def update_remaining_time
112
+ self.time -= elapsed_time
113
+ end
114
+
115
+ def update_goal
116
+ goal.update(elapsed_time)
117
+ reinitialize_match if goal.celebration_done?
118
+ end
119
+
120
+ def reinitialize_match
121
+ if FieldMetrics.position_side(ball.position) == :home
122
+ self.score_away += 1
123
+ else
124
+ self.score_home += 1
125
+ end
126
+
127
+ field.reinitialize
128
+ end
129
+
130
+ def draw_scoreboard
131
+ draw_text(time_text, Config.game.time_label_position, :gray)
132
+ draw_text(score_home.to_s, Config.game.score_home_label_position, :white)
133
+ draw_text(score_away.to_s, Config.game.score_away_label_position, :white)
134
+ end
135
+
136
+ def draw_team_labels
137
+ pos = Config.game.home_team_label_position
138
+ home_team_label.draw_rot(pos.x, pos.y, 1, -90)
139
+ pos = Config.game.away_team_label_position
140
+ away_team_label.draw_rot(pos.x, pos.y, 1, 90)
141
+ end
142
+
143
+ def draw_text(text, position, color, size = 1)
144
+ font.draw_rel(text, position.x, position.y, 0.5, 0.5, 1, size, size, color_to_hex(color))
145
+ end
146
+
147
+ def time_text
148
+ minutes = Integer(time) / 60
149
+ seconds = Integer(time) % 60
150
+ "%d:%02d" % [minutes, seconds]
151
+ end
152
+
153
+ def color_to_hex(color)
154
+ case color
155
+ when :white
156
+ 0xffffffff
157
+ when :gray
158
+ 0xff6d6e70
159
+ end
160
+ end
161
+
162
+ def end_match!
163
+ self.state = :ended
164
+ write_score
165
+ end
166
+
167
+ def write_score
168
+ puts "#{name_home} #{score_home} - #{score_away} #{name_away}"
169
+ end
170
+
171
+ def name_home
172
+ field.team_names[:home]
173
+ end
174
+
175
+ def name_away
176
+ field.team_names[:away]
177
+ end
178
+ end
179
+ end
@@ -0,0 +1,43 @@
1
+ require 'gosu'
2
+
3
+ require 'rubygoal/config'
4
+
5
+ module Rubygoal
6
+ class Goal
7
+ def initialize(game_window)
8
+ @goal_image = Gosu::Image.new(game_window, Config.goal.image_file, true)
9
+
10
+ @celebration_time = 0
11
+ end
12
+
13
+ def celebration_done?
14
+ !celebrating?
15
+ end
16
+
17
+ def draw
18
+ position = Config.game.goal_image_position
19
+ goal_image.draw(position.x, position.y, 1)
20
+ end
21
+
22
+ def update(elapsed_time)
23
+ start_celebration unless celebrating?
24
+ self.celebration_time -= elapsed_time
25
+ end
26
+
27
+ protected
28
+
29
+ attr_accessor :celebration_time
30
+
31
+ private
32
+
33
+ attr_reader :goal_image
34
+
35
+ def start_celebration
36
+ self.celebration_time = 3
37
+ end
38
+
39
+ def celebrating?
40
+ celebration_time > 0
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,52 @@
1
+ module Rubygoal
2
+ class Match
3
+ class Team
4
+ attr_reader :score, :result, :formation
5
+
6
+ def initialize(score, result, other_formation = nil)
7
+ @score = score
8
+ @result = result
9
+ @formation = other_formation
10
+ end
11
+
12
+ def draw?
13
+ result == :draw
14
+ end
15
+
16
+ def winning?
17
+ result == :win
18
+ end
19
+
20
+ def losing?
21
+ result == :lose
22
+ end
23
+ end
24
+
25
+ attr_reader :me, :other, :time
26
+
27
+ def initialize(my_score, other_score, time, other_formation)
28
+ @me = Match::Team.new(
29
+ my_score,
30
+ result(my_score, other_score)
31
+ )
32
+ @other = Match::Team.new(
33
+ other_score,
34
+ result(other_score, my_score),
35
+ other_formation
36
+ )
37
+ @time = time
38
+ end
39
+
40
+ private
41
+
42
+ def result(my_score, other_score)
43
+ if my_score > other_score
44
+ :win
45
+ elsif my_score < other_score
46
+ :lose
47
+ else
48
+ :draw
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,55 @@
1
+ require 'gosu'
2
+
3
+ require 'rubygoal/coordinate'
4
+
5
+ module Rubygoal
6
+ module Moveable
7
+ MIN_DISTANCE = 10
8
+
9
+ attr_accessor :position, :velocity
10
+
11
+ def initialize
12
+ @position = Position.new(0, 0)
13
+ @velocity = Velocity.new(0, 0)
14
+ @speed = 0
15
+ @destination = nil
16
+ end
17
+
18
+ def moving?
19
+ velocity.nonzero?
20
+ end
21
+
22
+ def distance(position)
23
+ Gosu.distance(self.position.x, self.position.y, position.x, position.y)
24
+ end
25
+
26
+ def move_to(destination)
27
+ self.destination = destination
28
+
29
+ angle = Gosu.angle(position.x, position.y, destination.x, destination.y)
30
+ velocity.x = Gosu.offset_x(angle, speed)
31
+ velocity.y = Gosu.offset_y(angle, speed)
32
+ end
33
+
34
+ def update
35
+ return unless moving?
36
+
37
+ if destination && distance(destination) < MIN_DISTANCE
38
+ stop
39
+ else
40
+ position.x += velocity.x
41
+ position.y += velocity.y
42
+ end
43
+ end
44
+
45
+ private
46
+
47
+ attr_reader :speed
48
+ attr_accessor :destination
49
+
50
+ def stop
51
+ self.destination = nil
52
+ self.velocity = Velocity.new(0, 0)
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,86 @@
1
+ require 'gosu'
2
+
3
+ require 'rubygoal/coordinate'
4
+ require 'rubygoal/moveable'
5
+
6
+ module Rubygoal
7
+ class Player
8
+ include Moveable
9
+
10
+ def initialize(game_window, side)
11
+ super()
12
+
13
+ @image = Gosu::Image.new(game_window, image_filename(side), false)
14
+ @time_to_kick_again = 0
15
+ @field = game_window.field
16
+ end
17
+
18
+ def can_kick?(ball)
19
+ !waiting_to_kick_again? && control_ball?(ball)
20
+ end
21
+
22
+ def kick(ball, target)
23
+ strength = Config.player.kick_strength * Random.rand(error_range)
24
+
25
+ direction = Gosu.angle(position.x, position.y, target.x, target.y)
26
+ direction *= Random.rand(error_range)
27
+
28
+ velocity = Velocity.new(
29
+ Gosu.offset_x(direction, strength),
30
+ Gosu.offset_y(direction, strength)
31
+ )
32
+ ball.velocity = velocity
33
+
34
+ reset_waiting_to_kick!
35
+ end
36
+
37
+ def update
38
+ update_waiting_to_kick!
39
+ super
40
+ end
41
+
42
+ def draw
43
+ if moving?
44
+ angle = Gosu.angle(position.x, position.y, destination.x, destination.y)
45
+ angle -= 90
46
+ else
47
+ angle = 0.0
48
+ end
49
+
50
+ image.draw_rot(position.x, position.y, 1, angle)
51
+ end
52
+
53
+ protected
54
+
55
+ attr_accessor :time_to_kick_again
56
+
57
+ private
58
+
59
+ attr_reader :field, :image, :error
60
+
61
+ def waiting_to_kick_again?
62
+ time_to_kick_again > 0
63
+ end
64
+
65
+ def reset_waiting_to_kick!
66
+ self.time_to_kick_again = Config.player.time_to_kick_again
67
+ end
68
+
69
+ def update_waiting_to_kick!
70
+ # TODO Make it time-based rather than counting ticks
71
+ self.time_to_kick_again -= 1 if waiting_to_kick_again?
72
+ end
73
+
74
+ def control_ball?(ball)
75
+ distance(ball.position) < Config.player.distance_control_ball
76
+ end
77
+
78
+ def error_range
79
+ (1.0 - error) .. (1.0 + error)
80
+ end
81
+ end
82
+ end
83
+
84
+ require 'rubygoal/players/average'
85
+ require 'rubygoal/players/fast'
86
+ require 'rubygoal/players/captain'
@@ -0,0 +1,20 @@
1
+ module Rubygoal
2
+ class AveragePlayer < Player
3
+ def initialize(*args)
4
+ super
5
+ @error = 0.10 + Random.rand(0.05)
6
+ @speed = 3.5
7
+ end
8
+
9
+ protected
10
+
11
+ def image_filename(side)
12
+ case side
13
+ when :home
14
+ Config.player.average_home_image_file
15
+ when :away
16
+ Config.player.average_away_image_file
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,20 @@
1
+ module Rubygoal
2
+ class CaptainPlayer < Player
3
+ def initialize(*args)
4
+ super
5
+ @error = 0.05
6
+ @speed = 4.5
7
+ end
8
+
9
+ protected
10
+
11
+ def image_filename(side)
12
+ case side
13
+ when :home
14
+ Config.player.captain_home_image_file
15
+ when :away
16
+ Config.player.captain_away_image_file
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,20 @@
1
+ module Rubygoal
2
+ class FastPlayer < Player
3
+ def initialize(*args)
4
+ super
5
+ @error = 0.10 + Random.rand(0.05)
6
+ @speed = 4
7
+ end
8
+
9
+ protected
10
+
11
+ def image_filename(side)
12
+ case side
13
+ when :home
14
+ Config.player.fast_home_image_file
15
+ when :away
16
+ Config.player.fast_away_image_file
17
+ end
18
+ end
19
+ end
20
+ end