TweetFollow Us on Twitter

Sep 98 Prog Challenge

Volume Number: 14 (1998)
Issue Number: 9
Column Tag: Programmer's Challenge

Sep 98 Programmer's Challenge

by Bob Boonstra, Westford, MA

Big Baby

Fifty years ago this past June, the Manchester Mark I prototype computer, also known as "Baby", became operational. Baby was the first computer to store a program electronically, and was also the first computer to store instructions and data in the same memory. Because vacuum tube technology was too immature to store memory reliably, Baby was designed to test memory based on a cathode ray tube. Not much memory, mind you. Baby boasted a full 1K bits of memory, organized as 32 words (or lines) of 32 bits each.

In celebration of the birth of the first stored program computer on June 21, 1948, the Department of Computer Science at the University of Manchester recently reconstructed Baby and ran a programming contest to write the most imaginative program for Baby. Inspired by that contest, your Challenge is to write an assembler and an emulator for an extended ("Big") version of Baby. The prototype for the code you should write is:

#if defined(__cplusplus)
pragma extern "C" {
#endif

#define kMaxInstructions 32

typedef UInt32 CRT_memory[kMaxInstructions];

pascal void AssembleBabyProgram(
   char *program,
   CRT_memory memory,
   UInt32 address_bits
);

pascal void ExecuteBabyProgram(
   CRT_memory memory,
   UInt32 address_bits
);

#if defined(__cplusplus)
}
#endif

Baby has a single general-purpose register, called the Accumulator. The program counter is called the Control Instruction, or CI. The CI is incremented just before the next instruction is fetched, which means that a jump instruction, for example, is coded with a value one less than the actual target address. Baby also has a red light that indicates the program has halted. One interesting thing about Baby is that it lacks an addition instruction - addition is done by subtraction.

Baby's instruction repertoire is listed below. The function bits (or opcode) associated with each instruction is listed in parentheses after the mnemonic.

STO (110)
Store the contents of the Accumulator in the store line.
SUB (001 or 101)
Subtract the contents of the store line from the Accumulator. There is no ADD instruction; addition is done indirectly by combining the SUB and the LDN instruction.
LDN (010)
Copy the contents of the store line, negated, to the accumulator.
JMP (000)
Copy the contents of the store line to the CI (so the store line holds the number of the line one before we want to jump to). In modern terms, this an indirect jump, which uses up an extra store line compared to a direct jump.
JRP (100)
Add the contents of the store line to the CI. This looks forward to larger machines, where it would be important to be able to load the same code in different places, and hence would need relative jumps.
CMP (011)
Skip the next instruction if the contents of the Accumulator are negative, i.e. a conditional branch.
STOP (111)
Stop the machine and turn the red light on
NUM (N/A)
An assembler mnemonic to initialize a store line to a data value.

For example, the following program computes the greatest common divisor of the number in locations 30 and 31:

22
0000 NUM 0
0001 LDN 30
0002 STO 29
0003 LDN 31
0004 STO 31
0005 LDN 31
0006 STO 30
0007 LDN 29
0008 SUB 30
0009 CMP
0010 JRP 27
0011 SUB 31
0012 STO 31
0013 SUB 28
0014 CMP
0015 JMP 00
0016 STP
0027 NUM -3
0028 NUM 2
0029 NUM 0
0030 NUM 3141593
0031 NUM 5214

Baby's instructions are assembled into a 32 bit word by placing the function code associated with the mnemonic into bits 13-15 (numbered with bit 0 as the most significant bit). In the original Baby, the store line associated with the instruction is placed in bits 0-4. Bits 5-12 and 16-31 are not used as part of the instruction, although they can be used as data. The program listed above assembles to the following:

22
0000:00000000000000000000000000000000
0001:01111000000000100000000000000000
0002:10111000000001100000000000000000
0003:11111000000000100000000000000000
0004:11111000000001100000000000000000
0005:11111000000000100000000000000000
0006:01111000000001100000000000000000
0007:10111000000000100000000000000000
0008:01111000000000010000000000000000
0009:00000000000000110000000000000000
0010:11011000000001000000000000000000
0011:11111000000000010000000000000000
0012:11111000000001100000000000000000
0013:00111000000000010000000000000000
0014:00000000000000110000000000000000
0015:00000000000000000000000000000000
0016:00000000000001110000000000000000
0027:10111111111111111111111111111111
0028:01000000000000000000000000000000
0029:00000000000000000000000000000000
0030:10011011111101111111010000000000
0031:01111010001010000000000000000000

