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.
Files changed (67) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +4 -0
  3. data/LICENSE +21 -0
  4. data/README.md +29 -0
  5. data/Rakefile +8 -0
  6. data/lib/tundengine.rb +67 -0
  7. data/lib/tundengine/algebraic_data_type.rb +33 -0
  8. data/lib/tundengine/card_percolator.rb +9 -0
  9. data/lib/tundengine/cards/card.rb +63 -0
  10. data/lib/tundengine/cards/null.rb +18 -0
  11. data/lib/tundengine/dealer.rb +52 -0
  12. data/lib/tundengine/deck.rb +41 -0
  13. data/lib/tundengine/declarations/base.rb +24 -0
  14. data/lib/tundengine/declarations/las_cuarenta.rb +15 -0
  15. data/lib/tundengine/declarations/las_veinte.rb +29 -0
  16. data/lib/tundengine/declarations/null.rb +18 -0
  17. data/lib/tundengine/declarations/tute.rb +23 -0
  18. data/lib/tundengine/declarations/void.rb +15 -0
  19. data/lib/tundengine/hand.rb +35 -0
  20. data/lib/tundengine/move.rb +13 -0
  21. data/lib/tundengine/null_move.rb +13 -0
  22. data/lib/tundengine/player/in_match.rb +29 -0
  23. data/lib/tundengine/player/in_round.rb +71 -0
  24. data/lib/tundengine/player/in_turn.rb +31 -0
  25. data/lib/tundengine/ranks/base.rb +31 -0
  26. data/lib/tundengine/ranks/cinco.rb +10 -0
  27. data/lib/tundengine/ranks/cuatro.rb +10 -0
  28. data/lib/tundengine/ranks/diez.rb +10 -0
  29. data/lib/tundengine/ranks/doce.rb +10 -0
  30. data/lib/tundengine/ranks/dos.rb +10 -0
  31. data/lib/tundengine/ranks/null.rb +10 -0
  32. data/lib/tundengine/ranks/once.rb +10 -0
  33. data/lib/tundengine/ranks/seis.rb +10 -0
  34. data/lib/tundengine/ranks/siete.rb +10 -0
  35. data/lib/tundengine/ranks/tres.rb +10 -0
  36. data/lib/tundengine/ranks/uno.rb +10 -0
  37. data/lib/tundengine/ranks/white.rb +9 -0
  38. data/lib/tundengine/round_analyzer.rb +67 -0
  39. data/lib/tundengine/stages/base.rb +74 -0
  40. data/lib/tundengine/stages/match.rb +80 -0
  41. data/lib/tundengine/stages/null.rb +13 -0
  42. data/lib/tundengine/stages/round.rb +85 -0
  43. data/lib/tundengine/stages/tournament.rb +72 -0
  44. data/lib/tundengine/stages/trick.rb +90 -0
  45. data/lib/tundengine/stages/turn.rb +44 -0
  46. data/lib/tundengine/strategies/automatic.rb +27 -0
  47. data/lib/tundengine/strategies/base.rb +8 -0
  48. data/lib/tundengine/strategies/manual.rb +33 -0
  49. data/lib/tundengine/strategies/random.rb +15 -0
  50. data/lib/tundengine/stringifiable_by_class.rb +9 -0
  51. data/lib/tundengine/suits/base.rb +19 -0
  52. data/lib/tundengine/suits/basto.rb +7 -0
  53. data/lib/tundengine/suits/copa.rb +7 -0
  54. data/lib/tundengine/suits/espada.rb +7 -0
  55. data/lib/tundengine/suits/null.rb +14 -0
  56. data/lib/tundengine/suits/oro.rb +7 -0
  57. data/lib/tundengine/tute_values/base.rb +17 -0
  58. data/lib/tundengine/tute_values/capotes.rb +22 -0
  59. data/lib/tundengine/tute_values/nothing.rb +13 -0
  60. data/lib/tundengine/tute_values/victory.rb +17 -0
  61. data/lib/tundengine/version.rb +17 -0
  62. data/test/test_card_strength.rb +58 -0
  63. data/test/test_helper.rb +35 -0
  64. data/test/test_playable_cards.rb +66 -0
  65. data/test/test_random_tournaments.rb +31 -0
  66. data/tundengine.gemspec +28 -0
  67. 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,13 @@
1
+ module Tundengine
2
+ module Stages
3
+ class Null
4
+
5
+ include Singleton
6
+
7
+ def lock!
8
+ # do nothing
9
+ end
10
+
11
+ end
12
+ end
13
+ 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