tundengine 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.
- checksums.yaml +7 -0
- data/Gemfile +4 -0
- data/LICENSE +21 -0
- data/README.md +29 -0
- data/Rakefile +8 -0
- data/lib/tundengine.rb +67 -0
- data/lib/tundengine/algebraic_data_type.rb +33 -0
- data/lib/tundengine/card_percolator.rb +9 -0
- data/lib/tundengine/cards/card.rb +63 -0
- data/lib/tundengine/cards/null.rb +18 -0
- data/lib/tundengine/dealer.rb +52 -0
- data/lib/tundengine/deck.rb +41 -0
- data/lib/tundengine/declarations/base.rb +24 -0
- data/lib/tundengine/declarations/las_cuarenta.rb +15 -0
- data/lib/tundengine/declarations/las_veinte.rb +29 -0
- data/lib/tundengine/declarations/null.rb +18 -0
- data/lib/tundengine/declarations/tute.rb +23 -0
- data/lib/tundengine/declarations/void.rb +15 -0
- data/lib/tundengine/hand.rb +35 -0
- data/lib/tundengine/move.rb +13 -0
- data/lib/tundengine/null_move.rb +13 -0
- data/lib/tundengine/player/in_match.rb +29 -0
- data/lib/tundengine/player/in_round.rb +71 -0
- data/lib/tundengine/player/in_turn.rb +31 -0
- data/lib/tundengine/ranks/base.rb +31 -0
- data/lib/tundengine/ranks/cinco.rb +10 -0
- data/lib/tundengine/ranks/cuatro.rb +10 -0
- data/lib/tundengine/ranks/diez.rb +10 -0
- data/lib/tundengine/ranks/doce.rb +10 -0
- data/lib/tundengine/ranks/dos.rb +10 -0
- data/lib/tundengine/ranks/null.rb +10 -0
- data/lib/tundengine/ranks/once.rb +10 -0
- data/lib/tundengine/ranks/seis.rb +10 -0
- data/lib/tundengine/ranks/siete.rb +10 -0
- data/lib/tundengine/ranks/tres.rb +10 -0
- data/lib/tundengine/ranks/uno.rb +10 -0
- data/lib/tundengine/ranks/white.rb +9 -0
- data/lib/tundengine/round_analyzer.rb +67 -0
- data/lib/tundengine/stages/base.rb +74 -0
- data/lib/tundengine/stages/match.rb +80 -0
- data/lib/tundengine/stages/null.rb +13 -0
- data/lib/tundengine/stages/round.rb +85 -0
- data/lib/tundengine/stages/tournament.rb +72 -0
- data/lib/tundengine/stages/trick.rb +90 -0
- data/lib/tundengine/stages/turn.rb +44 -0
- data/lib/tundengine/strategies/automatic.rb +27 -0
- data/lib/tundengine/strategies/base.rb +8 -0
- data/lib/tundengine/strategies/manual.rb +33 -0
- data/lib/tundengine/strategies/random.rb +15 -0
- data/lib/tundengine/stringifiable_by_class.rb +9 -0
- data/lib/tundengine/suits/base.rb +19 -0
- data/lib/tundengine/suits/basto.rb +7 -0
- data/lib/tundengine/suits/copa.rb +7 -0
- data/lib/tundengine/suits/espada.rb +7 -0
- data/lib/tundengine/suits/null.rb +14 -0
- data/lib/tundengine/suits/oro.rb +7 -0
- data/lib/tundengine/tute_values/base.rb +17 -0
- data/lib/tundengine/tute_values/capotes.rb +22 -0
- data/lib/tundengine/tute_values/nothing.rb +13 -0
- data/lib/tundengine/tute_values/victory.rb +17 -0
- data/lib/tundengine/version.rb +17 -0
- data/test/test_card_strength.rb +58 -0
- data/test/test_helper.rb +35 -0
- data/test/test_playable_cards.rb +66 -0
- data/test/test_random_tournaments.rb +31 -0
- data/tundengine.gemspec +28 -0
- metadata +154 -0
@@ -0,0 +1,74 @@
|
|
1
|
+
module Tundengine
|
2
|
+
module Stages
|
3
|
+
class Base
|
4
|
+
|
5
|
+
attr_reader :parent, :children, :child_in_play
|
6
|
+
|
7
|
+
def initialize(parent = Null.instance)
|
8
|
+
@parent = parent
|
9
|
+
@children = []
|
10
|
+
@rewinded = []
|
11
|
+
new_child_in_play!
|
12
|
+
@locked = false
|
13
|
+
end
|
14
|
+
|
15
|
+
def declare!(declaration = Declarations::Null.instance)
|
16
|
+
child_in_play.declare!(declaration)
|
17
|
+
end
|
18
|
+
|
19
|
+
def play!(card = Cards::Null.instance)
|
20
|
+
completed = completed?
|
21
|
+
until completed or @locked
|
22
|
+
child_in_play.play!(card)
|
23
|
+
card = Cards::Null.instance
|
24
|
+
completed = completed?
|
25
|
+
completed ? on_completed! : new_child_in_play!
|
26
|
+
end
|
27
|
+
@locked = false
|
28
|
+
block_given? ? (yield self) : self
|
29
|
+
end
|
30
|
+
|
31
|
+
def on_complete_child!
|
32
|
+
children << child_in_play
|
33
|
+
end
|
34
|
+
|
35
|
+
def lock!
|
36
|
+
@locked = true
|
37
|
+
parent.lock!
|
38
|
+
end
|
39
|
+
|
40
|
+
def rewind!(n)
|
41
|
+
[n, children.length].min.times { @rewinded.unshift(children.pop) }
|
42
|
+
end
|
43
|
+
|
44
|
+
def fast_forward!(n)
|
45
|
+
[n, @rewinded.length].min.times { children.push(@rewinded.shift) }
|
46
|
+
end
|
47
|
+
|
48
|
+
def as_hash
|
49
|
+
summary.merge(
|
50
|
+
children_key_name => children.map(&:as_hash)
|
51
|
+
)
|
52
|
+
end
|
53
|
+
|
54
|
+
protected
|
55
|
+
|
56
|
+
def new_child_in_play!
|
57
|
+
@child_in_play = new_child
|
58
|
+
end
|
59
|
+
|
60
|
+
def summary
|
61
|
+
{}
|
62
|
+
end
|
63
|
+
|
64
|
+
def children_key_name
|
65
|
+
"#{child_class_name}s".to_sym
|
66
|
+
end
|
67
|
+
|
68
|
+
def child_class_name
|
69
|
+
children.first.class.name.split('::').last.downcase
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module Tundengine
|
2
|
+
module Stages
|
3
|
+
class Match < Base
|
4
|
+
|
5
|
+
SETTINGS_KEYS = %i(max_points tute_value losing_position)
|
6
|
+
|
7
|
+
attr_reader :settings
|
8
|
+
|
9
|
+
def initialize(tournament, players, settings)
|
10
|
+
@players = players
|
11
|
+
@settings = settings
|
12
|
+
@trumps = Deck::SUITS.dup
|
13
|
+
@result = @players.each_with_object({}) { |k, h| h[k] = 0 }
|
14
|
+
super(tournament)
|
15
|
+
end
|
16
|
+
|
17
|
+
alias_method :tournament, :parent
|
18
|
+
alias_method :rounds, :children
|
19
|
+
alias_method :current_round, :child_in_play
|
20
|
+
|
21
|
+
SETTINGS_KEYS.each do |key|
|
22
|
+
define_method key do
|
23
|
+
settings.fetch key
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def on_complete_child!(result)
|
28
|
+
update_result!(result)
|
29
|
+
rotate_trumps_and_players!
|
30
|
+
super()
|
31
|
+
end
|
32
|
+
|
33
|
+
def on_completed!
|
34
|
+
tournament.on_complete_child!(@result)
|
35
|
+
end
|
36
|
+
|
37
|
+
def summary
|
38
|
+
{
|
39
|
+
settings: settings.each_with_object({}) { |(k,v),h| h[k.to_s] = v.to_s },
|
40
|
+
players: @players.map(&:name),
|
41
|
+
result: @result.each_with_object({}) { |(k,v),h| h[k.to_s] = v }
|
42
|
+
}
|
43
|
+
end
|
44
|
+
|
45
|
+
protected
|
46
|
+
|
47
|
+
def new_child
|
48
|
+
Round.new(self, players_for_new_round, trump_for_new_round)
|
49
|
+
end
|
50
|
+
|
51
|
+
def players_for_new_round
|
52
|
+
@players.map(&:in_new_round)
|
53
|
+
end
|
54
|
+
|
55
|
+
def trump_for_new_round
|
56
|
+
@trumps.first
|
57
|
+
end
|
58
|
+
|
59
|
+
def update_result!(round_result)
|
60
|
+
# these are all match scores, not round points
|
61
|
+
@result.merge!(round_result) do |_, acum_score, last_round_score|
|
62
|
+
acum_score + last_round_score
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def rotate_trumps_and_players!
|
67
|
+
[@trumps, @players].each { |a| a.rotate! 1 }
|
68
|
+
end
|
69
|
+
|
70
|
+
def completed?
|
71
|
+
not losers.empty?
|
72
|
+
end
|
73
|
+
|
74
|
+
def losers
|
75
|
+
@result.select { |_, points| points >= max_points }
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module Tundengine
|
2
|
+
module Stages
|
3
|
+
class Round < Base
|
4
|
+
|
5
|
+
extend Forwardable
|
6
|
+
def_delegator :@parent, :max_points, :max_match_points
|
7
|
+
def_delegators :@parent, :tute_value, :losing_position
|
8
|
+
|
9
|
+
attr_reader :players, :trump_suit, :last_declaration
|
10
|
+
|
11
|
+
LAST_TRICK_BONUS_POINTS = 10
|
12
|
+
|
13
|
+
def initialize(match, players_in_round, trump_suit)
|
14
|
+
@players = players_in_round
|
15
|
+
@players.each { |p| p.round = self }
|
16
|
+
@trump_suit = trump_suit.freeze
|
17
|
+
@last_declaration = Declarations::Null.instance
|
18
|
+
super(match)
|
19
|
+
Dealer.instance.deal!(players, trump_suit)
|
20
|
+
end
|
21
|
+
|
22
|
+
alias_method :match, :parent
|
23
|
+
alias_method :tricks, :children
|
24
|
+
alias_method :current_trick, :child_in_play
|
25
|
+
|
26
|
+
def declare!(declaration = Declarations::Null.instance)
|
27
|
+
last_winner_player.declare!(declaration)
|
28
|
+
end
|
29
|
+
|
30
|
+
def on_complete_child! # when the current trick is completed
|
31
|
+
super
|
32
|
+
rotate_players!
|
33
|
+
end
|
34
|
+
|
35
|
+
def on_completed!
|
36
|
+
last_winner_player.bonus_points = LAST_TRICK_BONUS_POINTS
|
37
|
+
parent.on_complete_child!(result)
|
38
|
+
end
|
39
|
+
|
40
|
+
def after_declaring!(declaration)
|
41
|
+
current_trick.after_declaring!(declaration)
|
42
|
+
@last_declaration = declaration
|
43
|
+
end
|
44
|
+
|
45
|
+
def summary
|
46
|
+
{
|
47
|
+
trump_suit: trump_suit.to_s,
|
48
|
+
players: players.map(&:summary)
|
49
|
+
}
|
50
|
+
end
|
51
|
+
|
52
|
+
protected
|
53
|
+
|
54
|
+
def new_child
|
55
|
+
Trick.new(self)
|
56
|
+
end
|
57
|
+
|
58
|
+
def rotate_players!
|
59
|
+
players.rotate!(players.index next_first_player)
|
60
|
+
end
|
61
|
+
|
62
|
+
def last_winner_player
|
63
|
+
last_trick.winner_player
|
64
|
+
end
|
65
|
+
alias_method :next_first_player, :last_winner_player
|
66
|
+
|
67
|
+
def last_trick
|
68
|
+
tricks.last || :no_trick
|
69
|
+
end
|
70
|
+
|
71
|
+
def completed?
|
72
|
+
any_player.has_empty_hand? or @last_declaration.finishes_round?(tute_value)
|
73
|
+
end
|
74
|
+
|
75
|
+
def any_player
|
76
|
+
players.first
|
77
|
+
end
|
78
|
+
|
79
|
+
def result
|
80
|
+
RoundAnalyzer.new(self).result
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module Tundengine
|
2
|
+
module Stages
|
3
|
+
class Tournament < Base
|
4
|
+
|
5
|
+
SETTINGS_KEYS = %i(max_matches match_settings)
|
6
|
+
# TODO: Add a setting to run matches on multiple threads.
|
7
|
+
|
8
|
+
attr_reader :players, :settings
|
9
|
+
|
10
|
+
def initialize(players, settings)
|
11
|
+
@players = players
|
12
|
+
@settings = settings
|
13
|
+
@result = initial_result
|
14
|
+
super()
|
15
|
+
end
|
16
|
+
|
17
|
+
alias_method :matches, :children
|
18
|
+
alias_method :current_match, :child_in_play
|
19
|
+
|
20
|
+
SETTINGS_KEYS.each do |key|
|
21
|
+
define_method key do
|
22
|
+
settings.fetch key
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def on_complete_child!(result)
|
27
|
+
update_result!(result)
|
28
|
+
super()
|
29
|
+
end
|
30
|
+
|
31
|
+
def summary
|
32
|
+
{
|
33
|
+
players: players.map(&:name),
|
34
|
+
result: @result.each_with_object({}) { |(k,v),h| h[k.to_s] = v }
|
35
|
+
}
|
36
|
+
end
|
37
|
+
|
38
|
+
protected
|
39
|
+
|
40
|
+
def initial_result
|
41
|
+
@players.each_with_object({}) { |p, h| h[p] = 0 }
|
42
|
+
end
|
43
|
+
|
44
|
+
def new_child
|
45
|
+
Match.new(self, @players, match_settings)
|
46
|
+
end
|
47
|
+
|
48
|
+
# TODO: Implement different ways to accumulate match results.
|
49
|
+
def update_result!(match_result)
|
50
|
+
@result.merge! match_result do |_, tournament_score, match_score|
|
51
|
+
tournament_score + match_score
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# TODO: Implement different criteria for finishing tournaments.
|
56
|
+
def completed?
|
57
|
+
matches.length == max_matches
|
58
|
+
end
|
59
|
+
|
60
|
+
def on_completed!
|
61
|
+
# for debugging purposes
|
62
|
+
# puts 'Tournament completed. As hash:'
|
63
|
+
# p as_hash
|
64
|
+
end
|
65
|
+
|
66
|
+
def children_key_name
|
67
|
+
:matches
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module Tundengine
|
2
|
+
module Stages
|
3
|
+
class Trick < Base
|
4
|
+
|
5
|
+
extend Forwardable
|
6
|
+
def_delegator :@winner_turn, :player_in_round, :winner_player
|
7
|
+
def_delegator :@winner_turn, :card, :winner_card
|
8
|
+
def_delegators :@parent, :players, :trump_suit
|
9
|
+
|
10
|
+
include CardPercolator
|
11
|
+
|
12
|
+
attr_reader :declaration
|
13
|
+
|
14
|
+
def initialize(round)
|
15
|
+
@next_player_index = 0
|
16
|
+
super
|
17
|
+
@winner_turn = current_turn
|
18
|
+
@declaration = :no_declaration
|
19
|
+
end
|
20
|
+
|
21
|
+
alias_method :round, :parent
|
22
|
+
alias_method :turns, :children
|
23
|
+
alias_method :current_turn, :child_in_play
|
24
|
+
|
25
|
+
def on_complete_child!(beats) # when the current turn is completed
|
26
|
+
@winner_turn = current_turn if beats
|
27
|
+
super()
|
28
|
+
end
|
29
|
+
|
30
|
+
def after_declaring!(declaration)
|
31
|
+
@declaration = declaration
|
32
|
+
on_completed_with_declaration!
|
33
|
+
end
|
34
|
+
|
35
|
+
def first_suit
|
36
|
+
cards.fetch(0, Cards::Null.instance).suit
|
37
|
+
end
|
38
|
+
|
39
|
+
def points
|
40
|
+
cards.reduce(0) { |acum, card| acum + card.round_points }
|
41
|
+
end
|
42
|
+
|
43
|
+
def cards
|
44
|
+
turns.map(&:card)
|
45
|
+
end
|
46
|
+
|
47
|
+
def next_player # in round
|
48
|
+
players.fetch(@next_player_index - 1)
|
49
|
+
end
|
50
|
+
|
51
|
+
def summary
|
52
|
+
{
|
53
|
+
cards: cards.map(&:to_s),
|
54
|
+
winner_player: winner_player.name,
|
55
|
+
winner_card: winner_card.to_s,
|
56
|
+
declaration: declaration.to_s,
|
57
|
+
}
|
58
|
+
end
|
59
|
+
|
60
|
+
protected
|
61
|
+
|
62
|
+
def new_child
|
63
|
+
Turn.new(self)
|
64
|
+
end
|
65
|
+
|
66
|
+
def new_child_in_play! # before starting a new turn
|
67
|
+
@next_player_index += 1
|
68
|
+
super
|
69
|
+
end
|
70
|
+
|
71
|
+
def completed?
|
72
|
+
turns.length == players.length
|
73
|
+
end
|
74
|
+
|
75
|
+
def on_completed!
|
76
|
+
winner_player.on_winning_trick!(self)
|
77
|
+
end
|
78
|
+
|
79
|
+
def on_completed_with_declaration!
|
80
|
+
round.on_complete_child!
|
81
|
+
end
|
82
|
+
|
83
|
+
def is_taken_by?(card)
|
84
|
+
card.beats? winner_card, trump_suit
|
85
|
+
end
|
86
|
+
alias_method :passes_card_percolator?, :is_taken_by?
|
87
|
+
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Tundengine
|
2
|
+
module Stages
|
3
|
+
class Turn < Base
|
4
|
+
|
5
|
+
attr_reader :card
|
6
|
+
|
7
|
+
def initialize(trick)
|
8
|
+
@card = Cards::Null.instance
|
9
|
+
super
|
10
|
+
end
|
11
|
+
|
12
|
+
alias_method :trick, :parent
|
13
|
+
alias_method :player, :child_in_play
|
14
|
+
|
15
|
+
def player_in_round
|
16
|
+
player.in_round
|
17
|
+
end
|
18
|
+
|
19
|
+
def play!(card = Cards::Null.instance)
|
20
|
+
player.play!(card)
|
21
|
+
end
|
22
|
+
|
23
|
+
def on_completed!(card, beats)
|
24
|
+
@card = card
|
25
|
+
trick.on_complete_child!(beats)
|
26
|
+
end
|
27
|
+
|
28
|
+
def as_hash
|
29
|
+
{ player: player.name, card: card.to_s }
|
30
|
+
end
|
31
|
+
|
32
|
+
protected
|
33
|
+
|
34
|
+
def new_child
|
35
|
+
Player::InTurn.new(self, trick.next_player)
|
36
|
+
end
|
37
|
+
|
38
|
+
def completed?
|
39
|
+
card.exists?
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|