Our contest will make one change to the original Baby: in our extended, Big Baby, machine, the store line is extended from 5 bits (0-4) to address_bits bits (0 - address_bits-1). This allows more than 32 words of memory and therefore larger programs.

Your AssembleBabyProgram routine should accept the mnemonic input listed above, pointed to by the program parameter, and assemble them into 32-bit Baby instructions in memory. Your ExecuteBabyProgram routine will be called to execute the program one or more times. Both of your routines will be provided an address_bits parameter that describes the size of memory. You will be asked to assemble more than one program, your assembled programs may be executed more than one time each, and you may be asked to execute a program that has been hand-assembled.

More information about the University of Manchester Baby programming contest can be found at http://www.cs.man.ac.uk/prog98/. Programming reference documentation for Baby can be found at http://www.cs.man.ac.uk/prog98/ssemref.html and at ftp://ftp.cs.man.ac.uk/pub/CCS-Archive/misc/progref1.doc.

The winner will be the solution that assembles and executes a set of test programs in the minimum amount of time.

This will be a native PowerPC Challenge, using the latest CodeWarrior environment. Solutions may be coded in C, C++, Pascal or, as is our tradition in the month of September, in assembly language. Thanks to Eric Shapiro for suggesting this Challenge.

Three Months Ago Winner

Congratulations to Tom Saxton for writing the most successful simulated gambler at the blackjack table of our June Programmer's Challenge Casino. Tom beat out four other entries and was one of only two entries to actually come out ahead at the blackjack table.

Tom precomputed the expected winnings for each situation and created tables with the action that led to the best result. He uses the Hi-Lo card counting method to determine whether the remaining cards contain a disproportionate number of high-valued cards, and then uses that estimate to adjust his wager. Tom's solution is also not too greedy; it contains heuristics to quit when it has won a reasonable amount or played long enough, ensuring that it has wagered enough credits to avoid the "freeloader" penalty imposed by the problem.

A few words about our other gamblers are in order. The second-place solution, by Kevin Hewitt, also used precomputed tables, but his were based only on the initial pair of cards dealt. Kevin also spent more time at the table, quitting only when winnings or losses exceeded a threshold. JG Heithcock's solution spent the least amount of time at the table. He quit soon after the minimum total bet criterion was met. Ken Slezak kept playing until he lost 75% of his bankroll (or quadrupled his money) and Randy Boring played until he ran out of money or, as it turned out, until the house threw him out of the casino. Both of those players left with not much more than the shirts on their backs.

Here are the statistics for the entries to the Blackjack Challenge. Each player played a series of five games where the house varied the number of decks of cards used. Players were given the same number of credits at the start of each game, totaling 21000 credits for all of the games. The table below lists the total number of credits wagered by the player, the number of credits left when the player decided to quit, the number of hands played, total execution time, and the overall player score. Also listed are the code and data sizes for the entries, along with the programming language used. As usual, the number in parentheses after the entrant's name is the total number of Challenge points earned in all Challenges to date prior to this one.

Name Credits Wagered Credits Left Hands Played Exec. Time Score Code Size Data Size Lang
Tom Saxton (19) 47451 25199 327 7169 25194 1496 1924 C
Kevin Hewitt 438700 23800 1833 37923 23766 996 2156 C
JG Heithcock (20) 22616 20484 769 17950 20470 1304 232 C
Ken Slezak (20) 91760 9140 1701 36911 9106 1240 172 C
Randy Boring (81) 437230 8670 15425 460099 8213 4920 353 C

Top Contestants

Here are the Top Contestants for the Programmer's Challenge, including everyone who has accumulated more than 20 points during the past two years. The numbers below include points awarded over the 24 most recent contests, including points earned by this month's entrants.

  1. Munter, Ernst 190
  2. Boring, Randy 76
  3. Cooper, Greg 54
  4. Mallett, Jeff 50
  5. Rieken, Willeke 47
  6. Nicolle, Ludovic 34
  7. Lewis, Peter 31
  8. Maurer, Sebastian 30
  9. Saxton, Tom 29
  10. Heithcock, JG 27
  11. Gregg, Xan 24
  12. Murphy, ACC 24
  13. Hart, Alan 21
  14. Antoniewicz, Andy 20
  15. Day, Mark 20
  16. Higgins, Charles 20
  17. Hostetter, Mat 20
  18. Studer, Thomas 20

