MacTech Network:   MacTech Forums  |  MacForge.net  |  Computer Memory  |  Register Domains  |  Cables  |  iPod Deals  | Mac Deals  | Mac Book Shelf


  MacTech Magazine

The journal of Macintosh technology

 
 

Magazine In Print
  About MacTech  
  Home Page  
  Subscribe  
  Archives DVD  
  Submit News  
  MacTech Forums  
  Get a copy of MacTech RISK FREE  
MacTech Only Search:
Community Search:
MacTech Central
  by Category  
  by Company  
  by Product  
MacTech News
  MacTech News  
  Previous News  
  MacTech RSS  
Article Archives
  Show Indices  
  by Volume  
  by Author  
  Source Code FTP  
Inside MacTech
  Writer's Kit  
  Editorial Staff  
  Editorial Calendar  
  Back Issues  
  Advertising  
Contact Us
  Customer Service  
  MacTech Store  
  Legal/Disclaimers  
  Webmaster Feedback  
 Get Netflix

 October 1998 Programmer's Challenge

Hearts

Mail solutions to: progchallenge@mactech.com
Due Date EXTENDED: 11:59pm ET, Thursday, 8 October 1998

A number of past Challenges have been based on games where one player competed against another player. This month the Challenge is based on the four-handed game of Hearts. Hearts is a card game where one attempts to avoid taking tricks containing hearts or the queen of spades.

The prototype for the code you should write is:

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

#pragma enumsalwaysint on

typedef enum {kNoSuit=0, kSpade, kHeart, kDiamond, kClub} Suit;
typedef enum {kNoSpot=0, k2, k3, k4, k5, k6, k7, k8, k9, k10, 
        kJack, kQueen, kKing, kAce} Spot;
typedef enum {kPassLeft=0, kPassRight, kPassAcross, kNoPass} Pass;

typedef struct Card {
  Suit suit;
  Spot spot;
} Card;

pascal void InitTournament(
  const UInt16 numPlayers,
  /* number of players in the tournament, indexed by seat */
  const UInt16 gameEndingScore
  /* game is over when one player reaches this score */
);

pascal void InitGame(
  const Uint32 playerID[4],  /* Identifier for players in this hand, indexed by seat */
  const UInt16 yourSeat      /* your seat at the table, 0..3 */
);

pascal void SelectPass(
  const Card dealtHand[13],  /* cards you are dealt */
  const Pass passDirection,
  /* direction you are passing, rotates kPassLeft, kPassRight, kPassAcross, kNoPass */
  UInt16 passedCards[3]      /* index in dealtHand of cards you choose to pass */
);

pascal void PlayTrick(
  const UInt16 trickNumber,   /* 13 tricks per hand, 0..12 */
  const UInt16 trickLeader,   /* which player leads this trick */
  const Card yourHand[13],    /* your cards at the beginning of this trick */
  const Card cardsPlayed[4],  /* cards already played this trick, indexed by seat */
  /* entries from [trickLeader] to [yourSeat-1 (mod 4)] are valid */
  UInt16 *yourPlay      /* index into yourHand of the card you choose to play */
);

pascal void TrickResults(
  const Card lastTrick[4],    /* cards played on previous trick, indexed by seat */
  const UInt16 trickWinner    /* which player won this trick */
);

pascal void HandResults(
  const SInt16 pointsThisHand[4],
  /* points earned by each player this hand, -26 .. 25 */
  const SInt32 cumPoints[4]  /* cumulative points earned by each player this game */
  /* both pointsThisHand and cumPoints are indexed by seat number */
  /* your points are pointsThisHand[yourSeat] */
);

#if defined(__cplusplus)
}
#endif

At the beginning of the tournament, your InitTournament routine will be called with the number of participating players (numPlayers) and the gameEndingScore. Before each game, your InitGame routine will be called to provide you with the playerID identifiers of the players involved in this game and your position at the table (yourSeat) relative to the other players. A game consists of a sequence of hands, each of which consists of 13 tricks. At the end of a hand, each player is awarded points based on the cards in the tricks he has taken, one point for each heart taken and 13 points for the queen of spades, with the objective being to take the fewest points. A player who captures all the hearts and the queen of spades, however, is said to "shoot the moon" and receives —26 points. At the end of each hand, your HandResults routine will be called to confirm the number of points received by each player (pointsThisHand) and the cumulative number of points earned by each player during this game (cumPoints).

