bagel 0.1.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.
- checksums.yaml +7 -0
- data/.gitignore +13 -0
- data/.prettierignore +2 -0
- data/.rspec +3 -0
- data/.rubocop.yml +13 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +187 -0
- data/Rakefile +12 -0
- data/bagel.gemspec +32 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/lib/bagel/colors.rb +8 -0
- data/lib/bagel/fonts.rb +3 -0
- data/lib/bagel/graphic/comment.rb +49 -0
- data/lib/bagel/graphic/intro.rb +49 -0
- data/lib/bagel/graphic/scoreboard/context.rb +28 -0
- data/lib/bagel/graphic/scoreboard/games.rb +13 -0
- data/lib/bagel/graphic/scoreboard/name.rb +32 -0
- data/lib/bagel/graphic/scoreboard/points.rb +13 -0
- data/lib/bagel/graphic/scoreboard/score.rb +38 -0
- data/lib/bagel/graphic/scoreboard/server.rb +16 -0
- data/lib/bagel/graphic/scoreboard/sets.rb +13 -0
- data/lib/bagel/graphic/scoreboard.rb +102 -0
- data/lib/bagel/graphic/stats/duration.rb +33 -0
- data/lib/bagel/graphic/stats/header.rb +32 -0
- data/lib/bagel/graphic/stats/result.rb +41 -0
- data/lib/bagel/graphic/stats/stat.rb +48 -0
- data/lib/bagel/graphic/stats.rb +80 -0
- data/lib/bagel/graphic.rb +23 -0
- data/lib/bagel/tennis/checker.rb +100 -0
- data/lib/bagel/tennis/outcome.rb +48 -0
- data/lib/bagel/tennis/player.rb +22 -0
- data/lib/bagel/tennis/point.rb +17 -0
- data/lib/bagel/tennis/score.rb +68 -0
- data/lib/bagel/tennis/scoring.rb +26 -0
- data/lib/bagel/tennis/serve.rb +36 -0
- data/lib/bagel/tennis/stats/aces.rb +28 -0
- data/lib/bagel/tennis/stats/break_points_won.rb +33 -0
- data/lib/bagel/tennis/stats/double_faults.rb +28 -0
- data/lib/bagel/tennis/stats/first_serve_in.rb +33 -0
- data/lib/bagel/tennis/stats/first_serve_points_won.rb +33 -0
- data/lib/bagel/tennis/stats/net_points_won.rb +33 -0
- data/lib/bagel/tennis/stats/points_won.rb +24 -0
- data/lib/bagel/tennis/stats/second_serve_points_won.rb +33 -0
- data/lib/bagel/tennis/stats/stat.rb +40 -0
- data/lib/bagel/tennis/stats/unforced_errors.rb +24 -0
- data/lib/bagel/tennis/stats/winners.rb +24 -0
- data/lib/bagel/tennis/stats.rb +16 -0
- data/lib/bagel/tennis.rb +13 -0
- data/lib/bagel/timeframe.rb +31 -0
- data/lib/bagel/version.rb +5 -0
- data/lib/bagel/video/clip.rb +41 -0
- data/lib/bagel/video/clip_builder_factory.rb +13 -0
- data/lib/bagel/video/clip_builders/clip_builder.rb +32 -0
- data/lib/bagel/video/clip_builders/intro_clip_builder.rb +37 -0
- data/lib/bagel/video/clip_builders/point_clip_builder.rb +67 -0
- data/lib/bagel/video/clip_builders/stats_clip_builder.rb +56 -0
- data/lib/bagel/video/clip_data_parser.rb +39 -0
- data/lib/bagel/video/clip_director.rb +16 -0
- data/lib/bagel/video/clip_saver.rb +59 -0
- data/lib/bagel/video/fade.rb +24 -0
- data/lib/bagel/video/ffmpeg/concat_file.rb +45 -0
- data/lib/bagel/video/ffmpeg/fade_filter.rb +34 -0
- data/lib/bagel/video/ffmpeg/overlay_filter.rb +52 -0
- data/lib/bagel/video/ffmpeg.rb +41 -0
- data/lib/bagel/video/orientation.rb +8 -0
- data/lib/bagel/video/overlays/comment.rb +15 -0
- data/lib/bagel/video/overlays/intro.rb +15 -0
- data/lib/bagel/video/overlays/overlay.rb +28 -0
- data/lib/bagel/video/overlays/scoreboard.rb +15 -0
- data/lib/bagel/video/overlays/second_serve.rb +15 -0
- data/lib/bagel/video/overlays/stats.rb +15 -0
- data/lib/bagel/video/padding.rb +9 -0
- data/lib/bagel/video/video.rb +18 -0
- data/lib/bagel/video.rb +32 -0
- data/lib/bagel.rb +76 -0
- metadata +196 -0
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'bagel/graphic/intro'
|
2
|
+
require 'bagel/graphic/comment'
|
3
|
+
|
4
|
+
require 'bagel/graphic/scoreboard'
|
5
|
+
require 'bagel/graphic/scoreboard/context'
|
6
|
+
require 'bagel/graphic/scoreboard/server'
|
7
|
+
require 'bagel/graphic/scoreboard/name'
|
8
|
+
require 'bagel/graphic/scoreboard/score'
|
9
|
+
require 'bagel/graphic/scoreboard/sets'
|
10
|
+
require 'bagel/graphic/scoreboard/games'
|
11
|
+
require 'bagel/graphic/scoreboard/points'
|
12
|
+
|
13
|
+
require 'bagel/graphic/stats'
|
14
|
+
require 'bagel/graphic/stats/header'
|
15
|
+
require 'bagel/graphic/stats/result'
|
16
|
+
require 'bagel/graphic/stats/duration'
|
17
|
+
require 'bagel/graphic/stats/stat'
|
18
|
+
|
19
|
+
module Bagel
|
20
|
+
module Graphic
|
21
|
+
FILENAME_EXTENSION = '.png'
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
module Bagel::Tennis
|
2
|
+
class Checker
|
3
|
+
BREAK_POINT = 'BREAK POINT'
|
4
|
+
SET_POINT = 'SET POINT'
|
5
|
+
MATCH_POINT = 'MATCH POINT'
|
6
|
+
|
7
|
+
MINIMUM_POINTS_TO_WIN_GAME = 4
|
8
|
+
MINIMUM_POINTS_TO_WIN_TIEBREAK = 7
|
9
|
+
|
10
|
+
MINIMUM_GAMES_TO_WIN_SET = 6
|
11
|
+
SETS_TO_WIN_MATCH = 2
|
12
|
+
|
13
|
+
TIEBREAK_AT = [ MINIMUM_GAMES_TO_WIN_SET, MINIMUM_GAMES_TO_WIN_SET ]
|
14
|
+
|
15
|
+
private attr_reader :sets, :points, :server, :receiver
|
16
|
+
|
17
|
+
def initialize(sets:, points:, server:)
|
18
|
+
@sets, @points, @server = sets, points, server
|
19
|
+
@receiver = Player.opponent(server)
|
20
|
+
end
|
21
|
+
|
22
|
+
def break_point?
|
23
|
+
next_point_wins_game?(receiver)
|
24
|
+
end
|
25
|
+
|
26
|
+
def set_point?
|
27
|
+
next_point_wins_set?(server) || next_point_wins_set?(receiver)
|
28
|
+
end
|
29
|
+
|
30
|
+
def match_point?
|
31
|
+
next_point_wins_match?(server) || next_point_wins_match?(receiver)
|
32
|
+
end
|
33
|
+
|
34
|
+
def tiebreak?
|
35
|
+
sets.last == Tennis::Checker::TIEBREAK_AT
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def points_for(player)
|
41
|
+
points[Player.index(player)]
|
42
|
+
end
|
43
|
+
|
44
|
+
def games_for(player, set)
|
45
|
+
set[Player.index(player)]
|
46
|
+
end
|
47
|
+
|
48
|
+
def next_point_wins_game?(player)
|
49
|
+
will_have_required_points?(player) && will_have_point_margin?(player)
|
50
|
+
end
|
51
|
+
|
52
|
+
def next_point_wins_set?(player)
|
53
|
+
next_point_wins_game?(player) && next_game_wins_set?(player)
|
54
|
+
end
|
55
|
+
|
56
|
+
def next_game_wins_set?(player)
|
57
|
+
will_have_required_games?(sets.last, player) && will_have_game_margin?(sets.last, player)
|
58
|
+
end
|
59
|
+
|
60
|
+
def next_point_wins_match?(player)
|
61
|
+
next_point_wins_set?(player) && next_set_wins_match?(player)
|
62
|
+
end
|
63
|
+
|
64
|
+
def next_set_wins_match?(player)
|
65
|
+
sets_won(player) + 1 == SETS_TO_WIN_MATCH
|
66
|
+
end
|
67
|
+
|
68
|
+
def will_have_required_points?(player)
|
69
|
+
points_for(player) + 1 >= (tiebreak? ? MINIMUM_POINTS_TO_WIN_TIEBREAK : MINIMUM_POINTS_TO_WIN_GAME)
|
70
|
+
end
|
71
|
+
|
72
|
+
def will_have_required_games?(set, player)
|
73
|
+
games_for(player, set) + 1 >= MINIMUM_GAMES_TO_WIN_SET
|
74
|
+
end
|
75
|
+
|
76
|
+
def will_have_point_margin?(player)
|
77
|
+
points_for(player) - points_for(Player.opponent(player)) >= 1
|
78
|
+
end
|
79
|
+
|
80
|
+
def will_have_game_margin?(set, player)
|
81
|
+
games_for(player, set) - games_for(Player.opponent(player), set) >= 1
|
82
|
+
end
|
83
|
+
|
84
|
+
def sets_won(player)
|
85
|
+
sets.count { |set| won_set?(set, player) }
|
86
|
+
end
|
87
|
+
|
88
|
+
def won_set?(set, player)
|
89
|
+
enough_games_won?(player, set) && more_games_than_opponent?(player, set)
|
90
|
+
end
|
91
|
+
|
92
|
+
def enough_games_won?(player, set)
|
93
|
+
games_for(player, set) >= MINIMUM_GAMES_TO_WIN_SET
|
94
|
+
end
|
95
|
+
|
96
|
+
def more_games_than_opponent?(player, set)
|
97
|
+
games_for(player, set) > games_for(Player.opponent(player), set)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Bagel::Tennis
|
2
|
+
class Outcome
|
3
|
+
SERVE = 'S'
|
4
|
+
FOREHAND = 'FH'
|
5
|
+
BACKHAND = 'BH'
|
6
|
+
|
7
|
+
WINNER = 'W'
|
8
|
+
FORCED_ERROR = 'FE'
|
9
|
+
UNFORCED_ERROR = 'UE'
|
10
|
+
|
11
|
+
private attr_reader :winner, :reason, :shot, :net
|
12
|
+
|
13
|
+
def initialize(winner:, reason:, shot:, net: nil)
|
14
|
+
@winner = winner
|
15
|
+
@reason = reason
|
16
|
+
@shot = shot
|
17
|
+
@net = net
|
18
|
+
end
|
19
|
+
|
20
|
+
def ace?
|
21
|
+
winner? && shot == SERVE
|
22
|
+
end
|
23
|
+
|
24
|
+
def winner?
|
25
|
+
reason == WINNER
|
26
|
+
end
|
27
|
+
|
28
|
+
def unforced_error?
|
29
|
+
reason == UNFORCED_ERROR
|
30
|
+
end
|
31
|
+
|
32
|
+
def p1_won?
|
33
|
+
winner == Player.ONE
|
34
|
+
end
|
35
|
+
|
36
|
+
def p2_won?
|
37
|
+
winner == Player.TWO
|
38
|
+
end
|
39
|
+
|
40
|
+
def p1_net_point?
|
41
|
+
net && !net[Player.index(Player.ONE)].nil?
|
42
|
+
end
|
43
|
+
|
44
|
+
def p2_net_point?
|
45
|
+
net && !net[Player.index(Player.TWO)].nil?
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Bagel::Tennis
|
2
|
+
class Player
|
3
|
+
PLAYER_ONE = 1
|
4
|
+
PLAYER_TWO = 2
|
5
|
+
|
6
|
+
def self.ONE
|
7
|
+
PLAYER_ONE
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.TWO
|
11
|
+
PLAYER_TWO
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.index(id)
|
15
|
+
id - 1
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.opponent(id)
|
19
|
+
id == PLAYER_ONE ? PLAYER_TWO : PLAYER_ONE
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Bagel::Tennis
|
2
|
+
class Point
|
3
|
+
extend Forwardable
|
4
|
+
|
5
|
+
def_delegators :@timeframe, :start, :finish
|
6
|
+
def_delegators :@score, :break_point?, :set_point?, :match_point?
|
7
|
+
def_delegators :@serve, :server?, :no_faults?, :second_serve?, :double_fault?, :p1_serve?, :p2_serve?
|
8
|
+
def_delegators :@outcome, :ace?, :p1_won?, :p2_won?, :winner?, :unforced_error?, :p1_net_point?, :p2_net_point?
|
9
|
+
|
10
|
+
def initialize(timeframe:, score:, serve:, outcome:)
|
11
|
+
@timeframe = timeframe
|
12
|
+
@score = score
|
13
|
+
@serve = serve
|
14
|
+
@outcome = outcome
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Bagel::Tennis
|
2
|
+
class Score
|
3
|
+
attr_reader :sets, :points
|
4
|
+
private attr_reader :server, :checker
|
5
|
+
|
6
|
+
def initialize(sets:, points: nil, server: nil)
|
7
|
+
@sets, @points = sets, points
|
8
|
+
@server = server
|
9
|
+
@checker = Tennis::Checker.new(sets: sets, points: points, server: server)
|
10
|
+
end
|
11
|
+
|
12
|
+
def id
|
13
|
+
id_parts.map { |i| i.to_s.rjust(2, '0') }.join('-')
|
14
|
+
end
|
15
|
+
|
16
|
+
def context
|
17
|
+
return nil if server.nil?
|
18
|
+
return Tennis::Checker::MATCH_POINT if match_point?
|
19
|
+
return Tennis::Checker::SET_POINT if set_point?
|
20
|
+
return Tennis::Checker::BREAK_POINT if break_point?
|
21
|
+
end
|
22
|
+
|
23
|
+
def server?(player_id)
|
24
|
+
server == player_id
|
25
|
+
end
|
26
|
+
|
27
|
+
def set_number
|
28
|
+
sets.count
|
29
|
+
end
|
30
|
+
|
31
|
+
def set_score
|
32
|
+
sets.last
|
33
|
+
end
|
34
|
+
|
35
|
+
def break_point?
|
36
|
+
checker.break_point?
|
37
|
+
end
|
38
|
+
|
39
|
+
def set_point?
|
40
|
+
checker.set_point?
|
41
|
+
end
|
42
|
+
|
43
|
+
def match_point?
|
44
|
+
checker.match_point?
|
45
|
+
end
|
46
|
+
|
47
|
+
def tiebreak?
|
48
|
+
checker.tiebreak?
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def id_parts
|
54
|
+
parts = [ set_number ]
|
55
|
+
parts << game_number if points
|
56
|
+
parts << point_number if points
|
57
|
+
parts
|
58
|
+
end
|
59
|
+
|
60
|
+
def game_number
|
61
|
+
sets.last.sum + 1
|
62
|
+
end
|
63
|
+
|
64
|
+
def point_number
|
65
|
+
points.sum + 1
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Bagel::Tennis
|
2
|
+
module Scoring
|
3
|
+
LOVE = '0'
|
4
|
+
FIFTEEN = '15'
|
5
|
+
THIRTY = '30'
|
6
|
+
FORTY = '40'
|
7
|
+
FORTY_BLANK = ''
|
8
|
+
ADVANTAGE = 'A'
|
9
|
+
|
10
|
+
def self.point_call(player, opponent, tiebreak=false)
|
11
|
+
return player.to_s if tiebreak
|
12
|
+
|
13
|
+
return LOVE if player == 0
|
14
|
+
return FIFTEEN if player == 1
|
15
|
+
return THIRTY if player == 2
|
16
|
+
|
17
|
+
if opponent < 3 || player == opponent
|
18
|
+
FORTY
|
19
|
+
elsif player < opponent
|
20
|
+
FORTY_BLANK
|
21
|
+
else
|
22
|
+
ADVANTAGE
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Bagel::Tennis
|
2
|
+
class Serve
|
3
|
+
NO_FAULT = 0
|
4
|
+
DOUBLE_FAULT = 2
|
5
|
+
|
6
|
+
private attr_reader :server, :faults
|
7
|
+
|
8
|
+
def initialize(server:, faults:)
|
9
|
+
@server, @faults = server, faults
|
10
|
+
end
|
11
|
+
|
12
|
+
def server?(player)
|
13
|
+
server == player
|
14
|
+
end
|
15
|
+
|
16
|
+
def no_faults?
|
17
|
+
faults == NO_FAULT
|
18
|
+
end
|
19
|
+
|
20
|
+
def second_serve?
|
21
|
+
faults > NO_FAULT
|
22
|
+
end
|
23
|
+
|
24
|
+
def double_fault?
|
25
|
+
faults == DOUBLE_FAULT
|
26
|
+
end
|
27
|
+
|
28
|
+
def p1_serve?
|
29
|
+
server?(Player.ONE)
|
30
|
+
end
|
31
|
+
|
32
|
+
def p2_serve?
|
33
|
+
server?(Player.TWO)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Bagel::Tennis::Stats
|
2
|
+
class Aces < Stat
|
3
|
+
private attr_reader :p1, :p2
|
4
|
+
|
5
|
+
def name
|
6
|
+
'ACES'
|
7
|
+
end
|
8
|
+
|
9
|
+
def values
|
10
|
+
[ p1, p2 ]
|
11
|
+
end
|
12
|
+
|
13
|
+
def superior
|
14
|
+
[ p1 > p2, p2 > p1 ]
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def calculate
|
20
|
+
@p1 = aces(Player.ONE)
|
21
|
+
@p2 = aces(Player.TWO)
|
22
|
+
end
|
23
|
+
|
24
|
+
def aces(player)
|
25
|
+
points.count { |point| point.server?(player) && point.ace? }
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Bagel::Tennis::Stats
|
2
|
+
class BreakPointsWon < Stat
|
3
|
+
private attr_reader :p1_label, :p2_label, :p1_percentage, :p2_percentage
|
4
|
+
|
5
|
+
def name
|
6
|
+
'BREAK POINTS WON'
|
7
|
+
end
|
8
|
+
|
9
|
+
def values
|
10
|
+
[ p1_label, p2_label ]
|
11
|
+
end
|
12
|
+
|
13
|
+
def superior
|
14
|
+
[ p1_percentage > p2_percentage, p2_percentage > p1_percentage ]
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def calculate
|
20
|
+
p1_break_points = points.select(&:break_point?).select(&:p2_serve?)
|
21
|
+
p2_break_points = points.select(&:break_point?).select(&:p1_serve?)
|
22
|
+
|
23
|
+
p1_break_points_won = p1_break_points.select(&:p1_won?)
|
24
|
+
p2_break_points_won = p2_break_points.select(&:p2_won?)
|
25
|
+
|
26
|
+
@p1_percentage = percentage(p1_break_points.count, p1_break_points_won.count)
|
27
|
+
@p2_percentage = percentage(p2_break_points.count, p2_break_points_won.count)
|
28
|
+
|
29
|
+
@p1_label = label(p1_break_points.count, p1_break_points_won.count, p1_percentage)
|
30
|
+
@p2_label = label(p2_break_points.count, p2_break_points_won.count, p2_percentage)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Bagel::Tennis::Stats
|
2
|
+
class DoubleFaults < Stat
|
3
|
+
private attr_reader :p1, :p2
|
4
|
+
|
5
|
+
def name
|
6
|
+
'DOUBLE FAULTS'
|
7
|
+
end
|
8
|
+
|
9
|
+
def values
|
10
|
+
[ p1, p2 ]
|
11
|
+
end
|
12
|
+
|
13
|
+
def superior
|
14
|
+
[ p1 < p2, p2 < p1 ]
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def calculate
|
20
|
+
@p1 = double_faults(Player.ONE)
|
21
|
+
@p2 = double_faults(Player.TWO)
|
22
|
+
end
|
23
|
+
|
24
|
+
def double_faults(player)
|
25
|
+
points.count { |point| point.server?(player) && point.double_fault? }
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|