001 /*
002 Copyright (c) 2005 Garrett Smith
003 The MIT License
004
005 Permission is hereby granted, free of charge, to any person obtaining a copy
006 of this software and associated documentation files (the "Software"), to deal
007 in the Software without restriction, including without limitation the rights
008 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
009 copies of the Software, and to permit persons to whom the Software is
010 furnished to do so, subject to the following conditions:
011
012 The above copyright notice and this permission notice shall be included in all
013 copies or substantial portions of the Software.
014
015 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
016 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
017 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
018 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
019 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
020 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
021 THE SOFTWARE.
022 */
023
024 // $Id: Blackjack.java.html,v 1.1 2005/06/14 06:50:55 gsmith Exp $
025
026 package garrettsmith.blackjack;
027
028 import java.util.*;
029 import garrettsmith.playingcards.*;
030
031 /**
032 * <p>
033 * Provides for playing a game of blackjack.
034 * </p>
035 *
036 * @author Garrett Smith, gsmith at northwestern dot edu
037 * @version Blackjack v1.0
038 * @since Blackjack v1.0
039 */
040 public class Blackjack {
041
042 private CardContainer _cards = null;
043 private CardList _dealerCards = new CardList();
044 private Rules _rules = null;
045
046 private ArrayList _handsToPlay = new ArrayList();
047 private ArrayList _handsToEvaluate = new ArrayList();
048
049 /**
050 * Creates a new Blackjack game simulator with the default {@link Rules}.
051 */
052 public
053 Blackjack() {
054 this( new Rules() );
055 }
056
057 /**
058 * Creates a new Blackjack simulator with the specified {@link Rules}.
059 *
060 * @param rules specifying which rules this simulator will follow
061 */
062 public
063 Blackjack( final Rules rules ) {
064 setRules( rules );
065 _cards = new CardContainer( StandardDeck.DECK,
066 rules.getNumberOfDecks() );
067 _cards.setAutoReset( rules.getShuffleLimit() );
068 }
069
070 /**
071 * Calculates the highest possible value for a list of cards without
072 * busting, if possible.
073 */
074 public static
075 int calculateBestValue( final CardList cards ) {
076 int value = 0;
077 int numAces = 0;
078 Card card;
079 for ( int i = 0; i < cards.size(); i++ ) {
080 card = cards.getCard( i );
081 if ( Card.Value.ACE.equals( card.getValue() ) ) {
082 value += 11;
083 numAces++;
084 }
085 else {
086 value += Blackjack.calculateValue( card );
087 }
088 }
089 while ( value > 21 ) {
090 if ( numAces == 0 ) return value;
091 value -= 10;
092 numAces--;
093 }
094 return value;
095 }
096
097 /**
098 * Accepts a {@link CardList} and returns its contents as a human-readable String.
099 */
100 static
101 String cardsToString( final CardList cards ) {
102 StringBuffer b = new StringBuffer();
103 b.append( "Cards[" );
104 for ( int i = 0; i < cards.size(); i++ ) {
105 b.append( cardToString( cards.getCard( i ) ) );
106 if ( i != cards.size() - 1 ) b.append( "," );
107 }
108 b.append( "]" );
109 return b.toString();
110 }
111
112 /**
113 * Accepts a {@link Card} and returns its contents as a human-readable String.
114 */
115 static String cardToString(final Card card) {
116 return card.getValue().toString();
117 }
118
119 /**
120 * Calculates the value of a playing card according to the rules of
121 * Blackjack; assumes all aces are worth 1.
122 *
123 * @return the value of card assuming an ace is worth 1
124 */
125 public static
126 int calculateValue( final Card card ) {
127 final Card.Value value = card.getValue();
128 if ( Card.Value.ACE.equals( value ) ) {
129 return 1;
130 }
131 else if ( isNonAceFaceCard( card ) ) {
132 return 10;
133 }
134 else if ( Card.Value.NINE.equals( value ) ) {
135 return 9;
136 }
137 else if ( Card.Value.EIGHT.equals( value ) ) {
138 return 8;
139 }
140 else if ( Card.Value.SEVEN.equals( value ) ) {
141 return 7;
142 }
143 else if ( Card.Value.SIX.equals( value ) ) {
144 return 6;
145 }
146 else if ( Card.Value.FIVE.equals( value ) ) {
147 return 5;
148 }
149 else if ( Card.Value.FOUR.equals( value ) ) {
150 return 4;
151 }
152 else if ( Card.Value.THREE.equals( value ) ) {
153 return 3;
154 }
155 else if ( Card.Value.TWO.equals( value ) ) {
156 return 2;
157 }
158 throw new IllegalArgumentException();
159 }
160
161 /**
162 * Calculates the value of a list of cards according to the rules of
163 * Blackjack; assumes all aces are worth 1.
164 */
165 public static
166 int calculateValue( final CardList cards ) {
167 int value = 0;
168 for ( int i = 0; i < cards.size(); i++ ) {
169 value += Blackjack.calculateValue( cards.getCard( i ) );
170 }
171 return value;
172 }
173
174 /**
175 * Returns the current {@link Rules} being used.
176 */
177 public Rules getRules() {
178 return _rules;
179 }
180
181 /**
182 * Returns whether the card passed in is a non-Ace face card: a ten, jack, queen, or king.
183 */
184 public static
185 boolean isNonAceFaceCard( final Card card ) {
186 final Card.Value value = card.getValue();
187 return ( Card.Value.KING.equals( value )
188 || Card.Value.QUEEN.equals( value )
189 || Card.Value.JACK.equals( value )
190 || Card.Value.TEN.equals( value ) );
191 }
192
193 /**
194 * Returns whether the list of cards passed in represents a blackjack. This method doesn't take
195 * into consideration whether these are the first cards dealt or the result of a split; in the
196 * latter case the cards are not a blackjack.
197 */
198 public static
199 boolean isBlackjack( final CardList cards ) {
200 if ( cards.size() != 2 ) return false;
201 for ( int i = 0; i < 2; i++ ) {
202 if ( Card.Value.ACE.equals( cards.getCard( i ).getValue() ) ) {
203 CardList cardsCopy = ( CardList ) cards.clone();
204 cardsCopy.remove( i );
205 final Card.Value otherCardValue = cardsCopy.getCard( 0 ).getValue();
206 return ( Card.Value.KING.equals( otherCardValue )
207 || Card.Value.QUEEN.equals( otherCardValue )
208 || Card.Value.JACK.equals( otherCardValue )
209 || Card.Value.TEN.equals( otherCardValue ) );
210 }
211 }
212 return false;
213 }
214
215 /**
216 * Returns whether the list of cards is a soft hand. That is, the list of cards
217 * contain an ace whose value could be 1 or 11 without exceeding 21.
218 */
219 public static
220 boolean isSoft( final CardList cards ) {
221 boolean hasAce = false;
222 for ( int i = 0; i < cards.size(); i++ ) {
223 if ( Card.Value.ACE.equals( cards.getCard( i ).getValue() ) ) {
224 hasAce = true;
225 break;
226 }
227 }
228 return hasAce && calculateValue( cards ) < 12;
229 }
230
231 /**
232 * Plays one game of blackjack betting <code>wager</code> and using <code>handler</code>
233 * to determine how the player's hand is played.
234 */
235 public
236 void playGame( final EventHandler handler, final double wager ) {
237 try {
238 Hand hand = dealInitialCards(wager);
239 if ( offerEarlySurrender( hand, handler ) ) return;
240 if ( isBlackjack( hand, handler ) ) return;
241 while ( !_handsToPlay.isEmpty() ) {
242 hand = ( Hand ) _handsToPlay.remove( 0 );
243 while ( hand.isInPlay() ) {
244 playTurn(handler, hand);
245 }
246 if ( hand.isEvaluationNeeded() ) _handsToEvaluate.add( hand );
247 }
248 if ( isDealerPlayNeeded( _handsToEvaluate ) ) {
249 evaluateOutcome( playDealerHand(), handler );
250 }
251 }
252 catch( RuntimeException e ) {
253 handler.fatalErrorOccurred( e );
254 }
255 }
256
257 /**
258 * Sets the rules used to determine how the game is handled.
259 */
260 public
261 void setRules( final Rules rules ) {
262 _rules = rules;
263 }
264
265 Card getDealerCard() {
266 return _dealerCards.getCard( 1 );
267 }
268
269 Card getCard()
270 throws NoMoreCardsException {
271 return _cards.getCard();
272 }
273
274 void setCards( CardContainer cards ) {
275 _cards = cards;
276 }
277
278 private boolean isDealerPlayNeeded( final ArrayList hands ) {
279 for ( int i = 0; i < hands.size(); i++ ) {
280 if ( ((Hand) hands.get( i )).isDealerPlayNeeded() ) return true;
281 }
282 return false;
283 }
284
285 private void evaluateOutcome( final int dealerValue, final EventHandler handler ) {
286 while ( !_handsToEvaluate.isEmpty() ) {
287 final Hand hand = ( Hand ) _handsToEvaluate.remove( 0 );
288 int playerValue = hand.getBestValue();
289 if ( hand.isBusted() ) {
290 handler.handFinished( hand,
291 -1 * hand.getWager(),
292 Result.BUSTED,
293 _dealerCards );
294 }
295 else if ( dealerValue > 21 ) {
296 handler.handFinished( hand,
297 hand.getWager(),
298 Result.DEALER_BUSTED,
299 _dealerCards );
300 }
301 else if ( dealerValue > playerValue ) {
302 handler.handFinished( hand,
303 -1 * hand.getWager(),
304 Result.LOSE,
305 _dealerCards );
306 }
307 else if ( playerValue > dealerValue ) {
308 handler.handFinished( hand,
309 hand.getWager(),
310 Result.WIN,
311 _dealerCards );
312 }
313 else if ( playerValue == dealerValue ) {
314 handler.handFinished( hand, 0, Result.PUSH, _dealerCards);
315 }
316 else {
317 throw new IllegalStateException();
318 }
319 }
320 }
321
322 private boolean isBlackjack( final Hand hand, final EventHandler handler ) {
323 final boolean playerHasBlackjack = isBlackjack( hand.getCards() );
324 final boolean dealerHasBlackjack = isBlackjack( _dealerCards );
325 if ( dealerHasBlackjack && playerHasBlackjack ) {
326 handler.handFinished( hand,
327 0.0,
328 Result.BLACKJACK_PUSH,
329 _dealerCards );
330 return true;
331 }
332 else if ( dealerHasBlackjack && ! playerHasBlackjack ) {
333 handler.handFinished( hand,
334 -1 * hand.getWager(),
335 Result.DEALER_BLACKJACK,
336 _dealerCards );
337 return true;
338 }
339 else if ( playerHasBlackjack & ! dealerHasBlackjack ) {
340 handler.handFinished( hand,
341 _rules.getBlackjackPayoff() * hand.getWager(),
342 Result.BLACKJACK,
343 _dealerCards );
344 return true;
345 }
346 return false;
347 }
348
349 private boolean offerEarlySurrender( final Hand hand, final EventHandler handler ) {
350 if ( _rules.canSurrenderEarly()
351 && handler.offerEarlySurrender( hand ) ) {
352 handler.handFinished( hand,
353 -0.5 * hand.getWager(),
354 Result.EARLY_SURRENDER,
355 _dealerCards );
356 return true;
357 }
358 return false;
359 }
360
361 private Hand dealInitialCards(final double wager) {
362 _handsToEvaluate.clear();
363 _handsToPlay.clear();
364 _dealerCards.clear();
365 CardList cards = new CardList();
366 cards.add( getCard() );
367 _dealerCards.add( getCard() );
368 cards.add( getCard() );
369 _dealerCards.add( getCard() );
370 Hand hand = new Hand( wager, this, cards );
371 _handsToPlay.add( hand );
372 return hand;
373 }
374
375 private
376 int playDealerHand() {
377 int value;
378 while ( true ) {
379 value = calculateBestValue( _dealerCards );
380 if ( value < 17 ) {
381 _dealerCards.add( getCard() );
382 }
383 else if ( value > 17 ) {
384 return value;
385 }
386 else if ( value == 17 && !isSoft( _dealerCards ) ) {
387 return 17;
388 }
389 else if ( value == 17
390 && _rules.doesDealerStandOnSoft17()
391 && isSoft( _dealerCards ) ) {
392 return 17;
393 }
394 else if ( value == 17
395 && !_rules.doesDealerStandOnSoft17()
396 && isSoft( _dealerCards ) ) {
397 _dealerCards.add( getCard() );
398 }
399 else {
400 throw new IllegalStateException();
401 }
402 }
403 }
404
405 private void playTurn(final EventHandler handler, final Hand hand) {
406 Move move = handler.offerRegularTurn( hand );
407 if ( move == null )
408 handler.fatalErrorOccurred( new NullPointerException( "null move returned" ) );
409 Hand newHand = move.execute( hand, handler, _dealerCards );
410 if ( newHand != null ) _handsToPlay.add( newHand );
411 }
412
413 } // class Blackjack
|