At the beginning of a hand, each player is dealt 13 cards (dealtHand) and selects three cards to pass to another player. The passDirection rotates from passing left (kPassLeft, where player [i] passes to player [i+1], mod 4), to passing right (kPassRight, player [i] passes to player [i-1]), to passing across (kPassAcross, player [i] passes to player [i+2]), to not passing (kNoPass). Your SelectPass routine is called at the beginning of each hand with your dealtHand and the passDirection; you select the cards to be passed and return in passedCards the index in dealtHand of the cards you want to pass.

Each trick, your PlayTrick and TrickResults routines will be called. PlayTrick provides you with the seat index of the player leading this trick, the cards in your hand at the beginning of this trick, and the cards already played on this trick. On the first trick, yourHand will be the same as dealtHand, except that the cards you passed will be replaced by the cards that were passed to you. On subsequent tricks, yourHand will be unchanged except to remove the card you played on the previous trick (by changing the suit and spot fields to kNoSuit and kNoSpot, respectively). When PlayTrick is called, you select the card you wish to play and return in yourPlay its index in yourHand. After all four players have played, your TrickResults routine will be called to identify the winner of the trick (trickWinner) and all of the cards played on the lastTrick.

The two of clubs is led on the first trick of each hand — the test code will ensure that the PlayTrick routine of the player who has the two of clubs is called first on that trick. The player who won the previous trick leads the next trick. Hearts may not be led until a point card has been played on an earlier trick, unless a player has only hearts left in his hand. The queen of spades may be led at any time.

If the number of entries is reasonable, the tournament will have each player compete against every combination of other players in all possible seat arrangements. If the number of entries is large, the best four entries will be selected in some fair way, after which those entries will compete against one another in a final tournament. If fewer than four entries are submitted, I will round out the table with a simple-minded player that tries to avoid taking any points.

The winner will be the solution that wins the tournament by achieving the lowest score. The score will be the sum of the tournament points earned and a time penalty of one point per millisecond of execution time. The execution time of all of your routines (InitTournament, InitGame, SelectPass, PlayTrick, TrickResults, and HandResults) will count toward the time penalty. This will be a native PowerPC Challenge, using the latest CodeWarrior environment. Solutions may be coded in C, C++, or Pascal. Thanks to Jim Lloyd for suggesting this Challenge.


Here are answers to a few questions asked about this Challenge on the Programmer's Challenge mailing list:

Q1: In case some tournament players play in games that take a lot of hands (and others play in games that take much fewer), are you going to normalize the time taken? Say like seconds per hand (or trick)? It'd be a shame to penalize players whose programs shoot the moon and--by lengthening the game--incur more time penalty.

This could also be alleviated by the alternate scoring for "shoot the moon": every opponent adds 26 points (instead of the moon-shooter removing 26). But "every opponent" means "every opponent in the current game" so it might skew those players' results versus the other tournament players not in the current game. Or that might be considered a fair penalty: letting someone shoot the moon really costs you in the tournament.

... and ...

There is a small, but a finite possibility that "gameEndingScore" (a positive number) is not reached for a very long time because every game results in Shooting the Moon with -26 points, which eventually will wrap around at -32768. At that point the big winner with all those successful moon shots will be severely penalized when his negative score becomes positive. According to some rules, moon shots result in the three opponents each getting 26 points. This would avoid the overflow problem, but makes a moon shot of course three times as valuable.

A1: It's an interesting idea, but I've decided not to change the way the scoring is done. I get complaints when I make a change like this to the original published problem via the mailing list. As I said in the problem statement, the time penalty is intended only to keep execution time reasonable - for programs that run in reasonable time, it shouldn't be a major factor.

Q2: Is there a way to determine how many points in the tournament your current-game opponents have? Each player seems only to be told the points of his opponents in games he plays.

A2: No, there isn't, although the playerID let's you aggregate points from the games you play in. I can see where having the tournament points would be useful, but it isn't worth the wrath of the list if I add a parameter to InitGame this late in the game.

After some correspondence, the questioner agreed with this decision:

