love_letter_application 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.
Files changed (83) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +11 -0
  3. data/.rspec +3 -0
  4. data/.travis.yml +7 -0
  5. data/Gemfile +5 -0
  6. data/Gemfile.lock +91 -0
  7. data/LICENSE.txt +21 -0
  8. data/README.md +39 -0
  9. data/Rakefile +6 -0
  10. data/bin/console +14 -0
  11. data/bin/setup +8 -0
  12. data/lib/love_letter_application.rb +7 -0
  13. data/lib/love_letter_application/action_creator.rb +16 -0
  14. data/lib/love_letter_application/actions/clown.rb +45 -0
  15. data/lib/love_letter_application/actions/general.rb +29 -0
  16. data/lib/love_letter_application/actions/knight.rb +31 -0
  17. data/lib/love_letter_application/actions/marquess.rb +28 -0
  18. data/lib/love_letter_application/actions/play_card.rb +33 -0
  19. data/lib/love_letter_application/actions/priestess.rb +36 -0
  20. data/lib/love_letter_application/actions/princess.rb +32 -0
  21. data/lib/love_letter_application/actions/soldier.rb +47 -0
  22. data/lib/love_letter_application/actions/wizard.rb +31 -0
  23. data/lib/love_letter_application/execute_action.rb +21 -0
  24. data/lib/love_letter_application/love_letter_imports.rb +641 -0
  25. data/lib/love_letter_application/models/card.rb +23 -0
  26. data/lib/love_letter_application/models/effects/discard_and_draw.rb +51 -0
  27. data/lib/love_letter_application/models/effects/eliminate_player.rb +43 -0
  28. data/lib/love_letter_application/models/effects/make_player_not_targetable.rb +44 -0
  29. data/lib/love_letter_application/models/effects/next_player_draw_card.rb +45 -0
  30. data/lib/love_letter_application/models/effects/play_card.rb +48 -0
  31. data/lib/love_letter_application/models/effects/round_complete.rb +29 -0
  32. data/lib/love_letter_application/models/effects/switch_hands.rb +50 -0
  33. data/lib/love_letter_application/models/game_board.rb +28 -0
  34. data/lib/love_letter_application/models/player.rb +36 -0
  35. data/lib/love_letter_application/results/eliminate_player.rb +43 -0
  36. data/lib/love_letter_application/results/nodes/card_played_node.rb +15 -0
  37. data/lib/love_letter_application/results/nodes/card_viewed_node.rb +21 -0
  38. data/lib/love_letter_application/results/nodes/discard_card_node.rb +20 -0
  39. data/lib/love_letter_application/results/nodes/drawn_card_node.rb +20 -0
  40. data/lib/love_letter_application/results/nodes/eliminated_player_node.rb +19 -0
  41. data/lib/love_letter_application/results/nodes/hands_switched_node.rb +22 -0
  42. data/lib/love_letter_application/results/nodes/knight_drawn_node.rb +15 -0
  43. data/lib/love_letter_application/results/nodes/knight_victory_node.rb +20 -0
  44. data/lib/love_letter_application/results/nodes/log_node.rb +20 -0
  45. data/lib/love_letter_application/results/nodes/next_player_node.rb +19 -0
  46. data/lib/love_letter_application/results/nodes/play_card_with_no_args_option_node.rb +19 -0
  47. data/lib/love_letter_application/results/nodes/play_card_with_player_id_and_card_id_option_node.rb +21 -0
  48. data/lib/love_letter_application/results/nodes/play_card_with_player_id_option_node.rb +20 -0
  49. data/lib/love_letter_application/results/nodes/player_not_targetable_node.rb +14 -0
  50. data/lib/love_letter_application/results/nodes/player_victory_node.rb +20 -0
  51. data/lib/love_letter_application/results/nodes/players_and_scores_node.rb +44 -0
  52. data/lib/love_letter_application/results/nodes/princess_discarded_node.rb +19 -0
  53. data/lib/love_letter_application/results/nodes/result_game_board_node.rb +19 -0
  54. data/lib/love_letter_application/results/process_correct_guess.rb +22 -0
  55. data/lib/love_letter_application/results/process_draw_after_discard.rb +26 -0
  56. data/lib/love_letter_application/results/process_incorrect_guess.rb +20 -0
  57. data/lib/love_letter_application/results/process_knight_showdown.rb +50 -0
  58. data/lib/love_letter_application/results/process_next_player_turn.rb +57 -0
  59. data/lib/love_letter_application/results/process_next_turn_options.rb +53 -0
  60. data/lib/love_letter_application/results/process_princess_discarded.rb +23 -0
  61. data/lib/love_letter_application/results/process_resolve_wizard.rb +36 -0
  62. data/lib/love_letter_application/results/process_round_complete_by_depleted_deck.rb +50 -0
  63. data/lib/love_letter_application/results/process_round_complete_by_elimination.rb +26 -0
  64. data/lib/love_letter_application/results/process_switch_hands.rb +51 -0
  65. data/lib/love_letter_application/results/yield_to_block.rb +12 -0
  66. data/lib/love_letter_application/types/card.rb +20 -0
  67. data/lib/love_letter_application/types/game_board.rb +59 -0
  68. data/lib/love_letter_application/types/player.rb +25 -0
  69. data/lib/love_letter_application/validator/base.rb +19 -0
  70. data/lib/love_letter_application/validator/builder.rb +50 -0
  71. data/lib/love_letter_application/validator/get_legal_card_ids.rb +23 -0
  72. data/lib/love_letter_application/validator/get_legal_card_ids/marquess.rb +18 -0
  73. data/lib/love_letter_application/validator/play_card.rb +21 -0
  74. data/lib/love_letter_application/validator/play_card/builder.rb +23 -0
  75. data/lib/love_letter_application/validator/play_card/no_options.rb +19 -0
  76. data/lib/love_letter_application/validator/play_card/select_target_player.rb +30 -0
  77. data/lib/love_letter_application/validator/play_card/select_target_player/builder.rb +44 -0
  78. data/lib/love_letter_application/validator/play_card/soldier.rb +39 -0
  79. data/lib/love_letter_application/validator/play_card/soldier/builder.rb +49 -0
  80. data/lib/love_letter_application/validator/validate_card_id.rb +23 -0
  81. data/lib/love_letter_application/version.rb +3 -0
  82. data/love_letter_application.gemspec +43 -0
  83. metadata +278 -0
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dry-initializer'
4
+
5
+ module LoveLetterApplication
6
+ module Results
7
+ class ProcessIncorrectGuess
8
+ extend Dry::Initializer
9
+ option :log_incorrect_guess_node, type: ::Types.Interface(:accept)
10
+ option :process_next_player_turn, type: ::Types::Callable
11
+
12
+ def call(game_board:, change_orders:)
13
+ process_next_player_turn.(
14
+ game_board: game_board,
15
+ change_orders: change_orders.push(log_incorrect_guess_node))
16
+ end
17
+ end
18
+ end
19
+ end
20
+
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dry-initializer'
4
+
5
+ module LoveLetterApplication
6
+ module Results
7
+ class ProcessKnightShowdown
8
+ @@execute = [
9
+ ->(pks, cid, tid, gb, co){pks.send(:do_victory, tid, cid, gb, co)},
10
+ ->(pks, cid, tid, gb, co){pks.send(:do_draw, cid, tid, gb, co)},
11
+ ->(pks, cid, tid, gb, co){pks.send(:do_victory, cid, tid, gb, co)}]
12
+
13
+ include Dry::Initializer.define -> do
14
+ option :knight_drawn_node, type: ::Types.Interface(:accept)
15
+ option :get_knight_victory_node, type: ::Types::Callable
16
+ option :eliminate_player, type: ::Types::Callable
17
+ option :process_next_player_turn, type: ::Types::Callable
18
+ end
19
+
20
+ def call(target_player_id:, game_board:, change_orders:)
21
+ current_player_id = game_board.current_player_id.to_i
22
+ result = get_result(current_player_id, target_player_id, game_board)
23
+ @@execute[result].(self, current_player_id, target_player_id, game_board, change_orders)
24
+ end
25
+
26
+ private
27
+ def get_result(current_player_id, target_player_id, game_board)
28
+ current_player = game_board.players.find{|player| player.id.to_i.eql?(current_player_id)}
29
+ target_player = game_board.players.find{|player| player.id.to_i.eql?(target_player_id)}
30
+ #<=> returns -1, 0, or 1. I add 1 at the end so I can use the result as an array index
31
+ (current_player.hand.first.rank.to_i <=> target_player.hand.first.rank.to_i) + 1
32
+ end
33
+
34
+ def do_draw(current_player_id, target_player_id, game_board, change_orders)
35
+ process_next_player_turn.(
36
+ game_board: game_board,
37
+ change_orders: change_orders.push(knight_drawn_node))
38
+ end
39
+
40
+ def do_victory(winning_player_id, losing_player_id, game_board, change_orders)
41
+ eliminate_player.(
42
+ target_player_id: losing_player_id,
43
+ game_board: game_board,
44
+ change_orders: change_orders.push(get_knight_victory_node.(
45
+ winning_player_id: winning_player_id,
46
+ losing_player_id: losing_player_id)))
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true:q
2
+
3
+ require 'dry-initializer'
4
+
5
+ module LoveLetterApplication
6
+ module Results
7
+ class ProcessNextPlayerTurn
8
+ include Dry::Initializer.define -> do
9
+ option :process_round_complete_by_depleted_deck, type: ::Types::Callable
10
+ option :get_next_player_node, type: ::Types::Callable
11
+ option :get_drawn_card_node, type: ::Types::Callable
12
+ option :next_player_draw_card, type: ::Types::Callable
13
+ option :get_result_game_board_node, type: ::Types::Callable
14
+ option :process_next_turn_options, type: ::Types::Callable
15
+ end
16
+
17
+ def call(game_board:, change_orders:)
18
+ if game_board.draw_pile.empty?
19
+ return process_round_complete_by_depleted_deck.(
20
+ game_board: game_board,
21
+ change_orders: change_orders)
22
+ end
23
+ next_player_id = get_next_player_id_from(game_board)
24
+ card_id = game_board.draw_pile.first.id.to_i
25
+ game_board = next_player_draw_card.(
26
+ game_board: game_board,
27
+ next_player_id: next_player_id)
28
+ process_next_turn_options.(
29
+ game_board: game_board,
30
+ change_orders: change_orders
31
+ .push(get_next_player_node.(player_id: next_player_id))
32
+ .push(get_drawn_card_node.(player_id: next_player_id, card_id: card_id))
33
+ .push(get_result_game_board_node.(game_board: game_board)))
34
+ end
35
+
36
+ private
37
+ def get_next_player_id_from(game_board)
38
+ current_player_id = game_board.current_player_id.to_i
39
+ current_player = game_board.players.find{|player| player.id.to_i.eql?(current_player_id)}
40
+ possible_players = game_board
41
+ .players
42
+ .select(&:active?)
43
+ .sort{|p, q| p.seat.to_i <=> q.seat.to_i}
44
+ .reject{|player| player.id.to_i.eql?(current_player_id)}
45
+ #get first active player with higher seat number than current player
46
+ next_player = possible_players
47
+ .select{|player| player.seat.to_i > current_player_id.to_i}
48
+ .first
49
+ #if no such player, "wrap around" and get active player with lowest seat number
50
+ next_player || possible_players.first
51
+
52
+ next_player.id.to_i
53
+ end
54
+ end
55
+ end
56
+ end
57
+
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dry-initializer'
4
+
5
+ module LoveLetterApplication
6
+ module Results
7
+ class ProcessNextTurnOptions
8
+ extend Dry::Initializer
9
+ option :get_legal_card_ids, type: ::Types::Callable
10
+ option :all_card_validator_builders, type: ::Types::ArrayOfCallable
11
+ option :get_play_card_with_no_options_node, ::Types::Callable
12
+ option :get_play_card_with_player_id_option_node, type: ::Types::Callable
13
+ option :get_play_card_with_player_id_and_card_id_option_node, type: ::Types::Callable
14
+
15
+ def call(game_board:, change_orders:)
16
+ current_player_id = game_board.current_player_id.to_i
17
+ hand = game_board.players
18
+ .find{|player| player.id.to_i.eql?(current_player_id)}
19
+ .hand
20
+ card_ids = get_legal_card_ids.(card_list: hand)
21
+ card_ids.each do |card_id|
22
+ change_orders = change_orders.push(get_node_for(game_board, card_id))
23
+ end
24
+ change_orders
25
+ end
26
+
27
+ def get_node_for(game_board, card_id)
28
+ build_validator = all_card_validator_builders[card_id]
29
+ validator = build_validator.(game_board)
30
+ # a handler will be called by validator#accept based on the type of the validator found
31
+ validator.accept(self, card_id: card_id)
32
+ end
33
+
34
+ def handle_no_options_validator(validator, card_id:)
35
+ get_play_card_with_no_options_node.(card_id: card_id)
36
+ end
37
+
38
+ def handle_select_player_id_validator(validator, card_id:)
39
+ get_play_card_with_player_id_option_node.(
40
+ card_id: card_id,
41
+ player_id_list: validator.legal_target_player_ids)
42
+ end
43
+
44
+ def handle_soldier_validator(validator, card_id:)
45
+ get_play_card_with_player_id_and_card_id_option_node.(
46
+ card_id: card_id,
47
+ player_id_list: validator.legal_target_player_ids,
48
+ card_id_list: validator.legal_target_card_ids)
49
+ end
50
+ end
51
+ end
52
+ end
53
+
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dry-initializer'
4
+
5
+ module LoveLetterApplication
6
+ module Results
7
+ class ProcessPrincessDiscarded
8
+ include Dry::Initializer.define -> do
9
+ option :get_princess_discarded_node, type: ::Types::Callable
10
+ option :eliminate_player, type: ::Types::Callable
11
+ end
12
+
13
+ def call(target_player_id:, game_board:, change_orders:)
14
+ eliminate_player.(
15
+ target_player_id: target_player_id,
16
+ game_board: game_board,
17
+ change_orders: change_orders.push(
18
+ get_princess_discarded_node.(player_id: target_player_id)))
19
+ end
20
+ end
21
+ end
22
+ end
23
+
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dry-initializer'
4
+
5
+ module LoveLetterApplication
6
+ module Results
7
+ class ProcessResolveWizard
8
+ include Dry::Initializer.define -> do
9
+ option :get_discard_card_node, type: ::Types::Callable
10
+ option :process_discard_passive_result, type: ::Types::ArrayOfCallable
11
+ option :process_draw_after_discard, type: ::Types::Callable
12
+ end
13
+
14
+ def call(target_player_id:, game_board:, change_orders:)
15
+ card_id = card_id_for(game_board, target_player_id)
16
+ process_discard_passive_result[card_id].(
17
+ target_player_id: target_player_id,
18
+ game_board: game_board,
19
+ change_orders: change_orders
20
+ .push(get_discard_card_node.(player_id: target_player_id, card_id: card_id)),
21
+ &process_draw_after_discard.method(:call))
22
+ end
23
+
24
+ private
25
+ def card_id_for(game_board, player_id)
26
+ game_board.players
27
+ .find{|player| player.id.to_i.eql?(player_id.to_i)}
28
+ .hand
29
+ .first
30
+ .id
31
+ .to_i
32
+ end
33
+ end
34
+ end
35
+ end
36
+
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dry-initializer'
4
+
5
+ module LoveLetterApplication
6
+ module Results
7
+ class ProcessRoundCompleteByDepletedDeck
8
+ extend Dry::Initializer
9
+ option :log_deck_depleted_node, type: ::Types.Interface(:accept), reader: :private
10
+ option :get_players_and_scores_node, type: ::Types::Callable, reader: :private
11
+ option :get_player_victory_node, type: ::Types::Callable, reader: :private
12
+ option :round_complete, type: ::Types::Callable, reader: :private
13
+ option :get_result_game_board_node, type: ::Types::Callable, reader: :private
14
+
15
+ def call(game_board:, change_orders:)
16
+ players_and_scores = get_players_and_scores(game_board)
17
+ victorious_player_id = get_victorious_player_id(game_board, players_and_scores)
18
+ game_board = round_complete.call(
19
+ game_board: game_board,
20
+ victorious_player_id: victorious_player_id)
21
+ change_orders
22
+ .push(get_players_and_scores_node.(players_and_scores: players_and_scores))
23
+ .push(get_player_victory_node.(player_id: victorious_player_id))
24
+ .push(get_result_game_board.(game_board: game_board))
25
+ end
26
+
27
+ private
28
+ def get_players_and_scores(game_board)
29
+ game_board.players
30
+ .select(&:active?)
31
+ .map{|player| [player.id, point_value_of(player)]}
32
+ .to_h
33
+ end
34
+
35
+ def point_value_of(player)
36
+ hand_card_value = hand_card.reduce{|c,d| c.rank.to_i + d.rank.to_i} * 100
37
+ played_cards_value = player.played_cards.reduce{|c, d| c.rank.to_i + d.rank.to_i}
38
+ hand_card_value + played_cards_value
39
+ end
40
+
41
+ def get_victorious_player_id(game_board, players_and_scores)
42
+ winning_score = players_and_scores.to_a.map(&:last).max
43
+ players_and_scores.map{|k, v| v.eql?(winning_score)}
44
+ .map(&:id)
45
+ .map(&:to_i)
46
+ end
47
+ end
48
+ end
49
+ end
50
+
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dry-initializer'
4
+
5
+ module LoveLetterApplication
6
+ module Results
7
+ class ProcessRoundCompleteByElimination
8
+ extend Dry::Initializer
9
+ option :log_all_opponents_eliminated_node, type: ::Types.Interface(:accept), reader: :private
10
+ option :get_player_victory_node, type: ::Types::Callable, reader: :private
11
+ option :round_complete, type: ::Types::Callable, reader: :private
12
+ option :get_result_game_board_node, type: ::Types::Callable, reader: :private
13
+
14
+ def call(game_board:, player:, change_orders:)
15
+ game_board = round_complete.call(
16
+ game_board: game_board,
17
+ victorious_player_id: [player.id.to_i])
18
+ change_orders
19
+ .push(log_all_opponents_eliminated_node)
20
+ .push(get_player_victory_node.(player_id: [player.id]))
21
+ .push(get_result_game_board_node.(game_board: game_board))
22
+ end
23
+ end
24
+ end
25
+ end
26
+
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dry-initializer'
4
+
5
+ module LoveLetterApplication
6
+ module Results
7
+ class ProcessSwitchHands
8
+ include Dry::Initializer.define -> do
9
+ option :get_hands_switched_node, type: ::Types::Callable
10
+ option :switch_hands, type: ::Types::Callable
11
+ option :process_next_player_turn, type: ::Types::Callable
12
+ end
13
+
14
+ def call(target_player_id:, game_board:, change_orders:)
15
+ player_id = game_board.current_player_id.to_i
16
+ old_card_id, new_card_id = get_cards_held_by(player_id, target_player_id.to_i, game_board)
17
+ game_board = switch_hands.(
18
+ player_id: player_id,
19
+ target_player_id: target_player_id,
20
+ game_board: game_board)
21
+ process_next_player_turn.(
22
+ game_board: game_board,
23
+ change_orders: change_orders.push(get_hands_switched_node.(
24
+ player_id: player_id,
25
+ target_player_id: target_player_id,
26
+ card_id_given: old_card_id,
27
+ card_id_taken: new_card_id)))
28
+ end
29
+
30
+ private
31
+ def get_cards_held_by(player_id, target_player_id, game_board)
32
+ old_card_id = game_board.players
33
+ .find{|player| player.id.to_i.eql?(player_id)}
34
+ .hand
35
+ .first
36
+ .id
37
+ .to_i
38
+
39
+ new_card_id = game_board.players
40
+ .find{|player| player.id.to_i.eql?(target_player_id)}
41
+ .hand
42
+ .first
43
+ .id
44
+ .to_i
45
+
46
+ [old_card_id, new_card_id]
47
+ end
48
+ end
49
+ end
50
+ end
51
+
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LoveLetterApplication
4
+ module Results
5
+ class YieldToBlock
6
+ def call(**args, &block)
7
+ yield(**args) if block_given?
8
+ end
9
+ end
10
+ end
11
+ end
12
+
@@ -0,0 +1,20 @@
1
+ #frozen string_literal: true
2
+
3
+ module LoveLetterApplication
4
+ module Types
5
+ i = Class::new do
6
+ @@has_card_methods = ::Types.Interface(:id, :rank, :targetable?)
7
+
8
+ def call(card)
9
+ @@has_card_methods.call(card)
10
+ ::Types::Coercible::Integer.call(card.id)
11
+ ::Types::Coercible::Integer.call(card.rank)
12
+ ::Types::Strict::Bool.call(card.targetable?)
13
+ card
14
+ end
15
+ end.new
16
+
17
+ LoveLetterApplication::Types::Card = ::Types::Constructor(Class){|value| i.call(value)}
18
+ end
19
+ end
20
+
@@ -0,0 +1,59 @@
1
+ #frozen string_literal: true
2
+
3
+ require 'dry-initializer'
4
+ require 'dry-types'
5
+ require 'love_letter_application/types/card'
6
+ require 'love_letter_application/types/player'
7
+
8
+ module LoveLetterApplication
9
+ module Types
10
+ i = Class::new do
11
+ @@has_game_board_methods =
12
+ ::Types.Interface(:players, :draw_pile, :set_aside_card, :current_player_id, :game_state)
13
+ def call(game_board)
14
+ @@has_game_board_methods.(game_board)
15
+ ::Types::Array::of(LoveLetterApplication::Types::Player).call(game_board.players)
16
+ ::Types::Array::of(LoveLetterApplication::Types::Card).call(game_board.draw_pile)
17
+ LoveLetterApplication::Types::Card.call(game_board.set_aside_card)
18
+ ::Types::Coercible::Integer.call(game_board.current_player_id)
19
+ LoveLetterApplication::Types::GameStateEnum.call(game_board.game_state)
20
+ all_players_have_different_ids_and_seats(game_board.players)
21
+ verify_current_player_id_is_valid(game_board)
22
+ game_board
23
+ end
24
+
25
+ private
26
+ def all_players_have_different_ids_and_seats(players)
27
+ id_list = []
28
+ seat_list = []
29
+ players.each do |player|
30
+ if id_list.include?(player.id.to_i)
31
+ raise Dry::Types::ConstraintError::new(
32
+ 'All players have distinct ids', players.map(&:id))
33
+ end
34
+ if seat_list.include?(player.seat.to_i)
35
+ raise Dry::Types::ConstraintError::new(
36
+ 'All players have distinct seats', players.map(&:seat))
37
+ end
38
+ id_list.push player.id.to_i
39
+ seat_list.push player.seat.to_i
40
+ end
41
+ end
42
+
43
+ def verify_current_player_id_is_valid(game_board)
44
+ id_list = game_board.players.map{|player| player.id.to_i}
45
+ if !id_list.include?(game_board.current_player_id.to_i)
46
+ raise Dry::Types::ConstraintError::new(
47
+ "The player id whose turn it is not in the player list (#{id_list})",
48
+ game_board.current_player_id)
49
+ end
50
+ end
51
+ end.new
52
+
53
+ LoveLetterApplication::Types::GameBoard = ::Types::Constructor(Class){|value| i.call(value)}
54
+
55
+ LoveLetterApplication::Types::GameStateEnum =
56
+ ::Types::String::enum('card_drawn', 'round_complete')
57
+ end
58
+ end
59
+