ftp.nice.ch/pub/next/games/card/NeXTmille.2.0.s.tar.gz#/NeXTmille-2.0a/Player.m

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.