There are three ways to earn points: (1) scoring in the top 5 of any Challenge, (2) being the first person to find a bug in a published winning solution or, (3) being the first person to suggest a Challenge that I use. The points you can win are:

  • 1st place 20 points
  • 2nd place 10 points
  • 3rd place 7 points
  • 4th place 4 points
  • 5th place 2 points
  • Finding bug 2 points
  • Suggesting Challenge 2 points

Here is Tom's winning solution:

Player.c
Copyright © 1998 Tom Saxton

#include "BlackJack.h"

// Naming Conventions:
//
// Without getting into the gory details of the Hungarian naming convention,
// here are some common prefixes and their meanings:
//
//      a       array
//      p       pointer
//      c       count
//      mp      map (one data type to another)
//      i       index
//
// The prefixes modify a base type. So, if FOO is a base type (like a struct,
// or an enum), the following declarations illustrate the above prefixes:
//
//      FOO     foo;
//      FOO *   pfoo;
//      FOO     afoo[10];
//      int     ifoo; // an index into an array of FOOs
//      int     cfoo; // a count of FOOs.
//
//      for (ifoo = 0; ifoo < cfoo; ++ifoo)
//         pfoo = &afoo[ifoo];
//

enum { fFalse = 0, fTrue = 1 };
#define DIM(a) (sizeof(a)/sizeof((a)[0]))

// Be sure to enable this define to pick up a couple of post-deadline bug fixes.
//
// #define BUGFIX

// disable debug code
#define Assert(f)

// The following tables determine the actions for all possible hands,
// divided into three groups: pairs, soft hands and hard hands, considered
// in that order. (A pair of aces is treated as a pair, not as a soft hand.)
//
// The tables were computed by taking the Dealer's up card and assuming
// a huge shoe with an even card distribution finding the probability
// for each of the possible final dealers scores (bust, 17, 18, 19, 20 and 21).
//
// Then, given that table, I computed the expected earnings (win, lose or
// push) for each of the possible actions, and recorded the action with
// the best result.
//
// I found the book "Best Blackjack" by Frank Scoblete (c) 1996 to be
// helpful, and my tables are close to his multi-deck tables.
// I modeled an infinite, evenly distributed shoe, he may have modeled
// a fixed number of decks.

// macros to make these tables manageable...
#define H kHitMe
#define D kDoubleDownAndHitMe
#define S kStandPat
#define X kSplitAndHitMe
#define B kClaimBlackjack

// For "hard" hands (no Aces scored as 11), plug in the dealer's up card
// (minus 1) and the hand's score to find the next action. If this isn't the
// first action of the hand, treat kDoubleDownAndHitMe as kHitMe.
Action mp_spot_score_actionHard[10][22] = 
{
//      0   - 21
   { H,H,H,H,H,H,H,H,H,H,H,H,H,H,H,H,H,S,S,S,S,S },   /*  A */
   { H,H,H,H,H,H,H,H,H,D,D,D,H,S,S,S,S,S,S,S,S,S },   /*  2 */
   { H,H,H,H,H,H,H,H,H,D,D,D,H,S,S,S,S,S,S,S,S,S },   /*  3 */
   { H,H,H,H,H,H,H,H,H,D,D,D,S,S,S,S,S,S,S,S,S,S },   /*  4 */
   { H,H,H,H,H,H,H,H,H,D,D,D,S,S,S,S,S,S,S,S,S,S },   /*  5 */
   { H,H,H,H,H,H,H,H,D,D,D,D,S,S,S,S,S,S,S,S,S,S },   /*  6 */
   { H,H,H,H,H,H,H,H,H,D,D,D,H,H,H,H,H,S,S,S,S,S },   /*  7 */
   { H,H,H,H,H,H,H,H,H,H,D,D,H,H,H,H,H,S,S,S,S,S },   /*  8 */
   { H,H,H,H,H,H,H,H,H,H,D,D,H,H,H,H,H,S,S,S,S,S },   /*  9 */
   { H,H,H,H,H,H,H,H,H,H,H,D,H,H,H,H,H,S,S,S,S,S }    /*  10 */
};

