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.