ventiuna 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.
@@ -0,0 +1,69 @@
1
+ module Ventiuna
2
+ class Hand
3
+ attr_accessor :cards
4
+
5
+ def initialize(card=[], *other_cards)
6
+ @cards = Array(card) + other_cards
7
+ @soft = false
8
+ @active = true
9
+ end
10
+
11
+ def << (another_card)
12
+ self.cards << another_card
13
+ end
14
+ alias :hit :<<
15
+
16
+ def stand
17
+ @active = false
18
+ end
19
+
20
+ def split
21
+ return false unless self.pair?
22
+ [Ventiuna::Hand.new(self.cards.first), Ventiuna::Hand.new(self.cards.last)]
23
+ end
24
+
25
+ def value
26
+ sum = 0
27
+ self.cards.sort.each_with_index do |card, i|
28
+ # hit low if haven't reached 11 or there are more cards left
29
+ hit_low = (sum >= 11) || (i+1 != self.cards.size)
30
+ @soft = card.ace? && !hit_low
31
+ sum += card.value(low: hit_low)
32
+ end
33
+ sum
34
+ end
35
+
36
+ def active?
37
+ @active
38
+ end
39
+
40
+ def blackjack?
41
+ self.value == 21 && self.cards.size == 2
42
+ end
43
+
44
+ def bust?
45
+ self.value > 21 #&& !self.soft?
46
+ end
47
+
48
+ def pair?
49
+ self.cards.size == 2 && (self.cards.first.value == self.cards.last.value)
50
+ end
51
+
52
+ def soft?
53
+ self.value
54
+ @soft
55
+ end
56
+
57
+ def to_s
58
+ "#{self.cards.collect{|c| c.to_s}} = #{self.value}"
59
+ end
60
+
61
+ def to_db
62
+ s = "#{value}"
63
+ s += "I" if cards.size == 2
64
+ s += "S" if soft?
65
+ s += "P" if pair?
66
+ s
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,127 @@
1
+ module Ventiuna
2
+ class Player
3
+ attr_accessor :name, :balance, :hands, :bet, :wins, :loses, :ties
4
+ attr_reader :decisions
5
+
6
+ def initialize(name, strategy: "Learning", balance: 1000000.0)
7
+ @name = name
8
+ @strategy = Ventiuna::Strategy.find_or_create_by_name(strategy)
9
+ @balance = balance
10
+ @hands = [Ventiuna::Hand.new]
11
+ @bet = 0
12
+ @wins = 0
13
+ @loses = 0
14
+ @ties = 0
15
+ @decisions = Hash.new([])
16
+ end
17
+
18
+ def stats
19
+ "Wins: #{self.wins} (#{self.wins_percent.round}%), Loses: #{self.loses} (#{self.loses_percent.round}%), Ties: #{self.ties} (#{self.ties_percent.round}%)"
20
+ end
21
+
22
+ def wins_percent
23
+ return 0 if total_rounds == 0
24
+ (self.wins.to_f/self.total_rounds) * 100
25
+ end
26
+
27
+ def loses_percent
28
+ return 0 if total_rounds == 0
29
+ (self.loses.to_f/self.total_rounds) * 100
30
+ end
31
+
32
+ def ties_percent
33
+ return 0 if total_rounds == 0
34
+ (self.ties.to_f/self.total_rounds) * 100
35
+ end
36
+
37
+ def total_rounds
38
+ @wins+@loses+@ties
39
+ end
40
+
41
+ def place_bet(min_bet, max_bet)
42
+ @bet = min_bet
43
+ @balance -= @bet
44
+ end
45
+
46
+ def active_hand
47
+ hands.select{ |h| h.active? }.first
48
+ end
49
+ alias :hand :active_hand
50
+
51
+ def reset_hands
52
+ @hands = [Ventiuna::Hand.new]
53
+ end
54
+ alias :reset_hand :reset_hands
55
+
56
+ def decision(dealers_card, strategy: "interactive")
57
+ define_posible_moves(dealers_card)
58
+ can_split = hands.size == 1
59
+ #d = @strategy.decision(dealers_card, hand, can_split)
60
+ d = nil
61
+
62
+ unless d
63
+ d = "s"
64
+ case strategy
65
+ when "random"
66
+ d = random_strategy
67
+ when "interactive"
68
+ d = interactive
69
+ end
70
+ end
71
+ move = Ventiuna::Move.find_by_strategy_id_and_dealers_card_value_and_hand_and_decision(@strategy.id, dealers_card.value, hand.to_db, d)
72
+ #puts move.inspect
73
+ @decisions[hand] = decisions[hand] << move
74
+ #puts @decisions
75
+ d
76
+ end
77
+
78
+ def reset_decisions
79
+ @decisions = Hash.new([])
80
+ end
81
+
82
+ private
83
+ def posible_moves(dealers_card)
84
+ moves_list = ["s"]
85
+ return moves_list if active_hand.bust?
86
+
87
+ moves_list.unshift("h") if active_hand.soft? || active_hand.value < 21
88
+ moves_list.unshift("sp") if active_hand.pair?
89
+ moves_list.unshift("d") if active_hand.cards.size == 2
90
+
91
+ moves_list
92
+ end
93
+
94
+ def define_posible_moves(dealers_card)
95
+ posible_moves(dealers_card).each do |m|
96
+ Ventiuna::Move.find_or_create_by_strategy_id_and_dealers_card_value_and_hand_and_decision(@strategy.id, dealers_card.value, hand.to_db, m).save
97
+ end
98
+ end
99
+
100
+ def random_strategy
101
+ return "s" if self.active_hand.bust?
102
+ return "s" if self.active_hand.value == 21 || self.active_hand.blackjack?
103
+ return ["s", "h", "d", "sp"].shuffle.pop if self.active_hand.pair?
104
+ return ["s", "h", "d"].shuffle.pop if self.active_hand.cards.size == 2
105
+ ["s", "h"].shuffle.pop
106
+ end
107
+
108
+ def interactive
109
+ choice = $stdin.gets.to_s.strip
110
+
111
+ case choice
112
+ when /^hit$|^h$/i
113
+ "h"
114
+ when /^stand$|^s$/i
115
+ "s"
116
+ when /^double$|^d$/i
117
+ "d"
118
+ when /^split$|^sp$/i
119
+ "sp"
120
+ else
121
+ puts "invalid choice!"
122
+ interactive()
123
+ end
124
+
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,19 @@
1
+ module Ventiuna
2
+ class Shoe < Ventiuna::Deck
3
+
4
+ def initialize(deck_count=8)
5
+ @cards = []
6
+ suits = @@suits * deck_count
7
+ suits.each do |suit|
8
+ @@ranks.each do |rank|
9
+ @cards << Ventiuna::Card.new(rank: rank, suit: suit)
10
+ end
11
+ end
12
+ @initial_size = @cards.size
13
+ end
14
+
15
+ def needs_shuffling?
16
+ !self.shuffled? || (self.size.to_f / @initial_size) <= 0.25
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,32 @@
1
+ module Ventiuna
2
+ class Move < ActiveRecord::Base
3
+ belongs_to :strategy
4
+
5
+ #validates_presence_of :dealers_card, :cards, :hand_value, :decision
6
+ validates_uniqueness_of :decision, :scope => [:strategy_id, :dealers_card_value, :hand]
7
+
8
+ def score
9
+ #win_count*2 + tie_count
10
+ (win + tie + progress) - (lose + bust)
11
+ end
12
+
13
+ def update_counts(outcome)
14
+ value = 1
15
+ value = 2 if decision == "d"
16
+ case outcome
17
+ when "blackjack", "win"
18
+ self.win += value
19
+ when "tie"
20
+ self.tie += value
21
+ when "bust"
22
+ self.lose += value
23
+ when "lose"
24
+ self.lose += value
25
+ when "progress"
26
+
27
+ self.progress += 1
28
+ end
29
+ self.save
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,28 @@
1
+ module Ventiuna
2
+ class Strategy < ActiveRecord::Base
3
+ has_many :moves
4
+
5
+ validates_uniqueness_of :name
6
+
7
+ def decision(dealers_card, players_hand, can_split)
8
+ best_score(dealers_card, players_hand, can_split)
9
+ end
10
+
11
+ private
12
+ def best_score(dealers_card, players_hand, can_split)
13
+ r = nil
14
+ best_move = nil
15
+ past_moves = self.moves.where(:dealers_card_value => dealers_card.value, :hand => players_hand.to_db)
16
+ #puts past_moves.inspect
17
+ #past_decisions = past_moves.collect{ |m| m.decision }
18
+ #return nil unless past_decisions.include?("h") && past_decisions.include?("s")
19
+ past_moves.each do |m|
20
+ best_move = m unless best_move
21
+ next if m.decision == "sp" && !can_split # can't split here
22
+ best_move = m if m.score > best_move.score
23
+ end
24
+ r = best_move.decision if best_move
25
+ r
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,3 @@
1
+ module Ventiuna
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,22 @@
1
+ require 'test_helper'
2
+
3
+ class TestCard < Test::Unit::TestCase
4
+ def test_init
5
+ duce_of_clubs = Ventiuna::Card.new(rank: "2", suit: "club")
6
+ assert_equal("2", duce_of_clubs.rank)
7
+ assert_equal("club", duce_of_clubs.suit)
8
+ end
9
+
10
+ def test_value
11
+ duce_of_clubs = Ventiuna::Card.new(rank: "2", suit: "club")
12
+ assert_equal(2, duce_of_clubs.value)
13
+
14
+ king_of_hearts = Ventiuna::Card.new(rank: "K", suit: "heart")
15
+ assert_equal(10, king_of_hearts.value)
16
+
17
+ ace_of_spades = Ventiuna::Card.new(rank: "A", suit: "spade")
18
+ assert_equal(11, ace_of_spades.value)
19
+ assert_equal(11, ace_of_spades.value(high: true))
20
+ assert_equal(1, ace_of_spades.value(low: true))
21
+ end
22
+ end
@@ -0,0 +1,153 @@
1
+ require 'test_helper'
2
+
3
+ class TestDealer < Test::Unit::TestCase
4
+ def setup
5
+ @dealer = Ventiuna::Dealer.new("TestDealer")
6
+
7
+ @duce = Ventiuna::Card.new(rank: "2", suit: "club")
8
+ @tres = Ventiuna::Card.new(rank: "3", suit: "diamond")
9
+ @quatro = Ventiuna::Card.new(rank: "4", suit: "heart")
10
+ @cinco = Ventiuna::Card.new(rank: "5", suit: "diamond")
11
+ @seis = Ventiuna::Card.new(rank: "6", suit: "heart")
12
+
13
+ @seven_of_diamonds = Ventiuna::Card.new(rank: "7", suit: "diamond")
14
+ @eight_of_spades = Ventiuna::Card.new(rank: "8", suit: "spade")
15
+ @eight_of_clubs = Ventiuna::Card.new(rank: "8", suit: "club")
16
+ @nine_of_spades = Ventiuna::Card.new(rank: "9", suit: "spade")
17
+ @ten_of_clubs = Ventiuna::Card.new(rank: "10", suit: "club")
18
+
19
+ @jack_of_clubs = Ventiuna::Card.new(rank: "J", suit: "club")
20
+ @king_of_hearts = Ventiuna::Card.new(rank: "K", suit: "heart")
21
+ @ace_of_diamonds = Ventiuna::Card.new(rank: "A", suit: "diamond")
22
+ @ace_of_spades = Ventiuna::Card.new(rank: "A", suit: "spade")
23
+ @ace_of_clubs = Ventiuna::Card.new(rank: "A", suit: "club")
24
+ end
25
+
26
+ def test_hit_up_to_17
27
+ @dealer.hand << @duce
28
+ @dealer.hand << @duce
29
+ assert_equal("h", @dealer.decision)
30
+ @dealer.reset_hand
31
+
32
+ @dealer.hand << @duce
33
+ @dealer.hand << @tres
34
+ assert_equal("h", @dealer.decision)
35
+ @dealer.reset_hand
36
+
37
+ @dealer.hand << @duce
38
+ @dealer.hand << @quatro
39
+ assert_equal("h", @dealer.decision)
40
+ @dealer.reset_hand
41
+
42
+ @dealer.hand << @duce
43
+ @dealer.hand << @cinco
44
+ assert_equal("h", @dealer.decision)
45
+ @dealer.reset_hand
46
+
47
+ @dealer.hand << @duce
48
+ @dealer.hand << @seis
49
+ assert_equal("h", @dealer.decision)
50
+ @dealer.reset_hand
51
+
52
+ @dealer.hand << @duce
53
+ @dealer.hand << @seven_of_diamonds
54
+ assert_equal("h", @dealer.decision)
55
+ @dealer.reset_hand
56
+
57
+ @dealer.hand << @duce
58
+ @dealer.hand << @eight_of_clubs
59
+ assert_equal("h", @dealer.decision)
60
+ @dealer.reset_hand
61
+
62
+ @dealer.hand << @duce
63
+ @dealer.hand << @nine_of_spades
64
+ assert_equal("h", @dealer.decision)
65
+ @dealer.reset_hand
66
+
67
+ @dealer.hand << @duce
68
+ @dealer.hand << @ten_of_clubs
69
+ assert_equal("h", @dealer.decision)
70
+ @dealer.reset_hand
71
+
72
+ @dealer.hand << @tres
73
+ @dealer.hand << @ten_of_clubs
74
+ assert_equal("h", @dealer.decision)
75
+ @dealer.reset_hand
76
+
77
+ @dealer.hand << @quatro
78
+ @dealer.hand << @ten_of_clubs
79
+ assert_equal("h", @dealer.decision)
80
+ @dealer.reset_hand
81
+
82
+ @dealer.hand << @cinco
83
+ @dealer.hand << @ten_of_clubs
84
+ assert_equal("h", @dealer.decision)
85
+ @dealer.reset_hand
86
+
87
+ @dealer.hand << @seis
88
+ @dealer.hand << @ten_of_clubs
89
+ assert_equal("h", @dealer.decision)
90
+ @dealer.reset_hand
91
+
92
+ @dealer.hand << @duce
93
+ assert_equal("h", @dealer.decision)
94
+ @dealer.hand << @tres
95
+ assert_equal("h", @dealer.decision)
96
+ @dealer.hand << @quatro
97
+ assert_equal("h", @dealer.decision)
98
+ @dealer.hand << @cinco
99
+ assert_equal("h", @dealer.decision)
100
+ @dealer.hand << @ace_of_spades
101
+ assert_equal("h", @dealer.decision)
102
+ @dealer.hand << @ace_of_diamonds
103
+ assert_equal("h", @dealer.decision)
104
+ @dealer.hand << @ace_of_clubs
105
+ assert_equal("s", @dealer.decision)
106
+ @dealer.reset_hand
107
+ end
108
+
109
+ def hit_on_soft_17
110
+ @dealer.hand << @ace_of_diamonds
111
+ @dealer.hand << @seis
112
+ assert_equal("h", @dealer.decision)
113
+ @dealer.reset_hand
114
+
115
+ @dealer.hand << @ace_of_diamonds
116
+ @dealer.hand << @duce
117
+ @dealer.hand << @quatro
118
+ assert_equal("h", @dealer.decision)
119
+ @dealer.reset_hand
120
+ end
121
+
122
+ def stand_on_17_and_up
123
+ #17
124
+ @dealer.hand << @nine_of_spades
125
+ @dealer.hand << @eight_of_clubs
126
+ assert_equal("s", @dealer.decision)
127
+ @dealer.reset_hand
128
+
129
+ #18
130
+ @dealer.hand << @ace_of_diamonds
131
+ @dealer.hand << @seven_of_diamonds
132
+ assert_equal("s", @dealer.decision)
133
+ @dealer.reset_hand
134
+
135
+ #19
136
+ @dealer.hand << @ace_of_diamonds
137
+ @dealer.hand << @eight_of_clubs
138
+ assert_equal("s", @dealer.decision)
139
+ @dealer.reset_hand
140
+
141
+ #20
142
+ @dealer.hand << @ace_of_diamonds
143
+ @dealer.hand << @nine_of_spades
144
+ assert_equal("s", @dealer.decision)
145
+ @dealer.reset_hand
146
+
147
+ #21
148
+ @dealer.hand << @ace_of_diamonds
149
+ @dealer.hand << @jack_of_clubs
150
+ assert_equal("s", @dealer.decision)
151
+ @dealer.reset_hand
152
+ end
153
+ end