// For "soft" hands (at least one Ace used as an 11), plug in the dealer's up card
// (minus 1) and the hand's "other" card (or combined score without the ace)
// to find the next action.
Action mp_spot_spot_actionSoft[10][10] = 
{
   { H, H, H, H, H, H, S, S, S, B },   /*  A */
   { H, H, H, H, H, D, S, S, S, B },   /*  2 */
   { H, H, H, H, H, D, D, S, S, B },   /*  3 */
   { H, H, H, H, D, D, D, S, S, B },   /*  4 */
   { H, H, H, D, D, D, D, S, S, B },   /*  5 */
   { H, H, H, D, D, D, D, S, S, B },   /*  6 */
   { H, H, H, H, H, D, S, S, S, B },   /*  7 */
   { H, H, H, H, H, H, S, S, S, B },   /*  8 */
   { H, H, H, H, H, H, H, S, S, B },   /*  9 */
   { H, H, H, H, H, H, H, S, S, B }    /* 10 */
};

// If dealt a pair, plug in the dealer's up card (minus 1) and the spot
// value of the pair (minus 1) to find the suggested action.
Action mp_spot_spot_actionPair[10][10] = 
{
   { X, H, H, H, H, H, H, H, S, S },   /*  A */
   { X, X, X, H, D, H, X, X, X, S },   /*  2 */
   { X, X, X, H, D, H, X, X, X, S },   /*  3 */
   { X, X, X, H, D, X, X, X, X, S },   /*  4 */
   { X, X, X, H, D, X, X, X, X, S },   /*  5 */
   { X, X, X, D, D, X, X, X, X, S },   /*  6 */
   { X, X, X, H, D, H, X, X, S, S },   /*  7 */
   { X, X, X, H, D, H, H, X, X, S },   /*  8 */
   { X, X, H, H, D, H, H, X, X, S },   /*  9 */
   { X, H, H, H, H, H, H, H, S, S }    /*  10 */
};

// undefine shortcuts used in the above tables
#undef H
#undef D
#undef S
#undef X
#undef B

// Score bust as zero.
#define scoreBust            0
// The Player's hand is limited to five cards.
#define ccardMaxPlayer       5
// The dealer can never draw more than nine cards
// (nine 2's for example).
#define ccardMaxDealer      10

// Entry in the SPOT table, used for scoring and printing card values
typedef struct ESPOT
{
   char   score;
   char   sz[3];
} ESPOT;

static const ESPOT s_dnspot[kKing+1] = 
{
   {  0, "?" },
   {  1, "A" },
   {  2, "2" },
   {  3, "3" },
   {  4, "4" },
   {  5, "5" },
   {  6, "6" },
   {  7, "7" },
   {  8, "8" },
   {  9, "9" },
   { 10, "10" },
   { 10, "J" },
   { 10, "Q" },
   { 10, "K" },
};

// game statistics...
static int s_cdeck;
static int s_ccreditStart;
static int s_ccreditMinBet;
static int s_ccreditMidBet;
static int s_ccreditMaxBet;
static int s_ccreditBalance;
static int s_ccreditTotalBet;

// callback functions
static BetProc *s_pfnMakeABet;
static HitProc *s_pfnHitMe;

// function to score a hand
static int _ScoreHand(const Card acard[], int ccard,
                              int *pcAce);

// struct for counting cards...
typedef struct DECK
{
   int acspot[10+1];
   int cspotStart;
   int cspotRemain;
   int dcount;
   int fInfinite;
} DECK;
static DECK s_deck;
// calls to reset card counters and count the cards in a hand
void _InitDeck(int cdeck, int fInfinite);
void _CountCards(const Card acard[], int ccard);
// call to compute the proper action given the player's hand and
// the dealer's up card, and whether or not this is the first action
static Action _ActionLookupHand(Spot spotDealer, Card acard[], int ccard, int fFirst);

InitBlackjack
// Call to start a game
void InitBlackjack(
   int numDecks,       /* number of decks used by the dealer, 2..10*/
   int yourBankroll,   /* number of credits you have to start */
   int minBet,         /* minimum bet for each hand */
   int maxBet,         /* maximum bet for each hand */
   BetProc pfnMakeABet,/* callback to place a wager */
   HitProc pfnHitMe    /* callback to get a card */
)
{
   s_cdeck             = numDecks;
   s_ccreditStart      = yourBankroll;
   s_ccreditMinBet     = minBet;
   s_ccreditMaxBet     = maxBet;
   s_pfnMakeABet       = pfnMakeABet;
   s_pfnHitMe          = pfnHitMe;

   s_ccreditTotalBet   = 0;
   s_ccreditBalance    = s_ccreditStart;
}

