blackjack 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.
- data/lib/cardtable.rb +363 -0
- metadata +67 -0
data/lib/cardtable.rb
ADDED
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
class Cardtable < ActiveRecord::Base
|
|
2
|
+
|
|
3
|
+
belongs_to :game
|
|
4
|
+
belongs_to :player_record, :class_name => "User", :foreign_key => 'player_id'
|
|
5
|
+
belongs_to :dealer_record, :class_name => "User", :foreign_key => 'dealer_id'
|
|
6
|
+
|
|
7
|
+
serialize :player
|
|
8
|
+
serialize :dealer
|
|
9
|
+
serialize :deck
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def startup(thisplayer)
|
|
13
|
+
# Setup its dealer, player and game associations
|
|
14
|
+
|
|
15
|
+
# Find the dealer record and associate it with this cardtable object
|
|
16
|
+
self.dealer_record = User.find(26)
|
|
17
|
+
# Update the number of game sessions for the dealer
|
|
18
|
+
self.dealer_record.num_sessions += 1
|
|
19
|
+
self.dealer_record.save
|
|
20
|
+
|
|
21
|
+
# Associate the cardtable player with the logged in user
|
|
22
|
+
self.player_record = thisplayer
|
|
23
|
+
# Update the user information regarding sessions
|
|
24
|
+
self.player_record.num_sessions += 1
|
|
25
|
+
self.player_record.save
|
|
26
|
+
|
|
27
|
+
# Set up a new game object and initialise it.
|
|
28
|
+
newgame
|
|
29
|
+
|
|
30
|
+
# Create a new shuffled desk of cards - we shuffle in case our logic always arranges
|
|
31
|
+
# the cards in the deck the same way.
|
|
32
|
+
self.deck = Deck.new
|
|
33
|
+
shuffle
|
|
34
|
+
|
|
35
|
+
# Create a new dealer object
|
|
36
|
+
self.dealer = Player.new
|
|
37
|
+
|
|
38
|
+
# Create a new player object
|
|
39
|
+
self.player = Player.new
|
|
40
|
+
|
|
41
|
+
# Set the bet to be 5 initially
|
|
42
|
+
self.bet = 5
|
|
43
|
+
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def newgame
|
|
47
|
+
|
|
48
|
+
# Create a new game object/record but don't save it just yet
|
|
49
|
+
# as the player may not actually decide to play a game in this session
|
|
50
|
+
@game = self.build_game
|
|
51
|
+
self.game = @game
|
|
52
|
+
|
|
53
|
+
# Initialize the game stats for this game
|
|
54
|
+
# We do this here for statistical purposes as it is legal for a session
|
|
55
|
+
# that has no games played
|
|
56
|
+
self.game.user = self.player_record
|
|
57
|
+
self.game.session_num = self.player_record.num_sessions
|
|
58
|
+
self.game.dealer_session_num = self.dealer_record.num_sessions
|
|
59
|
+
self.game.game_num, self.game.dealer_game_num = 0, 0
|
|
60
|
+
self.game.win = false
|
|
61
|
+
self.game.amount = 0
|
|
62
|
+
self.game.num_cards, self.game.dealer_num_cards = 0, 0
|
|
63
|
+
self.game.card_count, self.game.dealer_count = 0, 0
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def setBet(amount)
|
|
67
|
+
# Set the bet on the cardtable
|
|
68
|
+
self.bet = amount
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def deal
|
|
72
|
+
# This is the beginning of a new game
|
|
73
|
+
|
|
74
|
+
# Clear out any old game data to reset
|
|
75
|
+
if self.game.game_num > 0
|
|
76
|
+
|
|
77
|
+
# We need to call endGame to save the stats from teh previous game. This
|
|
78
|
+
# is necessary as the player could have hit 'stand' and the lost
|
|
79
|
+
# immediately to the dealer's 2 cards. In this case, the game will not
|
|
80
|
+
# have been saved.
|
|
81
|
+
endGame
|
|
82
|
+
|
|
83
|
+
# Reset the game stuff so it can be used later in the method
|
|
84
|
+
self.game.win = false
|
|
85
|
+
self.game.num_cards, self.game.dealer_num_cards = 0, 0
|
|
86
|
+
self.game.card_count, self.game.dealer_count = 0, 0
|
|
87
|
+
|
|
88
|
+
# Reset the player and dealer objects
|
|
89
|
+
self.player.reset
|
|
90
|
+
|
|
91
|
+
# If we don't make the second dealer card faceup, then this could be
|
|
92
|
+
# dealt to a player - not sure this is true really as cards are dealt from the deck
|
|
93
|
+
self.dealer.hand[1].faceup = true
|
|
94
|
+
self.dealer.reset
|
|
95
|
+
|
|
96
|
+
# Create a new game object and initialise it
|
|
97
|
+
newgame
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
# Deal 2 cards to both the dealer and the player
|
|
102
|
+
self.deck.deal(self.player)
|
|
103
|
+
self.deck.deal(self.player)
|
|
104
|
+
|
|
105
|
+
self.deck.deal(self.dealer)
|
|
106
|
+
self.deck.deal(self.dealer)
|
|
107
|
+
# The second card dealt to the dealer is face down
|
|
108
|
+
self.dealer.hand[1].faceup = false
|
|
109
|
+
|
|
110
|
+
# Update the number of games played by the player and dealer
|
|
111
|
+
self.player_record.games_played += 1
|
|
112
|
+
self.dealer_record.games_played += 1
|
|
113
|
+
|
|
114
|
+
# We subtract the bet from the player and add to the dealer
|
|
115
|
+
# This is in case the player exits the game before it completes
|
|
116
|
+
# When the game completes naturally, we will take this into consideration
|
|
117
|
+
# Think of this as the dealer holding the pot while the game is playing
|
|
118
|
+
self.player_record.balance -= self.bet
|
|
119
|
+
self.dealer_record.balance += self.bet
|
|
120
|
+
|
|
121
|
+
# Similarly, we assume that the player will lost and the dealer will win
|
|
122
|
+
# in case the game terminates unexpectedly
|
|
123
|
+
# Again, we will take this into consideration if the game ends normally
|
|
124
|
+
self.player_record.games_lost += 1
|
|
125
|
+
self.dealer_record.games_won += 1
|
|
126
|
+
|
|
127
|
+
# Update the games per session average for the player and dealer
|
|
128
|
+
avg = self.player_record.games_played / self.player_record.num_sessions
|
|
129
|
+
if avg < 1
|
|
130
|
+
self.player_record.games_per_session_avg = 1
|
|
131
|
+
else
|
|
132
|
+
self.player_record.games_per_session_avg = avg.floor
|
|
133
|
+
end
|
|
134
|
+
avg = self.dealer_record.games_played / self.dealer_record.num_sessions
|
|
135
|
+
if avg < 1
|
|
136
|
+
self.dealer_record.games_per_session_avg = 1
|
|
137
|
+
else
|
|
138
|
+
self.dealer_record.games_per_session_avg = avg.floor
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
self.player_record.save
|
|
142
|
+
self.dealer_record.save
|
|
143
|
+
|
|
144
|
+
# Update the game stats
|
|
145
|
+
self.game.game_num = self.player_record.games_played
|
|
146
|
+
self.game.dealer_game_num = self.dealer_record.games_played
|
|
147
|
+
|
|
148
|
+
self.game.amount = self.bet
|
|
149
|
+
|
|
150
|
+
# We will only update the players cards so that a 0 number of cards
|
|
151
|
+
# for the dealer signifies that the dealer won before playing his hand
|
|
152
|
+
self.game.num_cards = 2
|
|
153
|
+
self.game.card_count = self.player.score.handScore
|
|
154
|
+
|
|
155
|
+
self.game.save
|
|
156
|
+
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def hit(thisPlayer, numCards, cardCount)
|
|
160
|
+
# This is where the player gets another card
|
|
161
|
+
|
|
162
|
+
# Deal another card to the player
|
|
163
|
+
self.deck.deal(thisPlayer)
|
|
164
|
+
|
|
165
|
+
# Update the stats and save
|
|
166
|
+
if thisPlayer === self.player
|
|
167
|
+
self.game.num_cards += 1
|
|
168
|
+
self.game.card_count = thisPlayer.score.handScore
|
|
169
|
+
else
|
|
170
|
+
self.game.dealer_num_cards += 1
|
|
171
|
+
self.game.dealer_count = thisPlayer.score.handScore
|
|
172
|
+
end
|
|
173
|
+
self.game.save
|
|
174
|
+
|
|
175
|
+
# If the player is bust then we don't need to do anything - the stats have
|
|
176
|
+
# already been saved and the view will take care of starting a new game
|
|
177
|
+
|
|
178
|
+
# Try to rub salt into the player's wound by showing the
|
|
179
|
+
# dealer's hidden card if the player has bust.
|
|
180
|
+
if self.game.num_cards == 0
|
|
181
|
+
# It's not the dealer who has been hit with a card but the player
|
|
182
|
+
if self.game.card_count > 21
|
|
183
|
+
self.dealer.hand[1].faceup = true
|
|
184
|
+
endGame
|
|
185
|
+
end
|
|
186
|
+
elsif self.game.num_cards > 0
|
|
187
|
+
|
|
188
|
+
# if the dealer isn't winning yet...
|
|
189
|
+
if self.game.dealer_count < self.game.card_count
|
|
190
|
+
# Save the game as usual and wait for the next card to be dealt
|
|
191
|
+
self.game.save
|
|
192
|
+
elsif self.game.dealer_count > 21
|
|
193
|
+
# The dealer has bust
|
|
194
|
+
# Should we tidy up the stats to show that the player has won
|
|
195
|
+
# right here or leave that to 'endGame'?
|
|
196
|
+
endGame
|
|
197
|
+
else
|
|
198
|
+
# The dealer has beaten the player
|
|
199
|
+
endGame
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def stand
|
|
205
|
+
# This is where the player chooses to stand
|
|
206
|
+
|
|
207
|
+
# Give control to the dealer and play its hand
|
|
208
|
+
self.dealer.hand[1].faceup = true
|
|
209
|
+
|
|
210
|
+
self.game.dealer_num_cards = 2
|
|
211
|
+
self.game.dealer_count = self.dealer.score.handScore
|
|
212
|
+
self.game.save
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def double
|
|
216
|
+
# This is where the player chooses to double their initial bet
|
|
217
|
+
|
|
218
|
+
# Double the bet
|
|
219
|
+
self.setBet(self.bet * 2)
|
|
220
|
+
self.game.amount = self.bet
|
|
221
|
+
|
|
222
|
+
# Deal one more card only
|
|
223
|
+
hit(self.player, self.game.num_cards, self.game.card_count)
|
|
224
|
+
|
|
225
|
+
# If they're not bust, give control to the dealer and play its hand
|
|
226
|
+
if self.player.score.handScore < 22
|
|
227
|
+
stand
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
def shuffle
|
|
232
|
+
# Shuffle the deck
|
|
233
|
+
self.deck.shuffle
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
def endGame
|
|
237
|
+
|
|
238
|
+
# Call this function when we've determined that a game has finished.
|
|
239
|
+
# This will save the stats.
|
|
240
|
+
|
|
241
|
+
if self.game.dealer_count > 21
|
|
242
|
+
# If the dealer has gone bust, then the player has won. This is the
|
|
243
|
+
# only situation where the player wins. We have to reverse the stats
|
|
244
|
+
# where we assumed that the player would lose before saving the stats
|
|
245
|
+
|
|
246
|
+
# We add twice the bet to the player and subtract from the dealer
|
|
247
|
+
# This is because we already subtracted the bet from the player and
|
|
248
|
+
# added to the dealer at teh start of this game
|
|
249
|
+
self.player_record.balance += self.bet * 2
|
|
250
|
+
self.dealer_record.balance -= self.bet * 2
|
|
251
|
+
|
|
252
|
+
# Similarly, we assumed that the player lost and the dealer won
|
|
253
|
+
# So, we change this accordingly
|
|
254
|
+
self.player_record.games_lost -= 1
|
|
255
|
+
self.player_record.games_won += 1
|
|
256
|
+
self.dealer_record.games_won -= 1
|
|
257
|
+
self.dealer_record.games_lost += 1
|
|
258
|
+
|
|
259
|
+
self.game.win = true
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
# Save the game stats, and for the dealer and player
|
|
263
|
+
self.game.save
|
|
264
|
+
self.player_record.save
|
|
265
|
+
self.dealer_record.save
|
|
266
|
+
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
class Player
|
|
272
|
+
|
|
273
|
+
attr_accessor :hand, :isawinner, :score
|
|
274
|
+
|
|
275
|
+
def initialize
|
|
276
|
+
# Initially, each player has an empty hand of cards and they are assumed
|
|
277
|
+
# to be the loser of the game. This is in case the player kills the game
|
|
278
|
+
# before the end and that player is assumed to have lost.
|
|
279
|
+
self.hand = Array.new
|
|
280
|
+
self.isawinner = false
|
|
281
|
+
self.score = Array.new
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
def reset
|
|
285
|
+
# Blow away any old data and star again
|
|
286
|
+
self.hand.clear
|
|
287
|
+
self.score.clear
|
|
288
|
+
Player.new
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
class Card
|
|
294
|
+
RANKS = %w(2 3 4 5 6 7 8 9 10 J Q K A)
|
|
295
|
+
SCORES = %w(2 3 4 5 6 7 8 9 10 10 10 10 11)
|
|
296
|
+
SUITS = %w(Spades Clubs Hearts Diamonds)
|
|
297
|
+
|
|
298
|
+
attr_accessor :rank, :score, :suit, :image, :faceup
|
|
299
|
+
|
|
300
|
+
def initialize(index)
|
|
301
|
+
# Init the rank, suit, score and image of each card based on the
|
|
302
|
+
# argument passed in
|
|
303
|
+
self.rank = RANKS[index % 13]
|
|
304
|
+
self.score = SCORES[index % 13]
|
|
305
|
+
self.suit = SUITS[index % 4]
|
|
306
|
+
self.image = "/images/" + self.rank + self.suit + ".png"
|
|
307
|
+
|
|
308
|
+
# By default, each card is assumed to be dealt face up. When we start
|
|
309
|
+
# playing, only the second card of the dealer will be dealt face down.
|
|
310
|
+
# When it is the dealer's turn to play, this card will be set to be face up
|
|
311
|
+
self.faceup = true
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
class Deck
|
|
317
|
+
|
|
318
|
+
attr_accessor :cards, :nextcard
|
|
319
|
+
|
|
320
|
+
def initialize
|
|
321
|
+
# Create an array of 52 shuffled cards to be the deck
|
|
322
|
+
self.cards = (0..51).to_a.shuffle.collect { |index| Card.new(index) }
|
|
323
|
+
|
|
324
|
+
# Set the first card to be dealt as the card at index 0 in the deck
|
|
325
|
+
self.nextcard = 0
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
def deal(player)
|
|
329
|
+
# Add the next card in the deck to the player's hand
|
|
330
|
+
# We need to copy the card object from the deck before appending to the hand
|
|
331
|
+
# as it will be referenced otherwise. This can cause the deck card to be 'faceup = false'
|
|
332
|
+
# when we set this attribute for the dealer's second card. This will mean that when the
|
|
333
|
+
# deck loops around on itself, that card will be dealt facedown which is wrong!
|
|
334
|
+
player.hand << self.cards[self.nextcard].dup
|
|
335
|
+
player.score << self.cards[self.nextcard].score.to_i
|
|
336
|
+
|
|
337
|
+
# And then increment the position in the deck of the next card to be dealt
|
|
338
|
+
self.nextcard = (self.nextcard + 1) % 52
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
def shuffle
|
|
342
|
+
# Shuffle the cards in the deck and set the next card to be dealt
|
|
343
|
+
# as the card at index 0
|
|
344
|
+
self.cards = self.cards.shuffle
|
|
345
|
+
self.nextcard = 0
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
class Array
|
|
351
|
+
# Extend the Array class to create a handScore method
|
|
352
|
+
# It sorts the array so that the Aces are at the end. Aces are initially
|
|
353
|
+
# assumed to have a score of 11. Basically, if the score of the hand
|
|
354
|
+
# is > 21 and we're working with an Ace, make the ace 1 not 11. Also, if
|
|
355
|
+
# we encounter an Ace and we're not at the end of the hand, we take that
|
|
356
|
+
# into consideration by subtracting the value of the remaining Aces. Only
|
|
357
|
+
# really for the unlikely event we get a 10 an Ace and another Ace
|
|
358
|
+
|
|
359
|
+
def handScore
|
|
360
|
+
sort.each_with_index.inject(0){|sum, (current, i)| sum+current > 21-(length-(i+1)) && current==11 ? sum+1 : sum+current}
|
|
361
|
+
|
|
362
|
+
end
|
|
363
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: blackjack
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
hash: 29
|
|
5
|
+
prerelease: false
|
|
6
|
+
segments:
|
|
7
|
+
- 0
|
|
8
|
+
- 0
|
|
9
|
+
- 1
|
|
10
|
+
version: 0.0.1
|
|
11
|
+
platform: ruby
|
|
12
|
+
authors:
|
|
13
|
+
- Tony O'Keeffe
|
|
14
|
+
autorequire:
|
|
15
|
+
bindir: bin
|
|
16
|
+
cert_chain: []
|
|
17
|
+
|
|
18
|
+
date: 2011-12-17 00:00:00 +00:00
|
|
19
|
+
default_executable:
|
|
20
|
+
dependencies: []
|
|
21
|
+
|
|
22
|
+
description: Gem creation as part of WAF project
|
|
23
|
+
email: tony.okeeffe@student.ncirl.ie
|
|
24
|
+
executables: []
|
|
25
|
+
|
|
26
|
+
extensions: []
|
|
27
|
+
|
|
28
|
+
extra_rdoc_files: []
|
|
29
|
+
|
|
30
|
+
files:
|
|
31
|
+
- lib/cardtable.rb
|
|
32
|
+
has_rdoc: true
|
|
33
|
+
homepage:
|
|
34
|
+
licenses: []
|
|
35
|
+
|
|
36
|
+
post_install_message:
|
|
37
|
+
rdoc_options: []
|
|
38
|
+
|
|
39
|
+
require_paths:
|
|
40
|
+
- lib
|
|
41
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
42
|
+
none: false
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
hash: 3
|
|
47
|
+
segments:
|
|
48
|
+
- 0
|
|
49
|
+
version: "0"
|
|
50
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
51
|
+
none: false
|
|
52
|
+
requirements:
|
|
53
|
+
- - ">="
|
|
54
|
+
- !ruby/object:Gem::Version
|
|
55
|
+
hash: 3
|
|
56
|
+
segments:
|
|
57
|
+
- 0
|
|
58
|
+
version: "0"
|
|
59
|
+
requirements: []
|
|
60
|
+
|
|
61
|
+
rubyforge_project:
|
|
62
|
+
rubygems_version: 1.3.7
|
|
63
|
+
signing_key:
|
|
64
|
+
specification_version: 3
|
|
65
|
+
summary: Simple solo player blackjack game
|
|
66
|
+
test_files: []
|
|
67
|
+
|