poker-engine 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: bc329a00c9421d0689395ca15d79839d8f7141f7
4
+ data.tar.gz: f5b4b1d27ee2fcc6dd929587a2336832f73c6037
5
+ SHA512:
6
+ metadata.gz: cbdc36361e03f6919bdbb3cfa729131a893741942afa915a20851482c1f7ca2b44cf2dc90cc2a843f75b97b47541db732d7a8c53ac0f993bd80d963943847ffe
7
+ data.tar.gz: 52b87a7f764f916f2b0a035c72c6687212f46001c98dc8131a7b9cab4a0d0b78de53f77e9cbc6070578ee9cf71ffa9f04056841a9fd42c57a03cff577786d687
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2017 Kamen Kanev
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,24 @@
1
+ # poker-engine
2
+ Poker library written in Ruby :heart:
3
+
4
+ ![The price](the_price.jpg)
5
+
6
+ ## Try it
7
+
8
+ Currently there is simple 74% working console interface `./examples/console-ui`
9
+
10
+ ## TODOs
11
+
12
+ - [ ] Adding validators
13
+
14
+ Currently, the user moves are not validated and that can result in breaking the game logic.
15
+ - [ ] Rethink the concept of aggressor
16
+ - [ ] Plug-in the hand evaluation logic in to the game cycle
17
+
18
+ ## Test~~s~~
19
+
20
+ Run `rspec`
21
+
22
+ ## Resources
23
+
24
+ - https://www.pokernews.com/pokerterms
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'irb'
4
+ require 'irb/completion'
5
+ require 'bundler/setup'
6
+
7
+ Bundler.require(:default, :development)
8
+
9
+ IRB.start
@@ -0,0 +1 @@
1
+ require_relative 'poker_engine'
@@ -0,0 +1,9 @@
1
+ require 'hamster'
2
+ require 'ostruct'
3
+ require 'poker_engine/game'
4
+ require 'poker_engine/next_actions'
5
+ require 'poker_engine/state_operations'
6
+ require 'poker_engine/card'
7
+ require 'poker_engine/cards'
8
+ require 'poker_engine/hand_evaluator'
9
+ require 'poker_engine/version'
@@ -0,0 +1,27 @@
1
+ module PokerEngine
2
+ class Card
3
+ COLORS = { spade: :spade, club: :club, heart: :heart, diamond: :diamond }.freeze
4
+ RANKS = (2..10).to_a.map(&:to_s) + %w(J Q K A)
5
+
6
+ def self.french_deck
7
+ Card::RANKS
8
+ .product(Card::COLORS.values)
9
+ .map { |rank, color| Card.new rank, color }
10
+ end
11
+
12
+ attr_reader :rank, :color
13
+
14
+ def initialize(rank, color)
15
+ @rank = rank
16
+ @color = color
17
+ end
18
+
19
+ def value
20
+ @value ||= RANKS.index rank
21
+ end
22
+
23
+ def to_s
24
+ "#{@rank}#{@color[0]}"
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,64 @@
1
+ module PokerEngine
2
+ class Cards
3
+ include Enumerable
4
+
5
+ COLOR_BY_FIRST_LETTER = {
6
+ 's' => Card::COLORS[:spade],
7
+ 'c' => Card::COLORS[:club],
8
+ 'h' => Card::COLORS[:heart],
9
+ 'd' => Card::COLORS[:diamond],
10
+ }.freeze
11
+
12
+ def self.parse(str_cards)
13
+ cards = str_cards
14
+ .split(',')
15
+ .map do |str|
16
+ Card.new str.to_i, COLOR_BY_FIRST_LETTER.fetch(str[-1])
17
+ end
18
+
19
+ new(cards)
20
+ end
21
+
22
+ attr_reader :cards
23
+
24
+ def initialize(cards)
25
+ @cards = cards
26
+ end
27
+
28
+ def to_s
29
+ cards.map(&:to_s).join(', ')
30
+ end
31
+
32
+ def each(&block)
33
+ cards.each(&block)
34
+ end
35
+
36
+ def +(other)
37
+ Cards.new(cards + other.cards)
38
+ end
39
+
40
+ # TODO: Make it work with block, too
41
+ def sort
42
+ cards.sort_by(&:value)
43
+ end
44
+
45
+ def sorted_values
46
+ cards.map(&:value).sort
47
+ end
48
+
49
+ def combination(x)
50
+ cards.combination(x).map { |c| Cards.new(c) }
51
+ end
52
+
53
+ # Make descending order primary by occurency and secondary by value
54
+ def values_desc_by_occurency
55
+ values = cards.map(&:value)
56
+
57
+ values.sort do |a, b|
58
+ coefficient_occurency = (values.count(a) <=> values.count(b))
59
+
60
+ coefficient_occurency.zero? ? -(a <=> b) : -coefficient_occurency
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,81 @@
1
+ require_relative 'reducer'
2
+
3
+ module PokerEngine
4
+ module Game
5
+ POSITIONS_ORDER = {
6
+ preflop: %i(UTG MP CO D SB BB).freeze,
7
+ postflop: %i(SB BB UTG MP CO D).freeze,
8
+ }.freeze
9
+
10
+ module_function
11
+
12
+ def start(*args, &handler)
13
+ state = initial_state(*args)
14
+ run(state, &handler)
15
+ end
16
+
17
+ def next(state, player_action, &handler)
18
+ state = Reducer.call state, player_action
19
+
20
+ run(state, &handler)
21
+ end
22
+
23
+ def run(state, &handler)
24
+ subscribed_reducer = lambda do |old_state, action|
25
+ new_state = Reducer.call old_state, action
26
+ handler&.call [old_state, action], new_state
27
+
28
+ new_state
29
+ end
30
+
31
+ loop do
32
+ break state if state[:pending_request] || state[:game_ended]
33
+
34
+ actions = NextActions.call(state)
35
+ state = actions.reduce(state, &subscribed_reducer)
36
+ end
37
+ end
38
+
39
+ # TODO: remove blinds defaults
40
+ def initial_state(players, small_blind: 10, big_blind: 20, deck_seed: 1)
41
+ reversed_position_order = POSITIONS_ORDER[:preflop].reverse
42
+ positions = players.map.with_index do |player, index|
43
+ last_index = players.count - 1
44
+
45
+ [player[:id], reversed_position_order[last_index - index]]
46
+ end.to_h
47
+
48
+ normalized_players = players.map do |id:, balance:, **|
49
+ [
50
+ id,
51
+ {
52
+ id: id,
53
+ active: true,
54
+ balance: balance,
55
+ money_in_pot: 0,
56
+ position: positions[id],
57
+ cards: [],
58
+ last_move: {},
59
+ },
60
+ ]
61
+ end.to_h
62
+
63
+ Hamster.from(
64
+ players: normalized_players,
65
+ aggressor_id: nil,
66
+ board: [],
67
+ small_blind: small_blind,
68
+ big_blind: big_blind,
69
+ pot: 0,
70
+ pending_request: false,
71
+ winner_ids: [],
72
+ top_hands: {},
73
+ game_ended: false,
74
+ last_action: { type: :game_start },
75
+ current_stage: nil,
76
+ current_player_id: nil,
77
+ deck: Card.french_deck.shuffle(random: Random.new(deck_seed))
78
+ )
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,32 @@
1
+ require_relative 'hand_index'
2
+
3
+ module PokerEngine
4
+ module HandEvaluator
5
+ module_function
6
+
7
+ def find_top_hands(players, board)
8
+ players
9
+ .map do |id:, cards:, **|
10
+ cards = board + cards
11
+ player_top_hand = cards.combination(5)
12
+ .map do |five_cards|
13
+ HandIndex.new(Cards.new(five_cards))
14
+ end
15
+ .max
16
+
17
+ [id, player_top_hand]
18
+ end
19
+ .to_h
20
+ end
21
+
22
+ def find_winners(players, board)
23
+ top_hand_per_player_id = find_top_hands(players, board)
24
+
25
+ best_hand = top_hand_per_player_id.values.max
26
+
27
+ top_hand_per_player_id
28
+ .map { |player_id, hand| hand == best_hand ? player_id : nil }
29
+ .compact
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,36 @@
1
+ require_relative 'hand_levels'
2
+
3
+ module PokerEngine
4
+ class HandIndex
5
+ # The index is equivalent to the level strength
6
+ RANK_TABLE = [HandLevels::HighCard, HandLevels::OnePair,
7
+ HandLevels::TwoPairs, HandLevels::ThreeOfAKind,
8
+ HandLevels::Straight, HandLevels::Flush,
9
+ HandLevels::FullHouse, HandLevels::FourOfAKind,
10
+ HandLevels::StraightFlush].freeze
11
+
12
+ attr_reader :cards
13
+
14
+ def initialize(cards)
15
+ @cards = cards
16
+ end
17
+
18
+ def <=>(other)
19
+ outer_level_compare =
20
+ (RANK_TABLE.index(level) <=> RANK_TABLE.index(other.level))
21
+
22
+ return outer_level_compare unless outer_level_compare.zero?
23
+
24
+ level <=> other.level
25
+ end
26
+
27
+ def >(other)
28
+ level <=> other.level
29
+ end
30
+
31
+ def level
32
+ @level ||=
33
+ RANK_TABLE.reverse_each.find { |level| level.owns?(cards) }
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,107 @@
1
+ require_relative 'cards'
2
+
3
+ module PokerEngine
4
+ module HandLevels
5
+ # Abstract class for Hand level
6
+ class BaseLevel
7
+ attr_reader :cards
8
+
9
+ def initialize(cards)
10
+ @cards = cards
11
+ end
12
+
13
+ def <=>(other)
14
+ unless other.instance_of?(self.class)
15
+ fail "Can't detail detail hands of different level"
16
+ end
17
+
18
+ detail_compare(other)
19
+ end
20
+
21
+ def detail_compare(other)
22
+ cards.values_desc_by_occurency <=>
23
+ other.cards.values_desc_by_occurency
24
+ end
25
+ end
26
+
27
+ #============================= Levels ======================================
28
+
29
+ HighCard = Class.new(BaseLevel) do
30
+ def self.owns?(_cards)
31
+ true
32
+ end
33
+ end
34
+
35
+ OnePair = Class.new(BaseLevel) do
36
+ def self.owns?(cards)
37
+ cards.sorted_values
38
+ .group_by(&:itself)
39
+ .any? { |_, group| group.size == 2 }
40
+ end
41
+ end
42
+
43
+ TwoPairs = Class.new(BaseLevel) do
44
+ def self.owns?(cards)
45
+ cards.sorted_values
46
+ .group_by(&:itself)
47
+ .select { |_, group| group.size == 2 }
48
+ .count
49
+ .eql?(2)
50
+ end
51
+ end
52
+
53
+ ThreeOfAKind = Class.new(BaseLevel) do
54
+ def self.owns?(cards)
55
+ cards.sorted_values
56
+ .group_by(&:itself)
57
+ .one? { |_, group| group.size == 3 }
58
+ end
59
+ end
60
+
61
+ Straight = Class.new(BaseLevel) do
62
+ def self.owns?(cards)
63
+ cards.sorted_values
64
+ .each_cons(2)
65
+ .map { |a, b| a - b }
66
+ .uniq
67
+ .one?
68
+ end
69
+
70
+ def detail_compare(other)
71
+ cards.sorted_values.first <=> other.cards.sorted_values.first
72
+ end
73
+ end
74
+
75
+ Flush = Class.new(BaseLevel) do
76
+ def self.owns?(cards)
77
+ cards.map(&:color).uniq.one?
78
+ end
79
+ end
80
+
81
+ FullHouse = Class.new(BaseLevel) do
82
+ def self.owns?(cards)
83
+ cards.map(&:value).uniq.count > 1 &&
84
+ OnePair.owns?(cards) &&
85
+ ThreeOfAKind.owns?(cards)
86
+ end
87
+ end
88
+
89
+ FourOfAKind = Class.new(BaseLevel) do
90
+ def self.owns?(cards)
91
+ cards.sorted_values
92
+ .group_by(&:itself)
93
+ .one? { |_, group| group.size == 4 }
94
+ end
95
+ end
96
+
97
+ StraightFlush = Class.new(BaseLevel) do
98
+ def self.owns?(cards)
99
+ Straight.owns?(cards) && Flush.owns?(cards)
100
+ end
101
+
102
+ def detail_compare(other)
103
+ cards.sorted_values.first <=> other.cards.sorted_values.first
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,44 @@
1
+ module PokerEngine
2
+ module NextActions
3
+ def self.call(state)
4
+ state_operations = StateOperations.new state
5
+
6
+ case state.dig :last_action, :type
7
+ when :game_start
8
+ [
9
+ { type: :next_stage, stage: :preflop },
10
+ { type: :take_small_blind, player_id: state_operations.player_id_by(position: :SB) },
11
+ { type: :take_big_blind, player_id: state_operations.player_id_by(position: :BB) },
12
+ ]
13
+ when :take_big_blind
14
+ state_operations.ordered_player_ids.map do |id|
15
+ { type: :distribute_to_player, player_id: id }
16
+ end
17
+ when :distribute_to_player, :distribute_to_board
18
+ [{ type: :move_request, player_id: state_operations.first_player_id }]
19
+ when :check, :call, :raise, :fold
20
+ player_id = state_operations.next_player_id
21
+
22
+ if player_id == state[:current_player_id]
23
+ [{ type: :game_end, winner_ids: [player_id] }]
24
+ elsif player_id == state[:aggressor_id] && !state_operations.next_stage?
25
+ players = Hamster.to_ruby state[:players].values
26
+
27
+ [{
28
+ type: :game_end,
29
+ top_hands: HandEvaluator.find_top_hands(players, state[:board].to_a),
30
+ winner_ids: HandEvaluator.find_winners(players, state[:board].to_a),
31
+ }]
32
+ elsif player_id == state[:aggressor_id]
33
+ [{ type: :next_stage, stage: state_operations.next_stage }]
34
+ else
35
+ [{ type: :move_request, player_id: player_id }]
36
+ end
37
+ when :next_stage
38
+ [{ type: :distribute_to_board, cards_count: state_operations.stage_cards_count }]
39
+ else
40
+ raise 'error'
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,127 @@
1
+ require 'ostruct'
2
+
3
+ module PokerEngine
4
+ module Reducer
5
+ def self.call(state, action)
6
+ raise "Unknown action #{action[:type]}" unless allowed_actions.include? action[:type]
7
+
8
+ Actions
9
+ .public_send(action[:type], state, action)
10
+ .put(:last_action, action)
11
+ end
12
+
13
+ def self.allowed_actions
14
+ @allowed_actions ||= Actions.methods(false)
15
+ end
16
+
17
+ module Actions
18
+ module_function
19
+
20
+ def game_start(state, **_action)
21
+ state
22
+ end
23
+
24
+ def take_small_blind(state, **action)
25
+ take_blind state, action.merge(blind_kind: :small_blind)
26
+ end
27
+
28
+ def take_big_blind(state, **action)
29
+ take_blind state, action.merge(blind_kind: :big_blind)
30
+ end
31
+
32
+ def distribute_to_player(state, player_id:, **_action)
33
+ new_deck, poped_cards =
34
+ state[:deck].partition.with_index { |_, i| i + 1 <= state[:deck].count - 2 }
35
+
36
+ state
37
+ .put(:deck, new_deck)
38
+ .update_in(:players, player_id, :cards) { poped_cards }
39
+ end
40
+
41
+ def distribute_to_board(state, cards_count:, **_action)
42
+ new_deck, poped_cards =
43
+ state[:deck].partition.with_index { |_, i| i + 1 <= state[:deck].count - cards_count }
44
+
45
+ state
46
+ .put(:deck, new_deck)
47
+ .put(:board) { |board| board + poped_cards }
48
+ end
49
+
50
+ def move_request(state, player_id:, **_action)
51
+ state
52
+ .put(:current_player_id, player_id)
53
+ .put(:pending_request, true)
54
+ end
55
+
56
+ def call(state, **action)
57
+ bet = state.dig(:players, state[:aggressor_id], :last_move, :bet) || state[:big_blind]
58
+ money_to_give = bet - state.dig(:players, action[:player_id], :money_in_pot)
59
+
60
+ pay(state, action.merge(money_to_give: money_to_give))
61
+ .put(:aggressor_id) { |id| id || action[:player_id] } # HACK: try to get rid of it.
62
+ end
63
+
64
+ def raise(state, **action)
65
+ money_to_give = action[:bet] - state.dig(:players, action[:player_id], :money_in_pot)
66
+
67
+ pay(state, action.merge(money_to_give: money_to_give))
68
+ .put(:aggressor_id, action[:player_id])
69
+ end
70
+
71
+ def check(state, player_id:, **_action)
72
+ state
73
+ .put(:pending_request, false)
74
+ .put(:aggressor_id) { |id| id || player_id } # HACK: try to get rid of it.
75
+ end
76
+
77
+ def fold(state, **action)
78
+ state
79
+ .update_in(:players, action[:player_id]) do |player|
80
+ player.put(:active, false).put(:last_move, action)
81
+ end
82
+ .put(:pending_request, false)
83
+ end
84
+
85
+ def next_stage(state, stage:, **_action)
86
+ state
87
+ .update_in(:players) do |players|
88
+ players.map { |id, player| [id, player.put(:money_in_pot, 0)] }
89
+ end
90
+ .put(:current_stage, stage)
91
+ .put(:aggressor_id, nil)
92
+ end
93
+
94
+ def game_end(state, top_hands:, winner_ids:, **_action)
95
+ state
96
+ .put(:top_hands, top_hands)
97
+ .put(:winner_ids, winner_ids)
98
+ .put(:game_ended, true)
99
+ end
100
+
101
+ private_class_method def take_blind(state, player_id:, blind_kind:, **_action)
102
+ blind_size = state.fetch blind_kind
103
+
104
+ state
105
+ .update_in(:players, player_id) do |player|
106
+ player
107
+ .put(:balance) { |b| b - blind_size }
108
+ .put(:money_in_pot) { |mip| mip + blind_size }
109
+ end
110
+ .put(:pot) { |pot| pot + blind_size }
111
+ end
112
+
113
+ # TODO: handle the case when the bet is higher then the current balance
114
+ private_class_method def pay(state, money_to_give:, **action)
115
+ state
116
+ .update_in(:players, action[:player_id]) do |player|
117
+ player
118
+ .put(:balance) { |b| b - money_to_give }
119
+ .put(:money_in_pot) { |mip| mip + money_to_give }
120
+ .put(:last_move, action)
121
+ end
122
+ .put(:pot) { |pot| pot + money_to_give }
123
+ .put(:pending_request, false)
124
+ end
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,66 @@
1
+ module PokerEngine
2
+ class StateOperations
3
+ CARDS_COUNT_PER_STAGE_START = { flop: 3, turn: 1, river: 1 }.freeze
4
+ STAGES = %i(preflop flop turn river).freeze
5
+
6
+ attr_reader :state
7
+
8
+ def initialize(state)
9
+ @state = state
10
+ end
11
+
12
+ def player_id_by(position:)
13
+ players.find { |_id, player| player[:position] == position }.first
14
+ end
15
+
16
+ def next_stage?
17
+ state[:current_stage] != :river
18
+ end
19
+
20
+ def next_stage
21
+ STAGES.fetch STAGES.index(state[:current_stage]) + 1
22
+ end
23
+
24
+ def stage_cards_count
25
+ StateOperations::CARDS_COUNT_PER_STAGE_START.fetch state[:current_stage]
26
+ end
27
+
28
+ def next_player_id
29
+ ordered_player_ids.cycle.each_with_index.find do |id, order_index|
30
+ order_index > ordered_player_ids.index(state[:current_player_id]) &&
31
+ players[id][:active]
32
+ end.first
33
+ end
34
+
35
+ def one_player_left?
36
+ players.count { |_, player| player[:active] } == 1
37
+ end
38
+
39
+ def active_players
40
+ players.select { |_, player| player[:active] }
41
+ end
42
+
43
+ NO_ACTIVE_FILTER = Object.new
44
+ def ordered_player_ids(active: NO_ACTIVE_FILTER)
45
+ raise 'Unexpected state' unless state[:current_stage]
46
+
47
+ positions = Game::POSITIONS_ORDER[state[:current_stage] == :preflop ? :preflop : :postflop]
48
+
49
+ players.each_with_object(Array.new(players.count)) do |(id, player), ordered|
50
+ next if active != NO_ACTIVE_FILTER && active != player[:active]
51
+
52
+ ordered[positions.index player[:position]] = id
53
+ end.compact
54
+ end
55
+
56
+ def first_player_id
57
+ ordered_player_ids(active: true).first
58
+ end
59
+
60
+ private
61
+
62
+ def players
63
+ state[:players]
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,3 @@
1
+ module PokerEngine
2
+ VERSION = '1.0.0'.freeze
3
+ end
metadata ADDED
@@ -0,0 +1,145 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: poker-engine
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Kamen Kanev
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-01-31 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: hamster
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '3.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '3.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.3'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.3'
41
+ - !ruby/object:Gem::Dependency
42
+ name: pry-byebug
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '10.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '10.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.4'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.4'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubocop
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '0.48'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '0.48'
97
+ description: " Poker library introducing the game logic with a simple interface.
98
+ Currently offering only 6-max Holdem games.\n"
99
+ email: kamen.e.kanev@gmail.com
100
+ executables:
101
+ - console
102
+ extensions: []
103
+ extra_rdoc_files: []
104
+ files:
105
+ - LICENSE
106
+ - README.md
107
+ - bin/console
108
+ - lib/poker-engine.rb
109
+ - lib/poker_engine.rb
110
+ - lib/poker_engine/card.rb
111
+ - lib/poker_engine/cards.rb
112
+ - lib/poker_engine/game.rb
113
+ - lib/poker_engine/hand_evaluator.rb
114
+ - lib/poker_engine/hand_index.rb
115
+ - lib/poker_engine/hand_levels.rb
116
+ - lib/poker_engine/next_actions.rb
117
+ - lib/poker_engine/reducer.rb
118
+ - lib/poker_engine/state_operations.rb
119
+ - lib/poker_engine/version.rb
120
+ homepage: https://github.com/kekanev/poker-engine
121
+ licenses:
122
+ - MIT
123
+ metadata:
124
+ github: https://github.com/kekanev/poker-engine
125
+ post_install_message:
126
+ rdoc_options: []
127
+ require_paths:
128
+ - lib
129
+ required_ruby_version: !ruby/object:Gem::Requirement
130
+ requirements:
131
+ - - ">="
132
+ - !ruby/object:Gem::Version
133
+ version: '0'
134
+ required_rubygems_version: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ requirements: []
140
+ rubyforge_project:
141
+ rubygems_version: 2.5.2
142
+ signing_key:
143
+ specification_version: 4
144
+ summary: Poker library introducing the game logic into a simple interface.
145
+ test_files: []