Blackjack
// Call to play a hand
Boolean Blackjack(Boolean fNewDeck)
{
   int      ccreditBet;
   Card     acardPlayer[ccardMaxPlayer], acardDealer[ccardMaxDealer];
   int      ccardPlayer, ccardDealer;
   Action   actionFirst;
   int      ccreditWin;
   Spot     spotDealer;
   int      ihand, chand;
   int      count;
   Result   result;
   
   if (fNewDeck)
      _InitDeck(s_cdeck, fFalse /*fInfinite*/);
   // normalize the card count. A positive count means that the shoe is
   // heavy in large cards, which makes it more likely for the dealer to
   // bust. A negative count means that the shoe is heavy in small cards,
   // which makes it less likely that the dealer with bust.
   count = (s_deck.dcount*52)/s_deck.cspotRemain;
   // Make a bet that is proportional to our current balance, so that losing
   // streaks don't clean us out, and winning streaks rake in extra chips.
   // Bet more when the count is high, less when it's low, but stay within
   // the stated betting limits.
   ccreditBet = (count+2)*s_ccreditBalance/50;
   if (ccreditBet < s_ccreditMinBet)
      ccreditBet = s_ccreditMinBet;
   else if (ccreditBet > s_ccreditMaxBet)
      ccreditBet = s_ccreditMaxBet;
   // Place bet, get some cards
   (*s_pfnMakeABet)(ccreditBet, acardPlayer, acardDealer);
   ccardPlayer = ccardDealer = 2;
   // store and normalize the dealer's up card
   spotDealer = acardDealer[1].spot;
   if (spotDealer > k10)
      spotDealer = k10;
   // get the first action for the hand
   actionFirst = 
            _ActionLookupHand(spotDealer, acardPlayer, 2, fTrue);
   if (actionFirst == kDoubleDownAndHitMe)
      ccreditBet *= 2;
   chand = (actionFirst == kSplitAndHitMe) ? 2 : 1;
   // play out the hand(s) (there are two hands if we kSplitAndHitMe)
   for (ihand = 0; ihand < chand; ++ihand)
   {
      Boolean   fInsurance;
      Action action = actionFirst;
      // take the "insurance" side bet when the dealer shows an Ace and there is
      // a better than one third chance of the dealer having a 10 for the other card.
      fInsurance = (spotDealer == kAce) && 
                        (3*s_deck.acspot[k10] > s_deck.cspotRemain);
      for(;;)
      {
         // play out an action
         result = 
               (*s_pfnHitMe)(action, fInsurance, acardPlayer, 
                           &ccardPlayer, acardDealer, &ccardDealer, 
                                 &ccreditWin);
         if (result != kNoResult)
            break;
         
         // If we didn't kStandPat or bust, calculate the next action
         action = _
         ActionLookupHand(spotDealer, acardPlayer, ccardPlayer, 
                                                fFalse);
      }
      
      // count the cards shown in this hand
      _CountCards(acardPlayer, ccardPlayer);
      if (ihand == chand-1)
         _CountCards(acardDealer, ccardDealer);
      
      // tally our win/loss
      s_ccreditBalance += ccreditWin;
      s_ccreditTotalBet += ccreditBet;
   }

   // If we have lost most of our money, quit
   if (s_ccreditBalance < s_ccreditStart/3)
   {
      return fFalse;
   }
   // If we have won a lot, and will avoid the freeloader penalty, quit
   if (s_ccreditBalance > 7*s_ccreditStart/4 && 
            s_ccreditTotalBet > s_ccreditStart)
   {
      return fFalse;
   }
   // If we have won some, and played for twice the freeloader requirement, quit
   if (s_ccreditBalance > 5*s_ccreditStart/4 && 
            s_ccreditTotalBet > 2*s_ccreditStart)
   {
      return fFalse;
   }
   // If we haven't lost, and played for five times the freeloader requirement, quit
   if (s_ccreditBalance > s_ccreditStart && 
            s_ccreditTotalBet > 5*s_ccreditStart)
   {
      return fFalse;
   }
   // If we've played 10 times the freeloader penalty, quit before the time penalty
   // takes it all away...
   if (s_ccreditTotalBet > 10*s_ccreditStart)
   {
      return fFalse;
   }
   return fTrue;
}

