sports 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,16 @@
1
+ module Sports
2
+
3
+ class Group
4
+ attr_reader :key, :name, :teams
5
+
6
+ def initialize( key: nil,
7
+ name:,
8
+ teams: )
9
+ @key = key ## e.g. A,B,C or 1,2,3, - note: always a string or nil
10
+ @name = name
11
+ @teams = teams
12
+ end
13
+ end # class Group
14
+
15
+ end # module Sports
16
+
@@ -0,0 +1,35 @@
1
+
2
+ module Sports
3
+
4
+
5
+ class League
6
+ attr_reader :key, :name, :country, :intl
7
+ attr_accessor :alt_names
8
+
9
+ ## special import only attribs
10
+ attr_accessor :alt_names_auto ## auto-generated alt names
11
+
12
+ def initialize( key:, name:, alt_names: [], alt_names_auto: [],
13
+ country: nil, intl: false, clubs: true )
14
+ @key = key
15
+ @name = name
16
+ @alt_names = alt_names
17
+ @alt_names_auto = alt_names_auto
18
+
19
+ @country = country
20
+ @intl = intl
21
+ @clubs = clubs
22
+ end
23
+
24
+ def intl?() @intl == true; end
25
+ def national?() @intl == false; end
26
+ alias_method :domestic?, :national?
27
+
28
+ def clubs?() @clubs == true; end
29
+ def national_teams?() @clubs == false; end
30
+ alias_method :club?, :clubs?
31
+ alias_method :national_team?, :national_teams?
32
+
33
+ end # class League
34
+
35
+ end # module Sports
@@ -0,0 +1,159 @@
1
+
2
+ module Sports
3
+
4
+
5
+ class Match
6
+
7
+ attr_reader :date,
8
+ :team1, :team2, ## todo/fix: use team1_name, team2_name or similar - for compat with db activerecord version? why? why not?
9
+ :score1, :score2, ## full time
10
+ :score1i, :score2i, ## half time (first (i) part)
11
+ :score1et, :score2et, ## extra time
12
+ :score1p, :score2p, ## penalty
13
+ :score1agg, :score2agg, ## full time (all legs) aggregated
14
+ :winner, # return 1,2,0 1 => team1, 2 => team2, 0 => draw/tie
15
+ :round, ## todo/fix: use round_num or similar - for compat with db activerecord version? why? why not?
16
+ :leg, ## e.g. '1','2','3','replay', etc. - use leg for marking **replay** too - keep/make leg numeric?! - why? why not?
17
+ :stage,
18
+ :group,
19
+ :status, ## e.g. replay, cancelled, awarded, abadoned, postponed, etc.
20
+ :conf1, :conf2, ## special case for mls e.g. conference1, conference2 (e.g. west, east, central)
21
+ :country1, :country2, ## special case for champions league etc. - uses FIFA country code
22
+ :comments,
23
+ :league ## (optinal) added as text for now (use struct?)
24
+
25
+
26
+ attr_accessor :goals ## todo/fix: make goals like all other attribs!!
27
+
28
+ def initialize( **kwargs )
29
+ update( kwargs ) unless kwargs.empty?
30
+ end
31
+
32
+ def update( **kwargs )
33
+ ## note: check with has_key? because value might be nil!!!
34
+ @date = kwargs[:date] if kwargs.has_key? :date
35
+
36
+ ## todo/fix: use team1_name, team2_name or similar - for compat with db activerecord version? why? why not?
37
+ @team1 = kwargs[:team1] if kwargs.has_key? :team1
38
+ @team2 = kwargs[:team2] if kwargs.has_key? :team2
39
+
40
+ @conf1 = kwargs[:conf1] if kwargs.has_key? :conf1
41
+ @conf2 = kwargs[:conf2] if kwargs.has_key? :conf2
42
+ @country1 = kwargs[:country1] if kwargs.has_key? :country1
43
+ @country2 = kwargs[:country2] if kwargs.has_key? :country2
44
+
45
+ ## note: round is a string!!! e.g. '1', '2' for matchday or 'Final', 'Semi-final', etc.
46
+ ## todo: use to_s - why? why not?
47
+ @round = kwargs[:round] if kwargs.has_key? :round
48
+ @stage = kwargs[:stage] if kwargs.has_key? :stage
49
+ @leg = kwargs[:leg] if kwargs.has_key? :leg
50
+ @group = kwargs[:group] if kwargs.has_key? :group
51
+ @status = kwargs[:status] if kwargs.has_key? :status
52
+ @comments = kwargs[:comments] if kwargs.has_key? :comments
53
+
54
+ @league = kwargs[:league] if kwargs.has_key? :league
55
+
56
+
57
+ if kwargs.has_key?( :score ) ## check all-in-one score struct for convenience!!!
58
+ score = kwargs[:score]
59
+ if score.nil? ## reset all score attribs to nil!!
60
+ @score1 = nil
61
+ @score1i = nil
62
+ @score1et = nil
63
+ @score1p = nil
64
+ ## @score1agg = nil
65
+
66
+ @score2 = nil
67
+ @score2i = nil
68
+ @score2et = nil
69
+ @score2p = nil
70
+ ## @score2agg = nil
71
+ else
72
+ @score1 = score.score1
73
+ @score1i = score.score1i
74
+ @score1et = score.score1et
75
+ @score1p = score.score1p
76
+ ## @score1agg = score.score1agg
77
+
78
+ @score2 = score.score2
79
+ @score2i = score.score2i
80
+ @score2et = score.score2et
81
+ @score2p = score.score2p
82
+ ## @score2agg = score.score2agg
83
+ end
84
+ else
85
+ @score1 = kwargs[:score1] if kwargs.has_key? :score1
86
+ @score1i = kwargs[:score1i] if kwargs.has_key? :score1i
87
+ @score1et = kwargs[:score1et] if kwargs.has_key? :score1et
88
+ @score1p = kwargs[:score1p] if kwargs.has_key? :score1p
89
+ @score1agg = kwargs[:score1agg] if kwargs.has_key? :score1agg
90
+
91
+ @score2 = kwargs[:score2] if kwargs.has_key? :score2
92
+ @score2i = kwargs[:score2i] if kwargs.has_key? :score2i
93
+ @score2et = kwargs[:score2et] if kwargs.has_key? :score2et
94
+ @score2p = kwargs[:score2p] if kwargs.has_key? :score2p
95
+ @score2agg = kwargs[:score2agg] if kwargs.has_key? :score2agg
96
+
97
+ ## note: (always) (auto-)convert scores to integers
98
+ @score1 = @score1.to_i if @score1
99
+ @score1i = @score1i.to_i if @score1i
100
+ @score1et = @score1et.to_i if @score1et
101
+ @score1p = @score1p.to_i if @score1p
102
+ @score1agg = @score1agg.to_i if @score1agg
103
+
104
+ @score2 = @score2.to_i if @score2
105
+ @score2i = @score2i.to_i if @score2i
106
+ @score2et = @score2et.to_i if @score2et
107
+ @score2p = @score2p.to_i if @score2p
108
+ @score2agg = @score2agg.to_i if @score2agg
109
+ end
110
+
111
+ ## todo/fix:
112
+ ## gr-greece/2014-15/G1.csv:
113
+ ## G1,10/05/15,Niki Volos,OFI,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
114
+ ##
115
+
116
+ ## for now score1 and score2 must be present
117
+ if @score1.nil? || @score2.nil?
118
+ puts "** WARN: missing scores for match:"
119
+ pp kwargs
120
+ ## exit 1
121
+ end
122
+
123
+ ## todo/fix: auto-calculate winner
124
+ # return 1,2,0 1 => team1, 2 => team2, 0 => draw/tie
125
+ ### calculate winner - use 1,2,0
126
+ if @score1 && @score2
127
+ if @score1 > @score2
128
+ @winner = 1
129
+ elsif @score2 > @score1
130
+ @winner = 2
131
+ elsif @score1 == @score2
132
+ @winner = 0
133
+ else
134
+ end
135
+ else
136
+ @winner = nil # unknown / undefined
137
+ end
138
+
139
+ self ## note - MUST return self for chaining
140
+ end
141
+
142
+
143
+
144
+ def over?() true; end ## for now all matches are over - in the future check date!!!
145
+ def complete?() true; end ## for now all scores are complete - in the future check scores; might be missing - not yet entered
146
+
147
+
148
+ def score_str # pretty print (full time) scores; convenience method
149
+ "#{@score1}-#{@score2}"
150
+ end
151
+
152
+ def scorei_str # pretty print (half time) scores; convenience method
153
+ "#{@score1i}-#{@score2i}"
154
+ end
155
+ end # class Match
156
+
157
+ end # module Sports
158
+
159
+
@@ -0,0 +1,215 @@
1
+
2
+
3
+ module Sports
4
+
5
+
6
+ class Matchlist ## todo: find a better name - MatchStats, MatchFixtures, MatchSchedule, ...
7
+ ## use MatchCache/Buffer/Summary/Snippet/Segment/List...
8
+ ## or MatchAnalyzer/Checker/Proofer/Query - why? why not?
9
+ attr_reader :matches # count of matches
10
+ ## :name,
11
+ ## :goals, # count of (total) goals - use total_goals - why? why not?
12
+ ## :teams, -- has its own reader
13
+ ## :rounds # note: use if all teams have same match count
14
+ ## add last_updated/updated or something - why? why not?
15
+
16
+ def initialize( matches )
17
+ @matches = matches
18
+ end
19
+
20
+
21
+ def usage
22
+ @usage ||= build_usage( @matches )
23
+ @usage
24
+ end
25
+
26
+ def team_usage() usage.team_usage; end
27
+
28
+ def teams
29
+ @team_names ||= team_usage.keys.sort
30
+ @team_names
31
+ end
32
+
33
+ def goals() usage.goals; end
34
+
35
+ ## note: start_date and end_date might be nil / optional missing!!!!
36
+ def start_date?() usage.start_date?; end
37
+ def end_date?() usage.end_date?; end
38
+
39
+ def start_date() usage.start_date; end
40
+ def end_date() usage.end_date; end
41
+
42
+ def has_dates?() usage.has_dates?; end
43
+ def dates_str() usage.dates_str; end
44
+ def days() usage.days; end
45
+
46
+
47
+ def rounds() usage.rounds; end
48
+
49
+ ## todo: add has_rounds? alias for rounds? too
50
+ ## return true if all match_played in team_usage are the same
51
+ ## e.g. assumes league with matchday rounds
52
+ def rounds?() usage.rounds?; end
53
+
54
+ def match_counts() usage.match_counts; end
55
+ def match_counts_str() usage.match_counts_str; end
56
+
57
+
58
+
59
+ def stage_usage
60
+ @stage_usage ||= build_stage_usage( @matches )
61
+ @stage_usage
62
+ end
63
+
64
+ def stages() stage_usage.keys; end ## note: returns empty array for stages for now - why? why not?
65
+
66
+
67
+ ############################
68
+ # matchlist helpers
69
+ private
70
+ class StatLine
71
+ attr_reader :team_usage,
72
+ :matches,
73
+ :goals,
74
+ :rounds, ## keep rounds - why? why not?
75
+ :start_date,
76
+ :end_date
77
+
78
+ def teams() @team_usage.keys.sort; end ## (auto-)sort here always - why? why not?
79
+
80
+ def start_date?() @start_date.nil? == false; end
81
+ def end_date?() @end_date.nil? == false; end
82
+
83
+ def has_dates?() @start_date && @end_date; end
84
+ def dates_str
85
+ ## note: start_date/end_date might be optional/missing
86
+ if has_dates?
87
+ "#{start_date.strftime( '%a %d %b %Y' )} - #{end_date.strftime( '%a %d %b %Y' )}"
88
+ else
89
+ "??? - ???"
90
+ end
91
+ end
92
+
93
+ def days() end_date.jd - start_date.jd; end
94
+
95
+
96
+ def rounds
97
+ rounds? ## note: use rounds? to calculate (cache) rounds
98
+ @rounds ## note: return number of rounds or nil (for uneven matches played by teams)
99
+ end
100
+
101
+ ## todo: add has_rounds? alias for rounds? too
102
+ def rounds?
103
+ ## return true if all match_played in team_usage are the same
104
+ ## e.g. assumes league with matchday rounds
105
+ if @has_rounds.nil? ## check/todo: if undefined attribute is nil by default??
106
+ ## check/calc rounds
107
+ ## note: values => matches_played by team
108
+ if match_counts.size == 1
109
+ @rounds = match_counts[0][0]
110
+ else
111
+ @rounds = nil
112
+ end
113
+ @has_rounds = @rounds ? true : false
114
+ end
115
+ @has_rounds
116
+ end
117
+
118
+
119
+ def build_match_counts ## use/rename to matches_played - why? why not?
120
+ counts = Hash.new(0)
121
+ team_usage.values.each do |count|
122
+ counts[count] += 1
123
+ end
124
+
125
+ ## sort (descending) highest usage value first (in returned array)
126
+ ## e.g. [[32,8],[31,2]] ## 32 matches by 8 teams, 31 matches by 2 teams etc.
127
+ counts.sort_by {|count, usage| -count }
128
+ end
129
+
130
+ def match_counts
131
+ # match counts / nos played per team
132
+ @match_counts ||= build_match_counts
133
+ @match_counts
134
+ end
135
+
136
+ def match_counts_str
137
+ ## pretty print / formatted match_counts
138
+ buf = String.new('')
139
+ match_counts.each_with_index do |rec,i|
140
+ buf << ' ' if i > 0 ## add (space) separator
141
+ buf << "#{rec[0]}×#{rec[1]}"
142
+ end
143
+ buf
144
+ end
145
+
146
+
147
+
148
+ def initialize
149
+ @matches = 0
150
+ @goals = 0
151
+
152
+ @start_date = nil
153
+ @end_date = nil
154
+
155
+ @team_usage = Hash.new(0)
156
+
157
+ @match_counts = nil
158
+ end
159
+
160
+
161
+ def update( match )
162
+ @matches += 1 ## match counter
163
+
164
+ if match.score1 && match.score2
165
+ @goals += match.score1
166
+ @goals += match.score2
167
+
168
+ ## todo: add after extra time? if knock out (k.o.) - why? why not?
169
+ ## make it a flag/opt?
170
+ end
171
+
172
+ @team_usage[ match.team1 ] += 1
173
+ @team_usage[ match.team2 ] += 1
174
+
175
+ if match.date
176
+ ## return / store date as string as is - why? why not?
177
+ date = Date.strptime( match.date, '%Y-%m-%d' )
178
+
179
+ @start_date = date if @start_date.nil? || date < @start_date
180
+ @end_date = date if @end_date.nil? || date > @end_date
181
+ end
182
+ end
183
+ end # class StatLine
184
+
185
+
186
+ ## collect total usage stats (for all matches)
187
+ def build_usage( matches )
188
+ stat = StatLine.new
189
+ matches.each do |match|
190
+ stat.update( match )
191
+ end
192
+ stat
193
+ end
194
+
195
+ ## collect usage stats by stage (e.g. regular / playoff / etc.)
196
+ def build_stage_usage( matches )
197
+ stages = {}
198
+
199
+ matches.each do |match|
200
+ stage_key = if match.stage.nil?
201
+ 'Regular' ## note: assume Regular stage if not defined (AND not explicit unknown)
202
+ else
203
+ match.stage
204
+ end
205
+
206
+ stages[ stage_key ] ||= StatLine.new
207
+ stages[ stage_key ].update( match )
208
+ end
209
+
210
+ stages
211
+ end
212
+
213
+ end # class Matchlist
214
+
215
+ end # module Sports
@@ -0,0 +1,23 @@
1
+ module Sports
2
+
3
+ class Round
4
+ attr_reader :name, :start_date, :end_date, :knockout
5
+ attr_accessor :num # note: make read & writable - why? why not?
6
+
7
+ def initialize( name:,
8
+ num: nil,
9
+ start_date: nil,
10
+ end_date: nil,
11
+ knockout: false,
12
+ auto: true )
13
+ @name = name
14
+ @num = num
15
+ @start_date = start_date
16
+ @end_date = end_date
17
+ @knockout = knockout
18
+ @auto = auto # auto-created (inline reference/header without proper definition before)
19
+ end
20
+ end # class Round
21
+
22
+ end # module Sports
23
+