ventiuna 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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