_ActionLookupHand
// Call to get the next action for this hand
static Action _ActionLookupHand(Spot spotDealer, Card acard[], int ccard, int fFirst)
{
   int score, cAce;
   Spot spot;
   Action action;
   
   // get the hand's score, and the count of Aces scored as 11
   score = _ScoreHand(acard, ccard, &cace);
   Assert(kAce <= spotDealer && spotDealer <= k10);
   if (fFirst && ccard == 2 && acard[0].spot == acard[1].spot)
   {
      // first action on a pair, check the pair's table
      if ((spot = acard[0].spot) > k10)
         spot = k10;
      action = 
         (Action)mp_spot_spot_actionPair[spotDealer-1][spot-1];
   }
   else if (cAce > 0)
   {
      // "soft" hand, check the soft table
      spot = (Spot)(score - 11);
      Assert(kAce <= spot && spot <= k10);
#ifdef DEBUG
      if (ccard == 2)
      {
         int icard = acard[0].spot == kAce ? 1 : 0;
         Assert(spot == acard[icard].spot || 
                        (spot == k10 && acard[icard].spot > k10));
      }
#endif
      action = 
         (Action)mp_spot_spot_actionSoft[spotDealer-1][spot-1];
   }
   else
   {
      // "hard" hand, chech the hard table
      action = 
         (Action)mp_spot_score_actionHard[spotDealer-1][score];
   }
   
   // If it's not the first play of the hand, we can only kStandPat or kHitMe
#ifdef BUGFIX
   // Another Bug Fix: be careful trying to catch illegal actions...
   if (action == kClaimBlackjack && ccard != 2)
      action = kStandPat;
   if (!fFirst && (action == kDoubleDownAndHitMe || 
                                    action == kSplitAndHitMe))
      action = kHitMe;
#else
   // This code is wrong, it incorrectly returns kHitMe in two cases:
   //  1. If a pair of 10s or Aces is split, then one of them turned into a blackjack
   //  2. A score of 21 is reached with an Ace and two or more cards.
   if (!fFirst && action != kStandPat)
      action = kHitMe;
#endif
   return action;
}

_InitDeck
// reset the counts for a fresh set of decks
static void _InitDeck(int cdeck, int fInfinite)
{
   Spot spot;
   int cspot = 4*cdeck;
   for (spot = kAce; spot < k10; ++spot)
      s_deck.acspot[spot] = cspot;
   Assert(spot == k10);
   s_deck.acspot[k10] = 4*cspot;
   s_deck.cspotRemain = s_deck.cspotStart = 52*cdeck;
   s_deck.dcount = 0;
   s_deck.fInfinite = fInfinite;
}

_CountCards
// This is the counting method used by the couple of people I've talked
// who have actually counted cards playing Blackjack. It's call "Hi-Lo"
// in "Best Blackjack". I tried several other counting models listed
// in that book, and this performed the best. It gives a simple assessment
// of how far off balance the shoe is with respect to small and large cards.
static const int s_mp_spot_dcount[k10+1] =
//    A  2  3  4  5  6  7  8  9  10
{ 0, -1, 1, 1, 1, 1, 1, 0, 0, 0, -1 };

// Remove the specified set of cards from the shoe
void _CountCards(const Card acard[], int ccard)
{
   if (s_deck.fInfinite)
      return;
      
   while (ccard- > 0)
   {
      Spot spot = acard[ccard].spot;
// Bug Fix: we shouldn't count hidden cards...
#ifdef BUGFIX
      if (spot == kHiddenSpot)
         continue;
#endif
      if (spot > k10)
         spot = k10;
      -s_deck.acspot[spot];
      -s_deck.cspotRemain;
      
      s_deck.dcount += s_mp_spot_dcount[spot];
   }
}

_ScoreHand
// Determine the score for the given cards. When possible, score
// Aces at 11, and return the number of aces thusly scored.
static int _ScoreHand(const Card acard[], int ccard, int *pcAce)
{
   int cAceDummy;
   int score = 0;
   int cAce = 0;

   if (pcAce == NULL)
      pcAce = &cAceDummy;
   *pcAce = 0;
   while (ccard- > 0)
   {
      if (acard[ccard].spot == kAce)
         ++cAce;
      score += s_dnspot[acard[ccard].spot].score;
   }
   
   if (score > 21)
      return scoreBust;
   while (score + 10 <= 21 && cAce > 0)
   {
      score += 10;
      -cAce;
      ++*pcAce;
   }

   return score;
}
 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Notion 2.1.9 - A unified workspace for m...