"You can just explain to any others like me that the tournament is conducted in seclusion. No talking to other players, no scores posted. You are lead into the game room, see (and, over time, recognize) your opponents for this game, play the hands, get the results, and are led out of the room and back to your cell before the next game starts. In this martial-arts-death-match-movie-like scenario you are lucky to be told the total number of players!"

Q3: Are the points recorded in HandResults() just game points or do they include the time penalty points?

A3: Just game points.

Q4: Does "point cards" include the queen of spades or just hearts? I.e. is it OK to lead hearts once the queen of spades has been played? The various authorities do not agree on this.

A4: Yes. In my misspent youth, leading hearts was allowed once the queen was played, and I meant to allow this by saying "point card" instead of "heart".

Q5: Since:
>Hearts may not be led until a point card has been
>played on an earlier trick, unless a player has only hearts left in his
>hand.

and:

>The queen of spades may be led at any time.

One is left with the conclusion that if one has the lead and one's cards are all Hearts plus the Queen of Spades, one's only play is to lead the Queen of Spades.

Is this correct? There is often a house rule for this situation basically making the Queen of Spades a Heart (only in this instance) so any card may be lead, since one essentially has only 'Hearts'.

A5: No, what I meant is that if you have only point cards (hearts or Queen of Spades), one may lead any point card.

Q6: I guess it is legal to discard hearts on the first trick? ( another variant among the authorities.)

A6: I remember this both ways. My problem statement does not prohibit bleeding on the first trick, so I will allow it.

Q7: Just curiosity: Why the "pascal" which is not portable and not compatible with "ANSI keywords only"? (also, there are a few typos e.g. UInt32 for UInt32)

A7: Occasionally I am criticized for not allowing Pascal solutions. Lately I have been allowing them in the text but not including the Pascal calling convention in the problem statement. Actually, I think it doesn't matter with the PPC calling conventions, but I've included the "pascal" keyword anyway. (And sorry about the Uint typos.)


Test code for this Challenge is now available.


You can get a head start on the Challenge by reading the Programmer's Challenge mailing list. It will be posted to the list on or before the 12th of the preceding month. To join, send an email to listserv@listmail.xplain.com with the subject "subscribe challenge-A". You can also join the discussion list by sending a message with the subject "subscribe challenge-D".





Generate a short URL for this page:


Click on the cover to
see this month's issue!

TRIAL SUBSCRIPTION
Get a RISK-FREE subscription to the only technical Mac magazine!

Today's Deal


Apple Special

Order
Snow Leopard,
Mac Box Set, Family Pack,
and Snow Leopard Server
at a discount.
 


MacTech Magazine. www.mactech.com
Toll Free 877-MACTECH, Outside US/Canada: 805-494-9797

Register Low Cost (ok dirt cheap!) Domain Names in the MacTech Domain Store. As low as $1.99!
Save on long distance * Upgrade your Computer. See local info about Westlake Village
appleexpo.com | bathjot.com | bathroomjot.com | bettersupplies.com | comclothing.com | computerlunatics.com | dotcomclothing.com | explainit.com | exposinternational.com | homeismycastle.com | hoodcards.com | intlexpo.com | keyinfocard.com | kosheru.com | learnmorsels.com | localdealcards.com | lvschools.com | macjobsearch.com | mactechjobs.com | mactechmonitor.com | mactechsupplies.com | macwishbook.com | movie-depot.com | netprofessional.com | nibblelearning.com | notesintheshower.com | officejot.com | onlinebigbox.com | palmosdepot.com | peopleslineitemveto.com | showerjot.com | snapestore.com | snapishop.com | snapistore.com | snaptradingpost.com | stimulusmap.com | stimulusroadmap.com | triunfoguides.com | video-depot.com
Staff Site Links



All contents are Copyright 1984-2008 by Xplain Corporation. All rights reserved.

MacTech is a registered trademark of Xplain Corporation. Xplain, Video Depot, Movie Depot, Palm OS Depot, Explain It, MacDev, MacDev-1, THINK Reference, NetProfessional, NetProLive, JavaTech, WebTech, BeTech, LinuxTech, Apple Expo, MacTech Central and the MacTutorMan are trademarks or service marks of Xplain Corporation. Sprocket is a registered trademark of eSprocket Corporation. Other trademarks and copyrights appearing in this printing or software remain the property of their respective holders.