This is Player.m in view mode; [Download] [Up]
/* Generated by Interface Builder */
#import "Player.h"
#import "GameCoordinator.h"
#import "mille.h"
#import "prototypes.h"
#import <assert.h>
#import <limits.h>
@implementation Player
+ new
{
self = [ super new ];
safetyList = [ List new ];
coupFoureeList = [ List new ];
return self;
}
- free
{
[ safetyList free ];
[ coupFoureeList free ];
return [ super free ];
}
- setSafetyPile:anObject
{
safetyPile = anObject;
[[[ safetyPile setTag:P_SAFETY ] setTitle:"Safety Area" ] setFrameMode:YES ];
return self;
}
- setHand:anObject
{
handPile = anObject;
[[[ handPile setTag:P_HAND ] setTitle:"Hand" ] setFrameMode:YES ];
return self;
}
- setBattlePile:anObject
{
battlePile = anObject;
[[[ battlePile setTag:P_BATTLE ] setTitle:"Battle Pile" ] setFrameMode:YES ];
return self;
}
- setSpeedPile:anObject
{
speedPile = anObject;
[[[ speedPile setTag:P_SPEED ] setTitle:"Speed Pile" ] setFrameMode:YES ];
return self;
}
- setDistancePile:anObject
{
distancePile = anObject;
[[ distancePile setTag:P_DISTANCE ] setFrameMode:YES ];
return self;
}
- ( CardView * )battleCard
{
return [ battlePile topCard ];
}
- ( CardView * )speedCard
{
return [ speedPile topCard ];
}
- ( List * )safetyCards
{
return [ safetyPile holderList ];
}
- ( BOOL )isCoupFouree:( CardView * )aCard
{
return ([ coupFoureeList indexOf:aCard ] == NX_NOT_IN_LIST ) ? NO : YES;
}
- ( SafetyStacksView * )safetyPile
{
return safetyPile;
}
- ( Matrix * )otherScoreMatrix
{
return otherScoreMatrix;
}
- ( Matrix * )mainScoreMatrix
{
return mainScoreMatrix;
}
- ( HandTileView * )handPile
{
return handPile;
}
- ( StackView * )battlePile
{
return battlePile;
}
- ( StackView * )drawPile
{
return drawPile;
}
- ( StackView * )speedPile
{
return speedPile;
}
- ( StackView * )discardPile
{
return discardPile;
}
- ( DistanceCardStackView * )distancePile
{
return distancePile;
}
- opponent
{
return opponent;
}
- drawCard
{
CardView *cardToDraw = [ drawPile topCard ];
if( cardToDraw )
[ self completeMove:cardToDraw from:drawPile to:handPile ];
return self;
}
- newHand
{
// Empty all lists used to track cards for player.
// New hand starts, player has no cards.
[ safetyList empty ];
[ coupFoureeList empty ];
// The battle and speed piles are empty.
lastBattleCard =
lastSpeedCard = nil;
// Return all cards that the player has to the draw pile.
[ safetyPile sendAllCardsTo:drawPile ];
[ handPile sendAllCardsTo:drawPile ];
[ battlePile sendAllCardsTo:drawPile ];
[ speedPile sendAllCardsTo:drawPile ];
[ distancePile sendAllCardsTo:drawPile ];
// Set a bunch of counters for the player to 00.
[ OTHER_MILESTONES_SCORE ( self ) setIntValue: 0 ];
[ OTHER_SAFETIES_SCORE( self ) setIntValue: 0 ];
[ OTHER_ALL_FOUR_SAFETIES_SCORE( self ) setIntValue: 0 ];
[ OTHER_COUP_FOUREE_SCORE( self ) setIntValue: 0 ];
[ OTHER_TRIP_COMPLETE_SCORE( self ) setIntValue: 0 ];
[ OTHER_SAFE_TRIP_SCORE( self ) setIntValue: 0 ];
[ OTHER_DELAYED_ACTION_SCORE( self ) setIntValue: 0 ];
[ OTHER_EXTENSION_SCORE( self ) setIntValue: 0 ];
[ OTHER_SHUT_OUT_SCORE( self ) setIntValue: 0 ];
[ OTHER_HAND_TOTAL_SCORE( self ) setIntValue: 0 ];
[ MAIN_MILESTONES_SCORE( self ) setIntValue: 0 ];
[ MAIN_SAFETIES_SCORE( self ) setIntValue: 0 ];
[ MAIN_COUP_FOUREE_SCORE( self ) setIntValue: 0 ];
[ MAIN_HAND_TOTAL_SCORE( self ) setIntValue: 0 ];
cardWasPlayedFlag =
safetyWasPlayedFlag = NO;
return self;
}
- newGame
{
[ OTHER_OVERALL_TOTAL_SCORE( self ) setIntValue:0 ];
[ MAIN_OVERALL_TOTAL_SCORE( self ) setIntValue:[ OTHER_OVERALL_TOTAL_SCORE( self ) intValue ]];
return [ self newHand ];
}
- card:( CardView * )aCard movedFrom:( CardHolder * )oldHolder to:( CardHolder * )newHolder
{
// They player dragged a card. Clear these
// flags for post-move processing.
cardWasPlayedFlag =
safetyWasPlayedFlag = NO;
// Erase old messages.
[[ messagesText selectAll:self ] replaceSel:"" ];
// First determine where the card was dragged
// from then check the destination.
switch([ oldHolder tag ]) {
case P_HAND:
// No card can be drawn from the hand unless a card was drawn
// or the card is a safety.
if([ self cardDrawn ] || isSafety( aCard )) {
// The card was moved from the hand. Now determine
// the destination.
switch([ newHolder tag ]) {
case P_DISCARD:
[ self playCard:aCard fromHandPile:oldHolder toDiscardPile:newHolder ];
break;
case P_SPEED:
case P_BATTLE:
if( newHolder == battlePile )
[ self playCard:aCard fromHandPile:oldHolder toBattlePile:newHolder ];
else
if( newHolder == speedPile )
[ self playCard:aCard fromHandPile:oldHolder toSpeedPile:newHolder ];
else
if( newHolder == [ opponent battlePile ])
[ self playCard:aCard fromHandPile:oldHolder toOpponentBattlePile:newHolder ];
else
if( newHolder == [ opponent speedPile ])
[ self playCard:aCard fromHandPile:oldHolder toOpponentSpeedPile:newHolder ];
else
[[ messagesText selectAll:self ] replaceSel:"Invalid destination pile." ];
break;
case P_DISTANCE:
[ self playCard:aCard fromHandPile:oldHolder toDistancePile:newHolder ];
break;
case P_SAFETY:
[ self playCard:aCard fromHandPile:oldHolder toSafetyPile:newHolder ];
break;
case P_HAND:
case P_DRAW:
// Not a valid move.
// Display a message.
[[ messagesText selectAll:self ] replaceSel:"Invalid destination pile. Draw again." ];
NXBeep();
break;
default:
[[ messagesText selectAll:self ] replaceSel:"ugg, me detect software error in card "
"move code. unknown destination pile." ];
}
} else
[[ messagesText selectAll:self ] replaceSel:"Must draw card first." ];
break;
case P_DRAW:
if( newHolder == handPile )
[ self playCard:aCard fromDrawPile:oldHolder toHandPile:newHolder ];
else
[[ messagesText selectAll:self ] replaceSel:"Cannot drag card from draw pile to there." ];
break;
default:
[[ messagesText selectAll:self ] replaceSel:"ugg, me detect software error in card "
"move code. unknown source pile." ];
NXBeep();
}
if( cardWasPlayedFlag ) {
BOOL allowOpponentMove = YES;
[ self sumHand ];
// Record the top cards on the speed and battle
// piles for coup fouree detection.
lastBattleCard = [ battlePile topCard ];
lastSpeedCard = [ speedPile topCard ];
// If the hand is over then the game coordinator will
// prompt for an extension if appropriate.
if([ self sumWillEndHand:[ OTHER_MILESTONES_SCORE( self ) intValue ]])
if( ![ gameCoordinator willPlayerExtend ]) {
[ gameCoordinator handOver ];
allowOpponentMove = NO;
}
// If there are no cards in the draw pile and both players
// can't play a card then the game is over.
if( ![ drawPile topCard ] && ![ self canPlay ] && ![ opponent canPlay ]) {
[ gameCoordinator handOver ];
allowOpponentMove = NO;
}
if( !safetyWasPlayedFlag && allowOpponentMove && ([ oldHolder tag ] != P_DRAW ))
[ opponent playMove ];
} else
NXBeep();
// Always insure the game window is
// up to date after a card is played.
[[ gameCoordinator gameWindow ] update ];
return self;
}
- playCard:( CardView * )aCard fromDrawPile:( CardHolder * )oldHolder toHandPile:( CardHolder * )newHolder
{
if([[ handPile holderList ] count ] < HAND_SIZE ) {
[ self completeMove:aCard from:oldHolder to:newHolder ];
cardWasPlayedFlag = YES;
} else
[[ messagesText selectAll:self ] replaceSel:"Unable to draw card. Card exceeds hand limit." ];
return self;
}
- playCard:( CardView * )aCard fromHandPile:( CardHolder * )oldHolder toDiscardPile:( CardHolder * )newHolder
{
[ self completeMove:aCard from:oldHolder to:newHolder ];
[ cardsTracker cardPlayed:aCard ];
cardWasPlayedFlag = YES;
return self;
}
- playCard:( CardView * )aCard fromHandPile:( CardHolder * )oldHolder toSpeedPile:( CardHolder * )newHolder
{
// In order to play a card to the speed pile the speed pile
// must contain a hazard.
// Note that safties aren;t dragged onto op the speed pile.
// They're dragged to the safety view.
if( isRemedy( aCard ))
if( ![ self hasSafety:C_RIGHT_OF_WAY_SAFETY ])
if([ speedPile topCard ])
if( isHazard([ speedPile topCard ]))
if( isRemedyForHazard([ speedPile topCard ], aCard ))
cardWasPlayedFlag = YES;
if( cardWasPlayedFlag ) {
[ self completeMove:aCard from:oldHolder to:newHolder ];
[ cardsTracker cardPlayed:aCard ];
} else
[[ messagesText selectAll:self ] replaceSel:"Inappropriate remedy." ];
return self;
}
- playCard:( CardView * )aCard fromHandPile:( CardHolder * )oldHolder toBattlePile:( CardHolder * )newHolder
{
if( isRemedy( aCard ))
if([ aCard tag ] == C_ROLL_REMEDY ) {
if( ![ safetyPile numCardTypeInHolder:C_RIGHT_OF_WAY_SAFETY ])
if([ battlePile topCard ]) {
if( isRemedy([ battlePile topCard ]))
cardWasPlayedFlag = YES;
else
if( isRemedyForHazard([ battlePile topCard ], aCard ))
cardWasPlayedFlag = YES;
} else
cardWasPlayedFlag = YES;
} else
if( isRemedyForHazard([ battlePile topCard ], aCard ))
cardWasPlayedFlag = YES;
if( cardWasPlayedFlag ) {
[ self completeMove:aCard from:oldHolder to:newHolder ];
[ cardsTracker cardPlayed:aCard ];
} else
[[ messagesText selectAll:self ] replaceSel:"Cannot play that card." ];
return self;
}
- playCard:( CardView * )aCard fromHandPile:( CardHolder * )oldHolder toSafetyPile:( CardHolder * )newHolder
{
if( isSafety( aCard )) {
BOOL isCoupFouree = NO;
// Place the card on the appropriate safety
// list.
if( lastBattleCard != [ battlePile topCard ])
if( isRemedyForHazard([ battlePile topCard ], aCard ))
isCoupFouree = YES;
if( lastSpeedCard != [ speedPile topCard ])
if([ aCard tag ] == C_RIGHT_OF_WAY_SAFETY )
if( isRemedyForHazard([ speedPile topCard ], aCard ))
isCoupFouree = YES;
if( isCoupFouree )
[ coupFoureeList addObject:aCard ];
else
[ safetyList addObject:aCard ];
// Discard any hazards set on the player
// which the safety counters.
if( ![ self speedPileAllowsMove ] && isRemedyForHazard([ speedPile topCard ], aCard ))
[ self completeMove:[ speedPile topCard ] from:speedPile to:discardPile ];
if( ![ self battlePileAllowsMove ] && isRemedyForHazard([ battlePile topCard ], aCard ))
[ self completeMove:[ battlePile topCard ] from:battlePile to:discardPile ];
// Update the scores.
[ OTHER_SAFETIES_SCORE( self ) setIntValue:(([ safetyList count ] + [ coupFoureeList count ]) * SC_SAFETY )];
[ OTHER_COUP_FOUREE_SCORE( self ) setIntValue:([ coupFoureeList count ] * SC_COUP )];
[ MAIN_SAFETIES_SCORE( self ) setIntValue:[ OTHER_SAFETIES_SCORE( self ) intValue ]];
[ MAIN_COUP_FOUREE_SCORE( self ) setIntValue:[ OTHER_COUP_FOUREE_SCORE( self ) intValue ]];
if(([ safetyList count ] + [ coupFoureeList count ]) == NUMBER_OF_SAFETY_CARDS_IN_DECK )
[ OTHER_ALL_FOUR_SAFETIES_SCORE( self ) setIntValue:SC_ALL_SAFE ];
[ self completeMove:aCard from:oldHolder to:newHolder ];
[ cardsTracker cardPlayed:aCard ];
// Draw a card to replace the safety(s).
while(([[ handPile holderList ] count ] < HAND_SIZE ) && [ drawPile topCard ])
[ self drawCard ];
[ handPile update ];
safetyWasPlayedFlag =
cardWasPlayedFlag = YES;
} else
[[ messagesText selectAll:self ] replaceSel:"Only Safety cards can be moved there." ];
return self;
}
- playCard:( CardView * )aCard fromHandPile:( CardHolder * )oldHolder toDistancePile:( CardHolder * )newHolder
{
// This is a valid move as long as the card is
// a distance card.
if( isDistance( aCard )) {
if([ self canPlayDistanceCard:aCard ]) {
// If the card is 200 miles it can only be played if
// there is less than two 200 mile cards alread played.
if([ aCard tag ] == C_200 )
if([ distancePile numCardTypeInHolder:C_200 ] < 2 )
cardWasPlayedFlag = YES;
else
[[ messagesText selectAll:self ] replaceSel:"Only two 200 cards can be played." ];
else
cardWasPlayedFlag = YES;
// Update the score.
if( cardWasPlayedFlag ) {
[ OTHER_MILESTONES_SCORE( self ) setIntValue:( distanceCardValue( aCard ) + [ OTHER_MILESTONES_SCORE( self ) intValue ])];
[ MAIN_MILESTONES_SCORE( self ) setIntValue:[ OTHER_MILESTONES_SCORE( self ) intValue ]];
}
} else
[[ messagesText selectAll:self ] replaceSel:"Cannot not move now." ];
} else
[[ messagesText selectAll:self ] replaceSel:"Cannot move card to distance pile." ];
if( cardWasPlayedFlag ) {
[ self completeMove:aCard from:oldHolder to:newHolder ];
[ cardsTracker cardPlayed:aCard ];
}
return self;
}
- playCard:( CardView * )aCard fromHandPile:( CardHolder * )oldHolder toOpponentBattlePile:( CardHolder * )newHolder
{
if( isHazard( aCard ) && ([ aCard tag ] != C_SPEED_LIMIT_HAZARD )) {
if([ opponent battlePileAllowsMove ]) {
int i;
List *opSafety = [ opponent safetyCards ];
BOOL hasRemedy = NO;
for( i = 0; i < [ opSafety count ]; ++i )
if( isRemedyForHazard( aCard, [ opSafety objectAt:i ]))
hasRemedy = YES;
if( hasRemedy )
[[ messagesText selectAll:self ] replaceSel:"Cannot play hazard against opponent safety." ];
else
cardWasPlayedFlag = YES;
} else
[[ messagesText selectAll:self ] replaceSel:"Opponent cannot move." ];
} else
[[ messagesText selectAll:self ] replaceSel:"Cannot play against opponent." ];
if( cardWasPlayedFlag ) {
[ self completeMove:aCard from:oldHolder to:newHolder ];
[ cardsTracker cardPlayed:aCard ];
}
return self;
}
- playCard:( CardView * )aCard fromHandPile:( CardHolder * )oldHolder toOpponentSpeedPile:( CardHolder * )newHolder
{
if( isHazard( aCard ) && ([ aCard tag ] == C_SPEED_LIMIT_HAZARD )) {
if(([[[ opponent speedPile ] topCard ] tag ] != C_SPEED_LIMIT_HAZARD ) && ![ opponent hasSafety:C_RIGHT_OF_WAY_SAFETY ]) {
int i;
List *opSafety = [ opponent safetyCards ];
BOOL hasRemedy = NO;
for( i = 0; i < [ opSafety count ]; ++i )
if( isRemedyForHazard( aCard, [ opSafety objectAt:i ]))
hasRemedy = YES;
if( hasRemedy )
[[ messagesText selectAll:self ] replaceSel:"Cannot play hazard against opponent safety." ];
else
cardWasPlayedFlag = YES;
} else
[[ messagesText selectAll:self ] replaceSel:"Cannot play against opponent." ];
} else
[[ messagesText selectAll:self ] replaceSel:"Cannot play against opponent." ];
if( cardWasPlayedFlag ) {
[ self completeMove:aCard from:oldHolder to:newHolder ];
[ cardsTracker cardPlayed:aCard ];
}
return self;
}
- completeMove:( CardView * )aCard from:( CardHolder * )oldHolder to:( CardHolder * )newHolder
{
// Cards, as they are played, are shown face uo.
[ aCard setShowTopFace:NO ];
// Move the card between the card holders.
[[ oldHolder removeCard:aCard :self ] update ];
[[ newHolder addCard:aCard :self ] update ];
return self;
}
- playMove
{
int cardCount[ NUMBER_OF_CARDS_IN_DECK ];
BOOL newCardDrawnFlag,
playIt[ HAND_SIZE ];
CardView *cardWillEndHand,
*cardWillStopOpponent;
[[ messagesText selectAll:self ] replaceSel:"" ];
do {
int i;
List *cardList = [ handPile holderList ];
memset( &cardCount, 0, sizeof( cardCount ));
memset( &playIt, 0, sizeof( playIt ));
// We haven't drawn any cards.
// After we pass through this loop without
// drawing any cards then the results can be
// evaluated.
newCardDrawnFlag = NO;
// This card will cause the hand to end.
// If non-nil this card assumes a very high play
// priority.
cardWillEndHand = nil;
// This card will halt the opponent's advance.
// High play priority but lower than a card that
// will end the hand.
cardWillStopOpponent = nil;
// Count the cards in the hand based upon
// the card's type.
//
for( i = 0; i < [ cardList count ]; ++i )
++cardCount[[[ cardList objectAt:i ] tag ]];
// Take a look at all of the cards in the
// hand and determine which ones are canidates
// for being played.
for( i = 0; i < [ cardList count ]; ++i ) {
CardView *aCard= [ cardList objectAt:i ];
switch([ aCard tag ]) {
// If we can stop the opponent with a hazard
// then add the hazard to the list of cards that
// can be played.
case C_STOP_HAZARD:
case C_ACCIDENT_HAZARD:
case C_FLAT_TIRE_HAZARD:
case C_OUT_OF_GAS_HAZARD:
if( playIt[ i ] = [ opponent battlePileAllowsMove ] && ![ opponent hasSafety:safetyForHazard([ aCard tag ]) ])
cardWillStopOpponent = aCard;
break;
// If we can slow down the opponent with a
// speed limit hazard then add the hazard to the
// list of cards that can be played.
// The other hazard card types stop the opponent
// while the speed limit only slows him down.
// Therefore, if a hazard was previously found
// in tyhe hand then it takes precedence.
case C_SPEED_LIMIT_HAZARD:
if( playIt[ i ] = ([ opponent speedPileAllowsMove ] && ![ opponent hasSafety:safetyForHazard([ aCard tag ])] && !cardWillStopOpponent ))
cardWillStopOpponent = aCard;
break;
// If the opponent is too close to the
// hand limit then play the safety.
// If the safety card will cure the hazard
// we're afflicted with then add the safety
// to the list of cards that can be played.
case C_EXTRA_TANK_SAFETY:
case C_RIGHT_OF_WAY_SAFETY:
case C_DRIVING_ACE_SAFETY:
case C_PUNCTURE_PROOF_SAFETY:
if(([ gameCoordinator handLimit ] - [ OTHER_HAND_TOTAL_SCORE( opponent ) intValue ]) <= 100 ) {
[ self playCard:aCard fromHandPile:handPile toSafetyPile:safetyPile ];
newCardDrawnFlag = YES;
} else {
if( ![ self battlePileAllowsMove ] && isRemedyForHazard([ battlePile topCard ], aCard )) {
[ self playCard:aCard fromHandPile:handPile toSafetyPile:safetyPile ];
newCardDrawnFlag = YES;
}
if( ![ self speedPileAllowsMove ] && isRemedyForHazard([ speedPile topCard ], aCard )) {
[ self playCard:aCard fromHandPile:handPile toSafetyPile:safetyPile ];
newCardDrawnFlag = YES;
}
}
playIt[ i ] = YES;
break;
// If we can play the distance card then add it
// to the list.
case C_25:
case C_50:
case C_75:
case C_100:
case C_200:
if([ self canPlayDistanceCard:aCard ])
if([ self sumWillEndHand:[ OTHER_MILESTONES_SCORE( self ) intValue ] + distanceCardValue( aCard ) ]) {
cardWillEndHand = aCard;
playIt[ i ] = YES;
} else {
// If there are already two 200 cards played
// then don't play it.
if([ aCard tag ] == C_200 ) {
if([ handPile numCardTypeInHolder:C_200 ] < 2 )
if( ![ self sumWillExceedHand:([ OTHER_MILESTONES_SCORE( self ) intValue ] + distanceCardValue( aCard ))])
playIt[ i ] = YES;
} else
// If playing this card won't exceed the
// hand then consider playing it.
if( ![ self sumWillExceedHand:([ OTHER_MILESTONES_SCORE( self ) intValue ] + distanceCardValue( aCard ))])
playIt[ i ] = YES;
}
break;
// If there is no top card or the roll is
// a remedy to a hazard then add it to the list of
// cards that can be played.
case C_ROLL_REMEDY:
if( ![ safetyPile numCardTypeInHolder:C_RIGHT_OF_WAY_SAFETY ])
if( ![ self battlePileAllowsMove ])
if([ battlePile topCard ]) {
if( isRemedy([ battlePile topCard ]))
playIt[ i ] = YES;
else
if( isRemedyForHazard([ battlePile topCard ], aCard ))
playIt[ i ] = YES;
} else
playIt[ i ] = YES;
break;
// If there is a hazard and this card will remedy
// the hazard then add it to the list of
// cards that can be played.
case C_GASOLINE_REMEDY:
case C_SPARE_TIRE_REMEDY:
case C_REPAIRS_REMEDY:
if( ![ safetyPile numCardTypeInHolder:safetyForRemedy([ aCard tag ])])
if( ![ self battlePileAllowsMove ])
if( isRemedy([ battlePile topCard ]))
playIt[ i ] = YES;
else
if( isRemedyForHazard([ battlePile topCard ], aCard ))
playIt[ i ] = YES;
break;
// If we are currently under a speed limit
// then add this card to the list of cards
// that can be played.
case C_END_OF_LIMIT_REMEDY:
if( ![ self speedPileAllowsMove ])
playIt[ i ] = YES;
break;
default:
assert( 0 /* unknown card type in playMove */);
}
}
// Replenish the cards in hand.
// The cards are either replenished from a safety
// or coup fouree or the computer is drawing its first
// card for this turn.
while(([[ handPile holderList ] count ] < HAND_SIZE ) && [ drawPile topCard ]) {
[ self drawCard ];
newCardDrawnFlag = YES;
}
// Record the top cards on the speed and battle piles
// for coup fouree detection.
lastBattleCard = [ battlePile topCard ];
lastSpeedCard = [ speedPile topCard ];
} while( newCardDrawnFlag );
[ self sumHand ];
// Okay.
// We have a list of cards that can be played in
// the hand and a bunch of flags that could have
// been set in the scan.
if( cardWillEndHand ) {
// Playing this card will end the hand.
[ self playCard:cardWillEndHand fromHandPile:handPile toDistancePile:distancePile ];
if([ gameCoordinator handLimit ] == HAND_DISTANCE_LIMIT ) {
BOOL extend = NO;
// The hand can be extended, but do we
// want to?
// If I have more than one safety card played
// Then extension is a good possiblity.
if([[ self safetyCards ] count ] > 1 )
// If the opponent doesn't have any points and he
// can't move then we'll extend.
if([ OTHER_MILESTONES_SCORE( opponent ) intValue ] == 0 || ![ self couldPlay ])
extend = YES;
else
// If the opponent can move and has over 500 points
// then extension is a bad idea.
if( !([ opponent battlePileAllowsMove ] && [ OTHER_MILESTONES_SCORE( opponent ) intValue ] >= 500 ))
// If all of the safeties have been played
// then we can extend (the safeties are in
// the computers favor).
if([ cardsTracker allSafetiesPlayed ])
extend = YES;
else {
int i,
milesInHand,
mileCardsInHand;
List *cardList = [ handPile holderList ];
// Last chance for extension.
// If I have lots of milage left in hand and
// there aren't many cards in the deck then we can
// extend.
for( i = 0, milesInHand = 0, mileCardsInHand = 0; i < [ cardList count ]; ++i ) {
CardView *aCard = [ cardList objectAt:i ];
if( isDistance( aCard )) {
++mileCardsInHand;
milesInHand += distanceCardValue( aCard );
}
}
if((([ OTHER_MILESTONES_SCORE( self ) intValue ] + milesInHand ) >= HAND_DISTANCE_EXTENSION_LIMIT ) && ([ drawPile cardsInPile ] < mileCardsInHand ))
extend = YES;
}
if( extend ) {
// We're going to extend the hand.
[ gameCoordinator setHandLimit:HAND_DISTANCE_EXTENSION_LIMIT ];
[[ messagesText selectAll:self ] replaceSel:"Computer chooses to extend hand." ];
} else {
// Hand over.
// Uh, a little cheating is going on
// here. We should play those cards before
// we played that distance card but I wouldn't
// make a difference in the hand's outcome.
[ self finishThoseSafeties ];
[ self sumHand ];
[ gameCoordinator handOver ];
}
} else {
// Hand over.
// The extension is meet.
[ self finishThoseSafeties ];
[ self sumHand ];
[ gameCoordinator handOver ];
}
} else
if( cardWillStopOpponent ) {
// The card will stop the opponent's
// movement. Always go for it.
switch([ cardWillStopOpponent tag ]) {
case C_OUT_OF_GAS_HAZARD:
case C_FLAT_TIRE_HAZARD:
case C_ACCIDENT_HAZARD:
case C_STOP_HAZARD:
[ self playCard:cardWillStopOpponent fromHandPile:handPile toOpponentBattlePile:[ opponent battlePile ]];
break;
case C_SPEED_LIMIT_HAZARD:
[ self playCard:cardWillStopOpponent fromHandPile:handPile toOpponentSpeedPile:[ opponent speedPile ]];
break;
default:
assert( 0 /* unknown hazard card */ );
}
} else {
int i;
List *cardList = [ handPile holderList ];
int merit[ HAND_SIZE ],
lowestMeritValue = INT_MAX,
highestMeritValue = INT_MIN;
CardView *highestMeritCard = nil,
*lowestMeritCard = nil;
// If we could have played any safeties to
// cure a hazard or coup fouree then we would
// have done so previously.
// If playing a distance card would have ended
// the hand then we would have done so previously.
// If we could play a hazard then we would have
// done so previously.
// Lets determine the merits of the cards.
// We have remedies and point cards that
// can be played or discarded.
// Merits: + The larger the positive number the
// more merit it has. The greater
// its value in being played.
// + 0 implies to keep the card.
// + The more negative the number the
// less merit it has. It should be
// discarded.
for( i = 0; i < [ cardList count ]; ++i ) {
CardView *aCard = [ cardList objectAt:i ];
if( playIt[ i ] ) {
switch([ aCard tag ]) {
// To chhose wheather this card should be played
// simply calculate its metit by dividing its value
// by 25.
case C_25:
case C_50:
case C_75:
case C_100:
case C_200:
merit[ i ] = distanceCardValue( aCard ) / 25;
break;
// If there is a hazard on the battle pile which
// this remedy will counter, we're
// going to play it.
case C_GASOLINE_REMEDY:
case C_SPARE_TIRE_REMEDY:
case C_REPAIRS_REMEDY:
merit[ i ] = 0;
if( ![ self battlePileAllowsMove ])
if( isRemedyForHazard([ battlePile topCard ], aCard ))
merit[ i ] = 100;
break;
case C_ROLL_REMEDY:
merit[ i ] = 0;
if( ![ self battlePileAllowsMove ])
if([ battlePile topCard ]) {
if( isRemedy([ battlePile topCard ]))
merit[ i ] = 100;
else
if( isRemedyForHazard([ battlePile topCard ], aCard ))
merit[ i ] = 100;
} else
merit[ i ] = 100;
break;
case C_END_OF_LIMIT_REMEDY:
merit[ i ] = 99;
break;
case C_OUT_OF_GAS_HAZARD:
case C_FLAT_TIRE_HAZARD:
case C_ACCIDENT_HAZARD:
case C_STOP_HAZARD:
case C_SPEED_LIMIT_HAZARD:
merit[ i ] = 0;
break;
// If all of the hazards to which this safety
// apply have been played then we'll play it now.
case C_EXTRA_TANK_SAFETY:
merit[ i ] = 0;
if([ self numCardTypeObserved:C_OUT_OF_GAS_HAZARD ] == [ cardsTracker numInDeck:C_OUT_OF_GAS_HAZARD ])
merit[ i ] = 1;
if([ cardList count ] < HAND_SIZE )
merit[ i ] = 1;
break;
case C_PUNCTURE_PROOF_SAFETY:
merit[ i ] = 0;
if([ self numCardTypeObserved:C_FLAT_TIRE_HAZARD ] == [ cardsTracker numInDeck:C_FLAT_TIRE_HAZARD ])
merit[ i ] = 1;
if([ cardList count ] < HAND_SIZE )
merit[ i ] = 1;
case C_DRIVING_ACE_SAFETY:
merit[ i ] = 0;
if([ self numCardTypeObserved:C_ACCIDENT_HAZARD ] == [ cardsTracker numInDeck:C_ACCIDENT_HAZARD ])
merit[ i ] = 1;
if([ cardList count ] < HAND_SIZE )
merit[ i ] = 1;
case C_RIGHT_OF_WAY_SAFETY:
merit[ i ] = 0;
if(([ self numCardTypeObserved:C_STOP_HAZARD ] == [ cardsTracker numInDeck:C_STOP_HAZARD ]) && ([ self numCardTypeObserved:C_SPEED_LIMIT_HAZARD ] == [ cardsTracker numInDeck:C_SPEED_LIMIT_HAZARD ]))
merit[ i ] = 1;
if([ cardList count ] < HAND_SIZE )
merit[ i ] = 1;
break;
default:
assert( 0 /* unknown card type */ );
}
} else
if( isHazard( aCard )) {
// This hazard card is not playable.
// If the opponent has the safety then
// mark the card for discard.
if([[ opponent safetyPile ] numCardTypeInHolder:safetyForHazard([ aCard tag ])])
merit[ i ] = -10;
else
merit[ i ] = 0;
} else
if( isRemedy( aCard )) {
int hazardCard = hazardForRemedy([ aCard tag ]);
// The remedy card isn't playable.
// If all of the hazards have either been
// played or have been played and in my hand then
// mark the card for discard.
if([ self numCardTypeObserved:hazardCard ] == [ cardsTracker numInDeck:hazardCard ])
merit[ i ] = -10;
else
// If I have the safety for this card then
// discard.
if([ safetyPile numCardTypeInHolder:safetyForHazard( hazardCard ) ] || [ handPile numCardTypeInHolder:safetyForHazard( hazardCard )])
merit[ i ] = INT_MIN;
else
// If I have multiple cards of this remedy
// then consider it for discard; Otherwise
// we want to keep it.
if( cardCount[[ aCard tag ]] > 1 )
merit[ i ] = cardCount[[ aCard tag ]] * -5;
else
merit[ i ] = 0;
} else
if( isDistance( aCard )) {
// Distance cards.
// The lower the card's distance value
// the greater the probability that it
// will be discarded.
// A card value of 200 is to have a
// value of -1. If we have over two
// 200s then its value is -9
// ( -((200/25)+1).
if([ aCard tag ] == C_200 ) {
if(([ handPile numCardTypeInHolder:C_200 ] + [ distancePile numCardTypeInHolder:C_200 ]) > 2 )
merit[ i ] = -9;
else
if([ self sumWillExceedHand:([ OTHER_MILESTONES_SCORE( self ) intValue ] + distanceCardValue( aCard ))])
merit[ i ] = -9;
else
merit[ i ] = -1;
} else
merit[ i ] = -(( 200 - distanceCardValue( aCard )) / 25 );
} else
// Cards with large negative numbers are
// canidates for the discard pile.
merit[ i ] = cardCount[[ aCard tag ]] * -1;
}
// Find the card with the largest
// merit. We'll play that card.
// If a card of high merit can't be
// found then discard the card of
// worst merit.
for( i = 0; i < [ cardList count ]; ++i ) {
if( merit[ i ] > highestMeritValue ) {
highestMeritValue = merit[ i ];
highestMeritCard = [ cardList objectAt:i ];
}
if( merit[ i ] < lowestMeritValue ) {
lowestMeritValue = merit[ i ];
lowestMeritCard = [ cardList objectAt:i ];
}
}
//{ int i;
// for( i = 0; i < [ cardList count ]; ++i ) {
// CardView *aCard = [ cardList objectAt:i ];
//
// printf("%s, play=%d, cnt=%d, merit=%d\n",
// [ aCard name ],
// playIt[ i ],
// cardCount[[ aCard tag ]],
// merit[ i ]);
// }
//}
// Time to play a card.
if( highestMeritValue > 0 ) {
switch([ highestMeritCard tag ]) {
case C_25:
case C_50:
case C_75:
case C_100:
case C_200:
[ self playCard:highestMeritCard fromHandPile:handPile toDistancePile:distancePile ];
break;
case C_OUT_OF_GAS_HAZARD:
case C_FLAT_TIRE_HAZARD:
case C_ACCIDENT_HAZARD:
case C_STOP_HAZARD:
case C_SPEED_LIMIT_HAZARD:
assert( 0 /* card shouldn't have reached point */ );
case C_GASOLINE_REMEDY:
case C_SPARE_TIRE_REMEDY:
case C_REPAIRS_REMEDY:
case C_ROLL_REMEDY:
[ self playCard:highestMeritCard fromHandPile:handPile toBattlePile:battlePile ];
break;
case C_END_OF_LIMIT_REMEDY:
[ self playCard:highestMeritCard fromHandPile:handPile toSpeedPile:speedPile ];
break;
case C_EXTRA_TANK_SAFETY:
case C_PUNCTURE_PROOF_SAFETY:
case C_DRIVING_ACE_SAFETY:
case C_RIGHT_OF_WAY_SAFETY:
[ self playCard:highestMeritCard fromHandPile:handPile toSafetyPile:safetyPile ];
[ self playMove ];
break;
default:
assert( 0 /* unknown card of high merit */ );
}
//printf( "played card: %s, merit=%d\n", [ highestMeritCard name ], highestMeritValue );
} else {
if( lowestMeritValue < 0 )
[ self playCard:lowestMeritCard fromHandPile:handPile toDiscardPile:discardPile ];
else {
// No card could be played and couldn't
// find a best case card to discard.
// Pick any card and discard it.
for( i = 0, lowestMeritCard = nil; !lowestMeritCard && ( i < [ cardList count ]); ++i )
if( !isSafety([ cardList objectAt:i ]))
lowestMeritCard = [ cardList objectAt:i ];
// Its possible that there are no cards
// left in the hand. For example
// playing the safeties when the end of
// the hand is near forces this method to
// call itself resulting in no cards left
// in the hand.
if( lowestMeritCard )
[ self playCard:lowestMeritCard fromHandPile:handPile toDiscardPile:discardPile ];
}
//printf( "discard card: %s, merit=%d\n", [ lowestMeritCard name ], lowestMeritValue );
}
}
//printf("\n");
[ self sumHand ];
// If there are no more cards in the
// draw pile and both players cannot
// move then the hand is over.
if( ![ drawPile topCard ] && ![ self canPlay ] && ![ opponent canPlay ])
[ gameCoordinator handOver ];
return self;
}
- ( BOOL )canPlay
{
int i;
List *cardList = [ handPile holderList ];
for( i = 0; i < [ cardList count ]; ++i )
if( isSafety([ cardList objectAt:i ]))
return YES;
if([ self battlePileAllowsMove ])
for( i = 0; i < [ cardList count ]; ++i )
if( isDistance([ cardList objectAt:i ]))
if([ self speedPileAllowsMove:[ cardList objectAt:i ]])
return YES;
return NO;
}
- ( BOOL )couldPlay
{
BOOL retVal = NO;
if([ opponent battlePileAllowsMove ]) {
if([ opponent speedPileAllowsMove ]) {
int cardTypes[] = { C_25, C_50, C_75, C_100, C_200 },
i;
for( i = 0; i < ( sizeof( cardTypes ) / sizeof( int )); ++i )
if([ self numCardTypeObserved:cardTypes[ i ]] != [ cardsTracker numInDeck:cardTypes[ i ]])
retVal = YES;
} else {
int cardTypes[] = { C_25, C_50 },
i;
for( i = 0; i < ( sizeof( cardTypes ) / sizeof( int )); ++i )
if([ self numCardTypeObserved:cardTypes[ i ]] != [ cardsTracker numInDeck:cardTypes[ i ]])
retVal = YES;
}
}
if(([ cardsTracker numSafetiesPlayed ] + [ handPile numSafetiesInHolder ]) != NUMBER_OF_SAFETY_CARDS_IN_DECK )
retVal = YES;
return retVal;
}
- finishThoseSafeties
{
BOOL cardRemoved;
do {
int i;
List *cardList = [ handPile holderList ];
cardRemoved = NO;
// These safety cards aren't coup fouree.
lastBattleCard = [ battlePile topCard ];
lastSpeedCard = [ speedPile topCard ];
// Look for any safety cards in the hand.
// If they're there then play them.
for( i = 0; !cardRemoved && ( i < [ cardList count ]); ++i )
if( isSafety([ cardList objectAt:i ])) {
[ self playCard:[ cardList objectAt:i ] fromHandPile:handPile toSafetyPile:safetyPile ];
cardRemoved = YES;
}
} while( cardRemoved );
return self;
}
- ( int )numCardTypeObserved:( int )aCardType
{
return [ cardsTracker numPlayed:aCardType ] + [ handPile numCardTypeInHolder:aCardType ] ;
}
- ( BOOL )canPlayDistanceCard:( CardView * )aCard
{
BOOL retVal = NO;
// Check to see if the battle pile allows any
// distance card to be played.
if([ self battlePileAllowsMove ])
// Check to see if the speed pile allows the
// distance card to be played..
if([ self speedPileAllowsMove:aCard ])
// Check to see if the card exceeds the hand
// limit. If it doesn't then the card can
// indeed be played.
if( ![ self sumWillExceedHand:([ OTHER_MILESTONES_SCORE( self ) intValue ] + distanceCardValue( aCard ))])
retVal = YES;
return retVal;
}
- ( BOOL )hasSafety:( int )aCard
{
BOOL retVal = NO;
List *cardList = [ self safetyCards ];
int i;
for( i = 0; !retVal && ( i < [ cardList count ]); ++i )
if([[ cardList objectAt:i ] tag ] == aCard )
retVal = YES;
return retVal;
}
- ( BOOL )battlePileAllowsMove
{
if([ battlePile topCard ])
if([[ battlePile topCard ] tag ] == C_ROLL_REMEDY )
return YES;
if([ battlePile topCard ])
if( isRemedy([ battlePile topCard ]) && [ self hasSafety:C_RIGHT_OF_WAY_SAFETY ])
return YES;
if( ![ battlePile topCard ] && [ self hasSafety:C_RIGHT_OF_WAY_SAFETY ])
return YES;
return NO;
}
- ( BOOL )speedPileAllowsMove:( CardView * )aCard
{
BOOL retVal = NO;
if([ self speedPileAllowsMove ])
retVal = YES;
else
if(([ aCard tag ] == C_25 ) || ([ aCard tag ] == C_50 ))
retVal = YES;
return retVal;
}
- ( BOOL )speedPileAllowsMove
{
BOOL retVal = NO;
if([ speedPile topCard ]) {
if([[ speedPile topCard ] tag ] == C_END_OF_LIMIT_REMEDY )
retVal = YES;
else
if([ self hasSafety:C_RIGHT_OF_WAY_SAFETY ])
retVal = YES;
} else
retVal = YES;
return retVal;
}
- ( BOOL )sumWillEndHand:( int )aSum
{
return ([ gameCoordinator handLimit ] == aSum );
}
- ( BOOL )sumWillExceedHand:( int )aSum
{
return ([ gameCoordinator handLimit ] < aSum );
}
- ( BOOL )sumWillEndGame:( int )aSum
{
return ( aSum >= GAME_DISTANCE_LIMIT );
}
- sumHand
{
int i,
sum;
TextFieldCell *handTotal = OTHER_HAND_TOTAL_SCORE( self );
// Sum all of the score values for the player from the
// hand. Update the hand total.
for( i = 0, sum = 0; [[ self otherScoreMatrix ] findCellWithTag:i ] != handTotal; ++i )
sum += [[[ self otherScoreMatrix ] findCellWithTag:i ] intValue ];
[ handTotal setIntValue:sum ];
// Update the hand total in the main score window
// as well.
[ MAIN_HAND_TOTAL_SCORE( self ) setIntValue:sum ];
return self;
}
- sumOverall
{
// If there was an extension, the extension
// value was meet, and I won then there
// are other bonus points available.
if([ OTHER_HAND_TOTAL_SCORE( self ) intValue ] == HAND_DISTANCE_EXTENSION_LIMIT ) {
[ OTHER_TRIP_COMPLETE_SCORE( self ) setIntValue:SC_TRIP ];
if( ![ drawPile topCard ])
[ OTHER_DELAYED_ACTION_SCORE( self ) setIntValue:SC_DELAY ];
if( ![ OTHER_HAND_TOTAL_SCORE( opponent ) intValue ])
[ OTHER_SHUT_OUT_SCORE( self ) setIntValue:SC_SHUT_OUT ];
}
if( ![ distancePile numCardTypeInHolder:C_200 ])
[ OTHER_SAFE_TRIP_SCORE( self ) setIntValue:SC_SAFE ];
[ self sumHand ];
[ OTHER_OVERALL_TOTAL_SCORE( self ) setIntValue:[ OTHER_OVERALL_TOTAL_SCORE( self ) intValue ] + [ OTHER_HAND_TOTAL_SCORE( self ) intValue ]];
[ MAIN_OVERALL_TOTAL_SCORE( self ) setIntValue:[ OTHER_OVERALL_TOTAL_SCORE( self ) intValue ]];
return self;
}
- ( BOOL )cardDrawn
{
return ( [[ handPile holderList ] count ] == HAND_SIZE ) || ![ drawPile topCard ];
}
@end
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.