Notion is the unified workspace for modern teams. Features: Integration with Slack Documents Wikis Tasks More guests: invite up to 10 collaborators, friends & family to your pages Page... Read more
Spotify 1.2.0.1165 - Stream music, creat...
Spotify is a streaming music service that gives you on-demand access to millions of songs. Whether you like driving rock, silky R&B, or grandiose classical music, Spotify's massive catalogue puts... Read more
Thunderbird 102.5.1 - Email client from...
As of July 2012, Thunderbird has transitioned to a new governance model, with new features being developed by the broader free software and open source community, and security fixes and improvements... Read more
Pinegrow 7.03 - Mockup and design web pa...
Pinegrow (was Pinegrow Web Designer) is desktop app that lets you mockup and design webpages faster with multi-page editing, CSS and LESS styling, and smart components for Bootstrap, Foundation,... Read more
Adobe After Effects 2022 23.1 - Create p...
The new, more connected Adobe After Effects can make the impossible possible. Get powerful new features like a Live 3D Pipeline that brings CINEMA 4D scenes in as layers - without intermediate... Read more
SteerMouse 5.6.7 - Powerful third-party...
SteerMouse is an advanced driver for USB and Bluetooth mice. SteerMouse can assign various functions to buttons that Apple's software does not allow, including double-clicks, modifier clicks,... Read more
Wireshark 4.0.2 - Network protocol analy...
Wireshark is one of the world's foremost network protocol analyzers, and is the standard in many parts of the industry. It is the continuation of a project that started in 1998. Hundreds of... Read more
Adobe Premiere Pro 2022 23.1 - Digital v...
Adobe Premiere Pro is available as part of Adobe Creative Cloud for as little as $54.99/month. The price on display is a price for annual by-monthly plan for Adobe Premiere Pro only. Adobe Premiere... Read more
1Password 8.9.10 - Powerful password man...
1Password is a password manager that uniquely brings you both security and convenience. It is the only program that provides anti-phishing protection and goes beyond password management by adding Web... Read more
FotoMagico 6.3 - Powerful slideshow crea...
FotoMagico lets you create professional slideshows from your photos and music with just a few, simple mouse clicks. It sports a very clean and intuitive yet powerful user interface. High image... Read more

Latest Forum Discussions

See All

SwitchArcade Round-Up: ‘Chained Echoes’,...
Hello gentle readers, and welcome to the SwitchArcade Round-Up for December 8th, 2022. Today is Thursday, and that usually means an absolute deluge of new releases on the eShop. But the year is winding down, so we’ve only got ten or so to look at... | Read more »
‘Awaken Legends: Idle RPG’ Celebrates th...
Awaken Legends: Idle RPG is adding its first update since the game was soft-launched in November, letting players get their hands on a new hero “Hera Valen". Players can also look forward to the Covenant of the Dark Knight event and the Wishing Well... | Read more »
‘Horizon Chase 2’ Japan World Tour Expan...
Horizon Chase 2 () from Aquiris is getting a major expansion today on Apple Arcade. The Japan World Tour expansion brings in 11 new races across 9 cities and it should be rolling out now as of this writing. I expect it to be available worldwide... | Read more »
Dark Fantasy Visual Novel ‘The 13th Mont...
Originally announced for release in August, The 13th Month from Japanese developer Kobayashimaru and publisher Kodansha released on PC via Steam worldwide this month. The dark fantasy visual novel that reimagines the classic Sleeping Beauty tale, is... | Read more »
Tom Clancey’s The Divison Resurgence ann...
Ubisoft has announced the latest Live Test dates for Tom Clancy’s The Division Resurgence, the hotly anticipated mobile entry in the Divison series. Starting December 8th and ending on the 22nd, the test will offer a huge amount of content for the... | Read more »
‘Easy Come Easy Golf’ New Update Adds St...
Easy Come Easy Golf () from Clap Hanz is one of my favorite games on Apple Arcade. It has been updated quite a bit since launch bringing in new modes and improvements. It recently launched on Nintendo Switch as well. | Read more »
Out Now: ‘Magic vs Metal’, ‘Suzerain’, ‘...
Each and every day new mobile games are hitting the App Store, and so each week we put together a big old list of all the best new releases of the past seven days. Back in the day the App Store would showcase the same games for a week, and then... | Read more »
SwitchArcade Round-Up: Reviews Featuring...
Hello gentle readers, and welcome to the SwitchArcade Round-Up for December 7th, 2022. Today can be accurately described as Mikhail Madness, with a whopping four reviews from our pal-est of pals. Football Manager 2023 Touch, Wobbledogs, Soccer Story... | Read more »
Alchemy Stars celebrates 1 and a half ye...
It has been one and a half years since Alchemy Stars launched, and Level Infinite is celebrating in style with a host of new content. There will be a new story mission and even a store to explore, and a whole new mode for those budding idol... | Read more »
Fighting Game ‘Art of Fighting 2’ ACA Ne...
Last week, side-scrolling shooter Pulstar hit mobile platforms as the newest ACA NeoGeo series release from Hamster and SNK. Read Shaun’s review of it here. Today, fighting game Art of Fighting 2 has launched on iOS and Android. Art of Fighting 2... | Read more »

Price Scanner via MacPrices.net

New! Details on Verizon’s Christmas/Holiday p...
Verizon is offering discounts on iPhones, Apple Watch models, and iPads with specific promo codes as part of their Christmas/Holiday 2022 offerings. Codes are valid when adding a new line of service... Read more
Apple MagSafe accessories are back on Holiday...
Amazon has Apple MagSafe Chargers and Apple’s MagSafe Battery on sale for up to 24% off MSRP again as part of their Christmas/Holiday sale. Shipping is free, and all models are in stock: – MagSafe... Read more
13″ M2 MacBook Airs on sale again for the low...
Amazon has 13″ MacBook Airs with M2 CPUs in stock today and on sale for $150 off MSRP as part of their Christmas/Holiday Sale, prices start at $1049. Shipping is free. They are the lowest prices... Read more
Get an Apple 16″ MacBook Pro for $400 off MSR...
16″ MacBook Pros with Apple’s M1 Pro CPUs are in stock and on sale today at B&H Photo for $300-$400 off Apple’s MSRP for a limited time. Prices start at $2099 for M1 Pro models with 512GB or 1TB... Read more
Holiday clearance sale! Previous-generation A...
Amazon has 2nd generation 32GB and 64GB 4K Apple TVs with Siri remotes and 32GB Apple TV HDs on clearance sale for $80-$90 off original MSRP. Shipping is free, and delivery is available in time for... Read more
Christmas sale at Verizon: Apple AirPods Pro...
Verizon has first-generation Apple AirPods Pro on sale for $159.99 on their online store as part of their continuing Christmas/Holiday sale. Their price is $90 off Apple’s original MSRP, and it’s the... Read more
New Christmas/New Years promo at Xfinity Mobi...
Switch to Xfinity Mobile and open a new line of service, and take $400 off the price of a new iPhone, no trade-in required, through January 10, 2023. The $400 is applied to your account as credits... Read more
Apple iPad Smart Keyboard Folio prices drop u...
Apple iPad Smart Keyboard Folio prices have dropped up to $60 off MSRP at Amazon and Walmart as part of their Christmas/Holiday sales. These are the cheapest prices currently available for these iPad... Read more
Today is the final day for Xfinity Mobile’s $...
If you switch to Xfinity Mobile and open a new line of service, they will take $500 off the price of a new iPhone, no trade-in required. This is the best no trade-in Cyber Monday Apple iPhone 14 deal... Read more
Amazon restocks 10.2″ 64GB 9th-generation iPa...
Amazon has Apple’s 9th generation 10.2″ 64GB WiFi iPads (Silver) in stock and on sale for $269.99 shipped as part of their Christmas/Holiday Sale. Their price is $60 off Apple’s MSRP. Free delivery... Read more

Jobs Board

*Apple* Systems Administrator - JAMF - Activ...
…Administration **Duties and Responsibilities** + Configure and maintain the client's Apple Device Management (ADM) solution. The current solution is JAMF supporting Read more
Cashier - *Apple* Blossom Mall - JCPenney (...
Cashier - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Blossom Mall Read more
Omnichannel Associate - *Apple* Blossom Mal...
Omnichannel Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Read more
Sephora Beauty Advisor - *Apple* Blossom Ma...
Sephora Beauty Advisor - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Read more
Operations Associate - *Apple* Blossom Mall...
Operations Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.