
#import "Craps.h" 
#import "props.h"

#define ALLOWS_ODDS 1
#define CRAPS_ODDSCHIPOFFSET 18.0
#define CRAPS_ODDSCHIPXOFFSET 10.0

#define CRAPS_ODDSWAGER 1
#define CRAPS_FLATWAGER 0

#define CRAPS_NAMEFORDEFAULTS "NVCraps"

void autoRollHandler(DPSTimedEntry teNumber, double now, void *userData)
{
    id obj = (id)userData;
    [obj clockTick];
}
 

@implementation Craps

+ initialize
{
    static NXDefaultsVector ourDefaults = {
        {"oddsPref", "1"},
		{"autoRollPref", "5"},
		{NULL, NULL}
    };
    
    NXRegisterDefaults(CRAPS_NAMEFORDEFAULTS, ourDefaults);

	return self;
}

- awakeFromNib
/*
	Set the tableTop image (the craps table tiff), create sounds, set pointers to
	proposition views in betView, etc.
*/
{	
	// betView is an array that points to each proposition...
	betView[PASS] = passView;
    betView[DONT_PASS] = dontPassView;
    betView[COME] = comeView;
    betView[DONT_COME] = dontComeView;
    betView[FIELD] = fieldView;
    betView[PLACE_FOUR] = placeFourView;
    betView[PLACE_FIVE] = placeFiveView;
    betView[PLACE_SIX] = placeSixView;
    betView[PLACE_EIGHT] = placeEightView;
    betView[PLACE_NINE] = placeNineView;
    betView[PLACE_TEN] = placeTenView;
    betView[ANY_CRAPS] = anyCrapsView;
    betView[TWO] = twoView;
    betView[THREE] = threeView;
    betView[TWELVE] = twelveView;
    betView[ANY_SEVEN] = anySevenView;
    betView[HARD_FOUR] = hardFourView;
    betView[HARD_TEN] = hardTenView;
    betView[HARD_SIX] = hardSixView;
    betView[HARD_EIGHT] = hardEightView;
    betView[BIG_SIX] = bigSixView;
    betView[BIG_EIGHT] = bigEightView;
    //betView[HORN] = hornView;
    betView[ELEVEN] = elevenView;
    betView[COME_FOUR] = comeFourView;
    betView[COME_FIVE] = comeFiveView;
    betView[COME_SIX] = comeSixView;
    betView[COME_EIGHT] = comeEightView;
    betView[COME_NINE] = comeNineView;
    betView[COME_TEN] = comeTenView;
    betView[DONT_COME_FOUR] = dontComeFourView;
    betView[DONT_COME_FIVE] = dontComeFiveView;
    betView[DONT_COME_SIX] = dontComeSixView;
    betView[DONT_COME_EIGHT] = dontComeEightView;
    betView[DONT_COME_NINE] = dontComeNineView;
    betView[DONT_COME_TEN] = dontComeTenView;
	
		
	[betView[DONT_COME] puckOn:YES];
     	
	[self revertPreferences:self];
	
	point = 0;
	
	//  Set shooter to the current player
	[self setShooter:[pitBoss currentPlayer]];
	
	return self;
}

- free
{
	[dice free];
	
	return [super free];
}

- finishSessionAndClose
{
	int i;

	for(i=0; i<NUM_PROPS; i++)
	{
		[betView[i] discardTrackingRect];
	}
	
	if(isAutoRoll)
		[autoRollButton performClick:self];
	
	[self setPreferences:self];

	return self;
}

- setPayoffs
{
	int p, i, cost, payoff;

	// these views allow odds
	[[[betView[PASS] setViewType:ALLOWS_ODDS] setNumRows:2]
				setChipRow:CRAPS_ODDSCHIPOFFSET atRow:CRAPS_ODDSWAGER];
	[[[betView[DONT_PASS] setViewType:ALLOWS_ODDS] setNumRows:2]
				setChipRow:CRAPS_ODDSCHIPOFFSET atRow:CRAPS_ODDSWAGER];
	[[[betView[COME_FOUR] setViewType:ALLOWS_ODDS] setNumRows:2]
				setChipRow:CRAPS_ODDSCHIPOFFSET atRow:CRAPS_ODDSWAGER];
	[[[betView[COME_FIVE] setViewType:ALLOWS_ODDS] setNumRows:2] 
				setChipRow:CRAPS_ODDSCHIPOFFSET atRow:CRAPS_ODDSWAGER];
	[[[betView[COME_SIX] setViewType:ALLOWS_ODDS] setNumRows:2] 
				setChipRow:CRAPS_ODDSCHIPOFFSET atRow:CRAPS_ODDSWAGER];
	[[[betView[COME_EIGHT] setViewType:ALLOWS_ODDS] setNumRows:2] 
				setChipRow:CRAPS_ODDSCHIPOFFSET atRow:CRAPS_ODDSWAGER];
	[[[betView[COME_NINE] setViewType:ALLOWS_ODDS] setNumRows:2] 
				setChipRow:CRAPS_ODDSCHIPOFFSET atRow:CRAPS_ODDSWAGER];
	[[[betView[COME_TEN] setViewType:ALLOWS_ODDS] setNumRows:2]
				setChipRow:CRAPS_ODDSCHIPOFFSET atRow:CRAPS_ODDSWAGER];
	[[[betView[DONT_COME_FOUR] setViewType:ALLOWS_ODDS] setNumRows:2]
				setChipRow:CRAPS_ODDSCHIPOFFSET atRow:CRAPS_ODDSWAGER];
	[[[betView[DONT_COME_FIVE] setViewType:ALLOWS_ODDS] setNumRows:2] 
				setChipRow:CRAPS_ODDSCHIPOFFSET atRow:CRAPS_ODDSWAGER];
	[[[betView[DONT_COME_SIX] setViewType:ALLOWS_ODDS] setNumRows:2] 
				setChipRow:CRAPS_ODDSCHIPOFFSET atRow:CRAPS_ODDSWAGER];
	[[[betView[DONT_COME_EIGHT] setViewType:ALLOWS_ODDS] setNumRows:2] 
				setChipRow:CRAPS_ODDSCHIPOFFSET atRow:CRAPS_ODDSWAGER];
	[[[betView[DONT_COME_NINE] setViewType:ALLOWS_ODDS] setNumRows:2] 
				setChipRow:CRAPS_ODDSCHIPOFFSET atRow:CRAPS_ODDSWAGER];
	[[[betView[DONT_COME_TEN] setViewType:ALLOWS_ODDS] setNumRows:2] 
				setChipRow:CRAPS_ODDSCHIPOFFSET atRow:CRAPS_ODDSWAGER];
	
	for(p=0; p<NUM_PROPS; p++)
	{
		
		// Set the normal flat wager payoff
		[betView[p] setPayoff:prop[p].payoff cost:prop[p].cost];
		
		// Set the odds payoffs for views that have odds
		if([betView[p] viewType] == ALLOWS_ODDS)
		{
			payoff = 1; cost = 1;
			switch(p)
			{
				// PASS & DONT_PASS will get set after the point is set.
				
				case COME_FOUR: 
					payoff = 2; cost = 1;
					break;
				case COME_FIVE: 
					payoff = 3; cost = 2;
					break;
				case COME_SIX: 
					payoff = 6; cost = 5;
					break;
				case COME_EIGHT: 
					payoff = 6; cost = 5;
					break;
				case COME_NINE: 
					payoff = 3; cost = 2;
					break;
				case COME_TEN: 
					payoff = 2; cost = 1;
					break;
				case DONT_COME_FOUR: 
					payoff = 1; cost = 2;
					break;
				case DONT_COME_FIVE: 
					payoff = 2; cost = 3;
					break;
				case DONT_COME_SIX: 
					payoff = 5; cost = 6;
					break;
				case DONT_COME_EIGHT: 
					payoff = 5; cost = 6;
					break;
				case DONT_COME_NINE: 
					payoff = 2; cost = 3;
					break;
				case DONT_COME_TEN: 
					payoff = 1; cost = 2;
					break;
			}
			
			if(payoff != 1 && cost != 1)
			{
				for(i=0; i<MAX_PLAYERS; i++)
				{
					[betView[p] setPayoff:payoff cost:cost atRow:CRAPS_ODDSWAGER col:i];
				}
			}
		}
	}
	
	return self;
}

/**********************************************************************************  
 *                                Craps Methods                                   *
 **********************************************************************************/
- (BOOL)anyBetsTooLow
/*
	Scans each view on table and see if any of them have bets that are less than
	the table minimum.  If so, it runs an alert panel telling the player involved, and
	then returns YES.  If all bets check out okay, it returns NO.
*/
{
    int p, player, amount;
    
    for(p=0; p<NUM_PROPS; p++) 
	{
        for(player=0; player<[pitBoss numPlayers]; player++) 
		{
			amount = [betView[p] amountAtRow:CRAPS_FLATWAGER col:player];
	    
			// special case:  the horn must be made in (4*tableMin) increments
			if(p == HORN && amount != 0 && (amount%(4*tableMin)) != 0) 
			{
				[pitBoss playSound:NV_WARNSOUND];
				NXRunAlertPanel("Dealer", "%s,  Horn bets must be made in increments of $%d", "Okay", NULL, NULL, [[pitBoss playerAt:player] playerName], 4*tableMin);
				
				// make this player the current bettor
				[pitBoss setCurrentPlayer:[pitBoss playerAt:player]];
				return YES;
				
			} 
			else if(amount!=0 && amount < tableMin) 
			{
				[pitBoss playSound:NV_WARNSOUND];
				NXRunAlertPanel("Dealer", "%s,  Your bet on %s is below the table minimum!", "Okay", NULL, NULL, [[pitBoss playerAt:player] playerName], prop[p].name);
				
				// make this player the current bettor
				[pitBoss setCurrentPlayer:[pitBoss playerAt:player]];
				return YES;
			}
		}
    }
    
    return NO;
}

- (BOOL)anyBetsTooHigh
/*
	Scans each view on the table to see if any bets are above the table max.  If so,
	it runs an alert panel and warns the player involved, and returns YES.  If all
	bets check out okay, it returns NO.
*/
{
    int p, player, amount;
    
    for(p=0; p<NUM_PROPS; p++) {
        for(player=0; player<[pitBoss numPlayers]; player++) 
		{
			amount = [betView[p] amountAtRow:CRAPS_FLATWAGER col:player];
			
			// bets must
			if(amount != 0 && amount > tableMax) 
			{
				[pitBoss playSound:NV_WARNSOUND];
				NXRunAlertPanel("Dealer", "%s,  Your bet on %s is above the table maximum!", "Okay", NULL, NULL, [[pitBoss playerAt:player] playerName], prop[p].name);
				
				// make this player the current bettor
				[pitBoss setCurrentPlayer:[pitBoss playerAt:player]];
				return YES;
			}
		}
    }
    
    return NO;
}

- checkBets:(int)die1 :(int)die2 status:(int)status
/*
	checkBets takes two dice values and the status, and manipulates the bets on each
	view according to the dice.  This is an ugly routine that must be easier to do, 
	but this works fine, so I've left it.  For each proposition, it checks the dice,
	and either has the view payOut or clear the bet, depending on whether the
	decision is won or lost.
*/
	
{
    int roll;
    
    roll = die1 + die2;
     
    
    // check the pass/dont pass line
    if(status == WINNER) 
	{
		[[betView[PASS] payBets] sumColumnsToRow:CRAPS_FLATWAGER];
		[betView[DONT_PASS] clearBets];
    } 
	else if(status == LOOSER) 
	{
		[[betView[DONT_PASS] payBets] sumColumnsToRow:CRAPS_FLATWAGER];
		[betView[PASS] clearBets];
    }   
    
    // check come line
    if(roll == 7) 
	{
		[betView[COME] payBets];
		if(point == 0)
		{
			// Clear only the flat wagers when point was off
			[[betView[COME_FOUR] clearBets] sumColumnsToRow:CRAPS_FLATWAGER];
			[[betView[COME_FIVE] clearBets] sumColumnsToRow:CRAPS_FLATWAGER];
			[[betView[COME_SIX] clearBets] sumColumnsToRow:CRAPS_FLATWAGER];
			[[betView[COME_EIGHT] clearBets] sumColumnsToRow:CRAPS_FLATWAGER];
			[[betView[COME_NINE] clearBets] sumColumnsToRow:CRAPS_FLATWAGER];
			[[betView[COME_TEN] clearBets] sumColumnsToRow:CRAPS_FLATWAGER];
			
			// Add to COME line
			[[[[[[betView[COME] addBetsFrom:betView[COME_FOUR]]
								addBetsFrom:betView[COME_FIVE]]
								addBetsFrom:betView[COME_SIX]]
								addBetsFrom:betView[COME_EIGHT]]
								addBetsFrom:betView[COME_NINE]]
								addBetsFrom:betView[COME_TEN]];
								
			// Now remove odds from COME_N views
			[betView[COME_FOUR] clearBetsAtRow:CRAPS_FLATWAGER];
			[betView[COME_FIVE] clearBetsAtRow:CRAPS_FLATWAGER];
			[betView[COME_SIX] clearBetsAtRow:CRAPS_FLATWAGER];
			[betView[COME_EIGHT] clearBetsAtRow:CRAPS_FLATWAGER];
			[betView[COME_NINE] clearBetsAtRow:CRAPS_FLATWAGER];
			[betView[COME_TEN] clearBetsAtRow:CRAPS_FLATWAGER];
		}
		else
		{
			[betView[COME_FOUR] clearBets];
			[betView[COME_FIVE] clearBets];
			[betView[COME_SIX] clearBets];
			[betView[COME_EIGHT] clearBets];
			[betView[COME_NINE] clearBets];
			[betView[COME_TEN] clearBets];
		}
	
		[[betView[DONT_COME_FOUR] payBets] sumColumnsToRow:CRAPS_FLATWAGER];
		[[betView[DONT_COME_FIVE] payBets] sumColumnsToRow:CRAPS_FLATWAGER];
		[[betView[DONT_COME_SIX] payBets] sumColumnsToRow:CRAPS_FLATWAGER];
		[[betView[DONT_COME_EIGHT] payBets] sumColumnsToRow:CRAPS_FLATWAGER];
		[[betView[DONT_COME_NINE] payBets] sumColumnsToRow:CRAPS_FLATWAGER];
		[[betView[DONT_COME_TEN] payBets] sumColumnsToRow:CRAPS_FLATWAGER];
		[betView[DONT_COME] getBetsFrom:betView[DONT_COME_FOUR]];
		[betView[DONT_COME] addBetsFrom:betView[DONT_COME_FIVE]];
		[betView[DONT_COME] addBetsFrom:betView[DONT_COME_SIX]];
		[betView[DONT_COME] addBetsFrom:betView[DONT_COME_EIGHT]];
		[betView[DONT_COME] addBetsFrom:betView[DONT_COME_NINE]];
		[betView[DONT_COME] addBetsFrom:betView[DONT_COME_TEN]];
    } 
	else if(roll == 11) 
	{
		[betView[COME] payBets];
		[betView[DONT_COME] clearBets];
    } 
	else if (roll == 2 || roll == 3 || roll == 12) 
	{
		[betView[COME] clearBets];
		if(roll != 12)
			[betView[DONT_COME] payBets];
	} 
	else 
	{
		switch(roll) 
		{
			case 4:
				[[betView[COME_FOUR] payBets] sumColumnsToRow:CRAPS_FLATWAGER];
				[betView[COME] tradeBetsWith:betView[COME_FOUR]];
				[betView[DONT_COME_FOUR] getBetsFrom:betView[DONT_COME]];
				break;
			case 5:
				[[betView[COME_FIVE] payBets] sumColumnsToRow:CRAPS_FLATWAGER];
				[betView[COME] tradeBetsWith:betView[COME_FIVE]];
				[betView[DONT_COME_FIVE] getBetsFrom:betView[DONT_COME]];
				break;
			case 6:
				[[betView[COME_SIX] payBets] sumColumnsToRow:CRAPS_FLATWAGER];
				[betView[COME] tradeBetsWith:betView[COME_SIX]];
				[betView[DONT_COME_SIX] getBetsFrom:betView[DONT_COME]];
				break;
			case 8:
				[[betView[COME_EIGHT] payBets] sumColumnsToRow:CRAPS_FLATWAGER];
				[betView[COME] tradeBetsWith:betView[COME_EIGHT]];
				[betView[DONT_COME_EIGHT] getBetsFrom:betView[DONT_COME]];
				break;
			case 9:
				[[betView[COME_NINE] payBets] sumColumnsToRow:CRAPS_FLATWAGER];
				[betView[COME] tradeBetsWith:betView[COME_NINE]];
				[betView[DONT_COME_NINE] getBetsFrom:betView[DONT_COME]];
				break;
			case 10:
				[[betView[COME_TEN] payBets] sumColumnsToRow:CRAPS_FLATWAGER];
				[betView[COME] tradeBetsWith:betView[COME_TEN]];
				[betView[DONT_COME_TEN] getBetsFrom:betView[DONT_COME]];
				break;
		}
    }

    // check place bets
    if(roll==4)
		[betView[PLACE_FOUR] payBets];
    else if(roll == 7)
		[betView[PLACE_FOUR] clearBets];
    
    if(roll==5)
		[betView[PLACE_FIVE] payBets];
    else if(roll == 7)
        [betView[PLACE_FIVE] clearBets];
    
    if(roll==6)
		[betView[PLACE_SIX] payBets];
    else if(roll == 7)
        [betView[PLACE_SIX] clearBets];

    if(roll==8)
		[betView[PLACE_EIGHT] payBets];
    else if(roll == 7)
        [betView[PLACE_EIGHT] clearBets];
    
    if(roll==9)
		[betView[PLACE_NINE] payBets];
    else if(roll == 7)
        [betView[PLACE_NINE] clearBets];
    
    if(roll==10)
		[betView[PLACE_TEN] payBets];
    else if(roll == 7)
        [betView[PLACE_TEN] clearBets];
    
    // check hardways
    if(roll == 6 && die1 == die2) 
		[betView[HARD_SIX] payBets];
    else if(roll == 7 || (roll == 6 && die1 != die2)) 
        [betView[HARD_SIX] clearBets];
     
    if(roll == 10 && die1 == die2) 
		[betView[HARD_TEN] payBets];
    else if(roll == 7 || (roll == 10 && die1 != die2)) 
        [betView[HARD_TEN] clearBets];
    
    if(roll == 8 && die1 == die2) 
		[betView[HARD_EIGHT] payBets];
    else if(roll == 7 || (roll == 8 && die1 != die2)) 
        [betView[HARD_EIGHT] clearBets];
    
    if(roll == 4 && die1 == die2) 
		[betView[HARD_FOUR] payBets];
    else if(roll == 7 || (roll == 4 && die1 != die2)) 
        [betView[HARD_FOUR] clearBets];

    // check field
    if(roll==2 || roll==3 || roll==4 || roll==9 || roll==10 || roll==11 || roll==12)
	{
		if(roll==2 || roll==12) // pay double (special case)
		{
			[betView[FIELD] setPayoff:2 cost:1];
			[betView[FIELD] payBets];
			[betView[FIELD] setPayoff:1 cost:1];
		}
		else
			[betView[FIELD] payBets];
	}
    else
        [betView[FIELD] clearBets];
    
    
    // check craps, seven
    if(roll==7)
		[betView[ANY_SEVEN] payBets];
    else
        [betView[ANY_SEVEN] clearBets]; 
    
    if(roll == 2 || roll == 3 || roll == 12)
		[betView[ANY_CRAPS] payBets];
    else
        [betView[ANY_CRAPS] clearBets]; 
    

    // check others
    if(roll == 3)
		[betView[THREE] payBets];
    else
        [betView[THREE] clearBets]; 
    
    if(roll == 2)
		[betView[TWO] payBets];
    else
        [betView[TWO] clearBets]; 
    
    if(roll == 12)
		[betView[TWELVE] payBets];
    else
        [betView[TWELVE] clearBets]; 
    
    if(roll == 11)
		[betView[ELEVEN] payBets];
    else
        [betView[ELEVEN] clearBets]; 
    
    if(roll == 6)
		[betView[BIG_SIX] payBets];
    else
        [betView[BIG_SIX] clearBets]; 
    
    if(roll == 8)
		[betView[BIG_EIGHT] payBets];
    else
        [betView[BIG_EIGHT] clearBets]; 
	
    //if(roll==2 || roll==3 || roll==11 || roll==12) 
	//	[betView[HORN] payBets];
    //else
    //    [betView[HORN] clearBets]; 
        
    return self;
}

- gameWasWon:(BOOL)won
/*
	increments numWins or numLoses, and redisplays the winLoseForm.
*/
{
	char buf[512];
	
	if(won)
		numWins++;
	else
		numLoses++;
	
	sprintf(buf, "Number of Wins: %d", numWins);
	[numWinsText setStringValue:buf];
	sprintf(buf, "Number of Loses: %d", numLoses);
	[numLosesText setStringValue:buf];
	
	return self;
}

- (int)getRoll:(int *)roll
/*
	get the rolls and determine if the shooter won, lost, or continues
	return LOOSER if roll lost, WINNER if it one, or the roll if neither.
*/
{
    int die1, die2;
    
    [dice getDiceValues:&die1 :&die2];
    
    *roll = die1 + die2;
    
    if(*roll == point) 
	{
        [self say:"Winner!!!   Coming Out... Place Your Bets!"];
		return WINNER;
    }
    
    switch(*roll) 
	{
		case 2:
			if(point == 0) 
			{
				[self say:"Two Out!   Coming Out... Place Your Bets!"];
				return LOOSER;
			} 
			else 
				[self say:"Two! Hard Two!"];
			break;
		case 3:
			if(point == 0) 
			{
				[self say:"Craps Three Out!   Coming Out... Place Your Bets!"];
				return LOOSER;
			} 
			else 
				[self say:"Craps Three!"];
			break;
		case 4:
			if(die1 == die2) 
				[self say:"Hard Four!"];
			else 
				[self say:"Shooter Rolled a Four!"];
			break;
		case 5:
			[self say:"Five! A No Field Five!"];
			break;
		case 6:
			if(die1 == die2) 
				[self say:"Hard Six!"];
			else 
				[self say:"Shooter Rolled a Six!"];
			break;
		case 7:
			if(point == 0) 
			{
				[self say:"Winner!! Seven!!   Coming Out... Place Your Bets!"];
				return WINNER;
			} 
			else 
			{
				[self say:"Seven Out!   Coming Out... Place Your Bets!"];
				return LOOSER;
			} 
			break;
		case 8:
			if(die1 == die2) 
				[self say:"Hard Eight!"];
			else 
				[self say:"Shooter Rolled an Eight!"];
			break;
		case 9:
			[self say:"Nine!  Shooter Rolled a Nine"];
			break;
		case 10:
			if(die1 == die2) 
				[self say:"Hard Ten!"];
			else 
				[self say:"Shooter Rolled a Ten!"];
			break;
		case 11:
			if(point == 0) 
			{
				[self say:"Natural Eleven!   Coming Out... Place Your Bets!"];
				return WINNER;
			} 
			else 
				[self say:"EE-YOH Eleven!"];
			break;
		case 12:
			if(point == 0) 
			{
				[self say:"Twelve Out!   Coming Out... Place Your Bets!"];
				return LOOSER;
			} 
			else 
			{
				[self say:"Twelve The Hard Way!"];
				break;
			}
    }
    
    return *roll;
}

- (int) point
/*
	returns the point, or 0 if the point is off.
*/
{
    return point;
}

- (struct proposition *) proposition:(int)propNum
/*
	return the proposition at propNum
*/
{
    return &prop[propNum];
}

- resetChips
/*
	Called when ever a new player enters or leaves the game.  We simply ask each
	view to reset its chips to reflect the current number of players.
*/
{
    int i, p;
    
    for(i=0; i<NUM_PROPS; i++)
	{
		[[betView[i] setNumCols:[self numPlayers]] positionColumns];
		
		if([betView[i] viewType] == ALLOWS_ODDS)
		{
			float fx, fy, ox, oy;
			
			for(p=0; p<MAX_PLAYERS; p++)
			{
				[betView[i] getChipPosition:&fx :&fy atRow:CRAPS_FLATWAGER col:p];
				[betView[i] getChipPosition:&ox :&oy atRow:CRAPS_ODDSWAGER col:p];
				[betView[i] setChipPosition:fx+CRAPS_ODDSCHIPXOFFSET :oy
					atRow:CRAPS_ODDSWAGER col:p];
			}
		}
	}

    return self;
}

- takeBetsOff:(BOOL)flag
{
	int i, p;
	
	for(i=COME_FOUR; i<=COME_TEN; i++)
	{
		for(p=0; p<MAX_PLAYERS; p++)
		{
			[betView[i] setOff:flag atRow:CRAPS_ODDSWAGER col:p];
		}
	}
	for(i=PLACE_FOUR; i<=PLACE_TEN; i++)
	{
		for(p=0; p<MAX_PLAYERS; p++)
		{
			[betView[i] setOff:flag atRow:CRAPS_FLATWAGER col:p];
		}
	}
	
	return self;
}

- trueOdds:(int *)p for:(int *)c forRoll:(int)roll
/*
 *  return true odds for the roll.  If roll is negative, find true odds that 
 *  that roll won't happen.  Return values in *p and *c.
 */
{
    *p = *c = 0;
    
    switch(roll) 
	{
        case 4:		*p = 2; *c = 1;	break;
        case 5:		*p = 3; *c = 2;	break;
        case 6:		*p = 6; *c = 5;	break;
        case 8:		*p = 6; *c = 5;	break;
        case 9:		*p = 3; *c = 2;	break;
        case 10:	*p = 2; *c = 1;	break;
        case -4:	*p = 1; *c = 2;	break;
        case -5:	*p = 2; *c = 3;	break;
        case -6:	*p = 5; *c = 6;	break;
        case -8:	*p = 5; *c = 6;	break;
        case -9:	*p = 2; *c = 3;	break;
        case -10:	*p = 1; *c = 2;	break;
    }
    return self;
}

- enableViews:(BOOL)flag
{
	int i;
	
	for(i=0; i<NUM_PROPS; i++)
		[betView[i] setEnabled:flag];
	
	return self;
}

- toggleAutoRoll:sender
{
	if(isAutoRoll)
	{
		// Turn off autoroll
		if(autoRollTE) DPSRemoveTimedEntry(autoRollTE);
		[countDownText setStringValue:""];
		isAutoRoll = NO;
		autoRollTE = 0;
		[dice setEnabled:YES];
	}
	else
	{
		// Turn on autoroll
		[dice setEnabled:NO];
		isAutoRoll = YES;
		autoRollCount = 0;
		if(autoRollTE) DPSRemoveTimedEntry(autoRollTE);
		autoRollTE = 0;
		[self clockTick];    	
	}
	
	return self;
}

- clockTick
{
	
	if(autoRollCount == 0)
	{
		[countDownText setStringValue:""];
		// roll the dice
		[dice roll:self];
	}
	else 
		[countDownText setIntValue:autoRollCount];
	
	autoRollCount--;
		
	return self;
}

- beginCountDown
{
	[self enableViews:YES];
	if(isAutoRoll)
	{
		if(autoRollTE) DPSRemoveTimedEntry(autoRollTE);
		autoRollSecs = [autoRollSlider intValue];
		autoRollCount = autoRollSecs;
		autoRollTE = DPSAddTimedEntry(1.0, (DPSTimedEntryProc)autoRollHandler, self, NX_BASETHRESHOLD);
	}
	
	return self;
}

- endCountDown
{
	[self enableViews:NO];
	if(isAutoRoll)
	{
		if(autoRollTE) DPSRemoveTimedEntry(autoRollTE);
		autoRollTE = 0;
		[countDownText setStringValue:""];
	}
	
	return self;
}
 
/**********************************************************************************  
 *                               Player Methods                                   *
 **********************************************************************************/
- playerDidClose:player
/*
	Over writes NVDealer's playerDidClose: method.  Reset chips, set new shooter if
	necessary, and redisplay the table.
*/
{
	
	[self resetChips];
	if(player == shooter) [self setNewShooter];
	[table display];
	
	return self;
}

- playerDidJoin:player
/*
	Over writes NVDealer's playerDidJoin: method.  Reset chips, set new shooter if
	necessary, and redisplay the table.
*/
{
	[self resetChips];
	if(shooter == nil) [self setShooter:player];
	[table display];
	
	return self;
}

- playerDidThrow:sender
/*
	Player threw dice and a the dice have finished rolling.  Check if it was a winning
	roll or losing roll, or neither.  Set text accordingly.  Have players check their
	bets against the dice.
 */
{
    int roll, status, p=1, c=1, i;
    int die1, die2;
    
    [dice getDiceValues:&die1 :&die2];
    
    status = [self getRoll:&roll];
    
    if(status == LOOSER) 
	{
		[pitBoss playSound:NV_LOSESOUND];
		[self gameWasWon:NO];
    } 
	else if(status == WINNER) 
	{
		[pitBoss playSound:NV_WINSOUND];
		[self gameWasWon:YES];
    } 
    
    [self checkBets:die1 :die2 status:status];
    
    if(status == LOOSER || status == WINNER )
	{ 
		if(point != 0 && pointView)
		// take point off
		{
			point = 0;
			[betView[pointView] puckOn:NO];
			[betView[DONT_COME] puckOn:YES];
			
			[self takeBetsOff:YES];
			
		}
			
	}
    else
        if(point == 0) 
		{
			point = roll;
			switch(roll)
			{
				case 4: pointView = COME_FOUR; p=2; c=1; break;
				case 5: pointView = COME_FIVE; p=3; c=2; break;
				case 6: pointView = COME_SIX; p=6; c=5; break;
				case 8: pointView = COME_EIGHT; p=6; c=5; break;
				case 9: pointView = COME_NINE; p=3; c=2; break;
				case 10: pointView = COME_TEN; p=2; c=1; break;
			}
			[betView[DONT_COME] puckOn:NO];
			[betView[pointView] puckOn:YES];
			
			for(i=0; i<MAX_PLAYERS; i++)
			{
				[betView[PASS] setPayoff:p cost:c atRow:CRAPS_ODDSWAGER col:i];
				[betView[DONT_PASS] setPayoff:c cost:p atRow:CRAPS_ODDSWAGER col:i];
			}
			
			[self takeBetsOff:NO];
		}
    
    [table display];
    
    if(roll == 7 && status == LOOSER)
		[self setNewShooter];
		    
	[self enableViews:YES];
	
	[self beginCountDown];
       
	return self;
}


- (BOOL) playerWillThrow:sender
/*
 	here in case I need to stop a player from betting.  Make sure that each bet
	is within tableMin and tableMax.  Return NO if there is a bet that isn't okay,
	YES if all bets are okay and the player is free to throw.
 */
{
    // make sure that all bets are at least the minimum
    [self say:"Checking Bets..."];
    if([self anyBetsTooLow] || [self anyBetsTooHigh])
	{
        [self beginCountDown];
		return NO;
	}
    
    [self say:"Shooter rolls..."];
	
	[self endCountDown];
	
	return YES;
}


- setNewShooter
/*
	Called when ever a shooter rolls a losing 7.  We need to find the next shooter to
	roll the dice.  If there are 0 players, return nil.  If there is only one player,
	set that player as the shooter.  If there are more than one players, find the next
	shooter by asking each one after the current shooter if they want to roll the dice
	until one of them responds that they do.
*/
{
    int nextPlayerTag, numPlayers = [pitBoss numPlayers];
	id newShooter;
    
	if([PBoss() appIsTerminating])
		return self;
		
    if(numPlayers == 0)
        newShooter = nil;
    else if(numPlayers == 1) 
	{
		nextPlayerTag = 0;
		while([pitBoss playerAt:nextPlayerTag] == nil)
			nextPlayerTag++;
		newShooter = [pitBoss playerAt:nextPlayerTag];
    } 
	else 
	{
		nextPlayerTag = [shooter tag];
		do 
		{
			nextPlayerTag++;
			if(nextPlayerTag == MAX_PLAYERS)
				nextPlayerTag = 0;
		} while ([pitBoss playerAt:nextPlayerTag] == nil);
        
		if(!isAutoRoll)
		{
			while(NXRunAlertPanel("New Shooter!", "%s, do you wish to roll the dice?",
					"Sure", "Maybe Next Time...", NULL, 
					[[pitBoss playerAt:nextPlayerTag] playerName]) == NX_CANCELTAG) 
			{
				do 
				{
					nextPlayerTag++;
					if(nextPlayerTag == MAX_PLAYERS)
						nextPlayerTag = 0;
				} while ([pitBoss playerAt:nextPlayerTag] == nil);
			}
		}

		newShooter = [pitBoss playerAt:nextPlayerTag];
    }
    
	[self setShooter:newShooter];
	
    return self;
}

- setShooter:aPlayer
/*
	Just sets aPlayer to be the shooter, and updates the shooterText.
*/
{
	shooter = aPlayer;
	
	if([pitBoss numPlayers] > 0)
        [shooterText setStringValue:[shooter playerName]];
    else
        [shooterText setStringValue:""];
    
    return self;
}


/**********************************************************************************  
 *                           Preferences Type Methods                             *
 **********************************************************************************/
- preferencesChanged:sender
/*
	Target of items on inspector.  I should check to make sure that the player
	has given us integers to work with, but for now, we'll let it slide.
*/
{
	[self setPreferences:sender];
		
	return self;
}

- (int)tableOdds
{
    return tableOdds;
}

- revertPreferences:sender
{
    char buf[128];
	
	autoRollCount = atoi(NXGetDefaultValue(CRAPS_NAMEFORDEFAULTS, "autoRollPref"));
	tableOdds = atoi(NXGetDefaultValue(CRAPS_NAMEFORDEFAULTS, "oddsPref"));
	
	[self selectCellWithTag:tableOdds forPopUpButton:prefOddsButton];
	
	[autoRollSlider setIntValue:autoRollCount];
	[autoRollText setIntValue:autoRollCount];
	
    sprintf(buf, "%dX Odds", tableOdds);
    [tableOddsText setStringValue:buf];

	return self;
}

- setPreferences:sender
{
    static NXDefaultsVector newDefaults = {
        {"oddsPref", "1"},
		{"autoRollPref", "5"},
		{NULL, NULL}
    };
    char buf[128];
    
	autoRollCount = [autoRollText intValue];
	tableOdds = [[[[prefOddsButton target] itemList] selectedCell] tag];
	
    newDefaults[0].value = alloca(256);
    newDefaults[1].value = alloca(256);

	sprintf(newDefaults[0].value, "%d", tableOdds);
	sprintf(newDefaults[1].value, "%d", autoRollCount);

    NXWriteDefaults(CRAPS_NAMEFORDEFAULTS, newDefaults);
    
    sprintf(buf, "%dX Odds", tableOdds);
    [tableOddsText setStringValue:buf];
   
	return self;
}


/**********************************************************************************  
 *                             Other NVDealer Methods                             *
 **********************************************************************************/
- (int)collectAllBetsForPlayer:(int)playerNum
/*
	Over writes NVDealer's collectAllBetsForPlayer which is sent to us when the player
	pushes the "Collect All Bets" button on their window.  We have to get each bet
	that they have placed on the table, and return the total amount that they had
	on the table.  We don't allow bets on pass, don't pass, come, or don't come to be
	removed after the point has been established.
*/
{
    int p, total;
    
    total = 0;
    
    for(p=0; p<NUM_PROPS; p++) 
	{
		// dont allow pass bets, or come bets to be removed after their point
		// has been established.
		if(prop[p].type != COME_FOUR && prop[p].type != COME_FIVE && 
		   prop[p].type != COME_SIX  && prop[p].type != COME_EIGHT &&
		   prop[p].type != COME_NINE && prop[p].type != COME_TEN) 
		{
			if(prop[p].type != PASS || (prop[p].type == PASS && point == 0)) 
			{
				total += [betView[p] amountAtRow:CRAPS_FLATWAGER col:playerNum];
				[betView[p] setOff:NO atRow:CRAPS_FLATWAGER col:playerNum];
				[betView[p] clearBetAtRow:CRAPS_FLATWAGER col:playerNum];
				
			}
		}
		total += [betView[p] amountAtRow:CRAPS_ODDSWAGER col:playerNum];
		[betView[p] setOff:NO atRow:CRAPS_ODDSWAGER col:playerNum];
		[betView[p] clearBetAtRow:CRAPS_ODDSWAGER col:playerNum];
    }
    	
    [table display];
    
    return total;
}

- (BOOL)hasRules
/*
	returns YES if we have rules, NO if we don't.  We do.
*/
{
	return YES;
}


- view:aView wasLoadedOnTable:tableObject
/*
	Over writes NVDealer's view:wasLoaded... method so I can set tracking rects for my 
	views...
 */
{
	int i;
	
	[super view:aView wasLoadedOnTable:tableObject];
	
    for(i=0; i<NUM_PROPS; i++) 
	{
       [[[[[[[betView[i] init] 
						 setTag:i] 
						 setTrackingRectForWindow:tableObject]
						 setNumCols:[self numPlayers]]
						 positionColumns]
						 setEnabled:YES]
						 setSoundEnabled:YES];
						
    }
	
	[self setPayoffs];
	
	[self resetChips];
	
	[self say:"Coming out...  Place your bets..."];
	
	return self;
}

@end

@implementation Craps(BetViewDelegate)

- mouseEnteredView:sender
/*
	When a mouse enters a tableView, it sends this message to us.  We use it to
	update the tableAdvice text field.
*/
{
    char buf[256];
	int viewTag = [sender tag];
        
	if(viewTag >= 0) 
	{
		sprintf(buf, "%s:  %s", prop[viewTag].name, prop[viewTag].advice);
		[tableAdvice setStringValue:buf];
    } 
	else 
	{
        [tableAdvice setStringValue:""];
    }
    return self;
}

- player:aPlayer willBet:(int)betType onView:aBetView
{
	int propType = [aBetView tag], playerTag = [aPlayer tag];
	int amount = [aPlayer selectedChip], prevAmount, prevOdds;
	char buf[256];
	BOOL isOddsBet = NO;
	
	if(aPlayer == nil) return nil;
	
	//[aBetView getBet:&thisBet atRow:CRAPS_FLATWAGER col:playerTag];
	prevAmount = [aBetView amountAtRow:CRAPS_FLATWAGER col:playerTag];
	prevOdds = [aBetView amountAtRow:CRAPS_ODDSWAGER col:playerTag];
	
	switch(betType)
	{
		case NV_BETOFF:
			if(prevOdds > 0)
				[aBetView switchBetOnOffAtRow:CRAPS_ODDSWAGER col:playerTag];
			else if(prevAmount>0)
				[aBetView switchBetOnOffAtRow:CRAPS_FLATWAGER col:playerTag];
			break;
			
//////////////////////////
//		CASE BET        //
//////////////////////////
		case NV_BET:
			// don't allow to increase flat wager once point is established
			if(((propType==PASS || propType==DONT_PASS) && point!=0 && prevAmount==0) ||
				(!(propType == PASS || propType == DONT_PASS) && 
				[aBetView viewType]==ALLOWS_ODDS && prevAmount == 0))
			{
				return nil;
			}
			
			// This is an odds bet if:
			//		1.  The view is either PASS or DONT_PASS and point has been est.
			//		2.  The view isn't either PASS or DONT_PASS, and there's already
			//				an amount set for the flat wager.
			if(((propType==PASS || propType==DONT_PASS) && point!=0 && prevAmount>0) ||
				(!(propType == PASS || propType == DONT_PASS) && 
				[aBetView viewType]==ALLOWS_ODDS && prevAmount > 0))
			{
				isOddsBet = YES;
			}
			
			// use default bet if user wants
			if(amount == DEFAULT)
			{ 
				if(isOddsBet)
				{
					amount = prevAmount;
				}
				else
				{
					amount = (prop[propType].cost>tableMin) ? prop[propType].cost : tableMin;
					// special case HORN
					//if(propType == HORN)
					//	amount = prop[propType].cost * tableMin;
				}
			}
			
			// don't allow more than nX odds
			if(isOddsBet && (amount+prevOdds > prevAmount*tableOdds))
			{
				[pitBoss playSound:NV_WARNSOUND];
				sprintf(buf, "Only %dX odds allowed!", tableOdds);
				[self say:buf];
				return nil;
			}
				
			// if this bet is more than the player has in chips, or if units would put
			// the bet above the table maximum, do not allow it.
			if(amount > [aPlayer amountInBank]) 
			{
				[pitBoss playSound:NV_WARNSOUND];
				sprintf(buf, "Can't bet more than you have!");
				[self say:buf];
				return nil;
			}
			
			if(amount == 0)
				return self;
			
			// Place the bet
			if(isOddsBet)
			{
				if([aPlayer removeChip:amount])
				{
					[aBetView bet:amount atRow:CRAPS_ODDSWAGER col:playerTag];
					[aPlayer removeChip:amount];
					sprintf(buf, "Dealer accepts %s's $%d odds wager on %s.", 
						[aPlayer playerName], prevOdds+amount, prop[propType].name);
					[self say:buf];
				}
			}
			else
			{
				if([aPlayer removeChip:amount])
				{
					[aBetView bet:amount atRow:CRAPS_FLATWAGER col:playerTag];
					sprintf(buf, "Dealer accepts %s's $%d wager on %s.", 
						[aPlayer playerName], prevAmount+amount, prop[propType].name);
					[self say:buf];
				}
			}
			
			
			break;
			
//////////////////////////
//	  CASE REMOVEBET    //
//////////////////////////
		case NV_REMOVEBET:
			// This is an odds bet if:
			//		1.  The view allows odds and there is a previous odds value
			if([aBetView viewType]==ALLOWS_ODDS && prevOdds > 0)			
			{
				isOddsBet = YES;
			}
			
			if(isOddsBet && prevOdds <= 0)
				return self;
			else if(!isOddsBet && prevAmount <= 0)
				return self;
			
			// Don't allow user to decrease flat bets once point is established
			if(!isOddsBet)
			{
				if([aBetView viewType] == ALLOWS_ODDS && propType!=PASS && propType!=DONT_PASS)
				{
					[PBoss() playSound:NV_WARNSOUND];
					sprintf(buf, "You are not allowed to remove a flat wager on %s!",
						 prop[propType].name);
					[self say:buf];
					return nil;
				}
				else if((propType==PASS || propType==DONT_PASS) && point != 0)
				{
					[PBoss() playSound:NV_WARNSOUND];
					sprintf(buf, "You are not allowed to remove a flat wager on %s once the point has been established!",
						 prop[propType].name);
					[self say:buf];
					return nil;
				}
			}
			
			// Set the amount if user wants default
			//    if odds, default subtracts all of the odds.
			//    if not odds, default subtracts all of flat bet.
			if(amount == DEFAULT)
			{
				if(isOddsBet)
				{
					amount = prevOdds;
				}
				else
				{
					amount = prevAmount;
				}
			} 
			
			// Don't allow player to subtract more than they've bet
			if(isOddsBet)
			{
				if(amount > prevOdds) 
					amount = prevOdds;
			}
			else
			{
				if(amount > prevAmount) 
					amount = prevAmount;
			}
				
			if(amount == 0)
				return self;
				
			// Place the bet
			if(isOddsBet)
				[aBetView removeBet:amount atRow:CRAPS_ODDSWAGER col:playerTag];
			else
				[aBetView removeBet:amount atRow:CRAPS_FLATWAGER col:playerTag];
			[aPlayer addChip:amount];
			
			sprintf(buf, "%s removed $%d from %s.", 
				[aPlayer playerName], amount, prop[propType].name);
			[self say:buf];
			
			break;
			
		case NV_REMOVEALL:
			// This is an odds bet if:
			//		1.  The view allows odds and there is a previous odds value
			if([aBetView viewType]==ALLOWS_ODDS && prevOdds > 0)			
			{
				isOddsBet = YES;
			}
			
			if(isOddsBet && prevOdds <= 0)
				return self;
			else if(!isOddsBet && prevAmount <= 0)
				return self;
			
			// Don't allow user to decrease flat bets once point is established
			if(!isOddsBet)
			{
				if([aBetView viewType] == ALLOWS_ODDS && propType!=PASS && propType!=DONT_PASS)
				{
					[PBoss() playSound:NV_WARNSOUND];
					sprintf(buf, "You are not allowed to remove a flat wager on %s!",
						 prop[propType].name);
					[self say:buf];
					return nil;
				}
				else if((propType==PASS || propType==DONT_PASS) && point != 0)
				{
					[PBoss() playSound:NV_WARNSOUND];
					sprintf(buf, "You are not allowed to remove a flat wager on %s once the point has been established!",
						 prop[propType].name);
					[self say:buf];
					return nil;
				}
			}
			
			// Don't allow player to subtract more than they've bet
			if(isOddsBet)
			{
				amount = prevOdds;
			}
			else
			{
				amount = prevAmount;
			}
				
			if(amount == 0)
				return self;
				
			// Place the bet
			if(isOddsBet)
				[aBetView removeBet:amount atRow:CRAPS_ODDSWAGER col:playerTag];
			else
				[aBetView removeBet:amount atRow:CRAPS_FLATWAGER col:playerTag];
			[aPlayer addChip:amount];
			
			sprintf(buf, "%s removed $%d from %s.", 
				[aPlayer playerName], amount, prop[propType].name);
			[self say:buf];
			
			break;
		
		default:
			NXBeep();
			[self say:"That action is not allowed in this craps game..."];
			break;
	}
	return self;
}

- (BOOL)playerWillDragChipAtRow:(int)row andCol:(int)col fromView:sender
{
	int propType = [sender tag];
	int prevOdds;
	
	 prevOdds = [sender amountAtRow:CRAPS_ODDSWAGER col:col];
	
	// Don't allow player to move flat wagers when odds are on bet.
	if([sender viewType]==ALLOWS_ODDS && row==CRAPS_FLATWAGER && prevOdds>0)
	{
		return NO;
	}
	
	// Don't allow player to move flat bets once their points are established
	if(((propType==PASS || propType==DONT_PASS) && point!=0 && row==CRAPS_FLATWAGER) ||
		(!(propType == PASS || propType == DONT_PASS) && 
		[sender viewType]==ALLOWS_ODDS && row==CRAPS_FLATWAGER))
	{
		return NO;
	}
				
	return YES;
}

- (BOOL)playerWillDropChipAtRow:(int *)row andCol:(int *)col InView:sender;
{
	int propType = [sender tag];
	int prevAmount;
	BOOL isOddsBet = NO;
		
	if(*col == BV_GENERICPLAYERSOURCE)
	{
		*col = [[PBoss() currentPlayer] tag];
	}

	prevAmount = [sender amountAtRow:CRAPS_FLATWAGER col:*col];

	if(((propType==PASS || propType==DONT_PASS) && point!=0 && prevAmount>0) ||
		(!(propType == PASS || propType == DONT_PASS) && 
		[sender viewType]==ALLOWS_ODDS && prevAmount > 0))
	{
		isOddsBet = YES;
	}
	
	if(isOddsBet)
	{
		*row = CRAPS_ODDSWAGER;
	}
	else
	{
		*row = CRAPS_FLATWAGER;
	}

	if([sender viewType] != ALLOWS_ODDS)
		return YES;
	
	if(((propType==PASS || propType==DONT_PASS) && point!=0 && prevAmount>0) ||
		(!(propType == PASS || propType == DONT_PASS) && 
		[sender viewType]==ALLOWS_ODDS && prevAmount > 0))
	{
		return YES;
	}
	
	if((propType==PASS || propType==DONT_PASS) && point==0)
		return YES;
	
	return NO;
}

- playerDroppedChipAtRow:(int)row andCol:(int)col inView:sender
{
/*
	int propType = [sender tag];
	BOOL isOddsBet = NO;
	int prevAmount;
		
	if(*col == BV_GENERICPLAYERSOURCE)
	{
		*col = [[PBoss() currentPlayer] tag];
	}

	prevAmount = [sender amountAtRow:CRAPS_FLATWAGER col:*col];

	if(((propType==PASS || propType==DONT_PASS) && point!=0 && prevAmount>0) ||
		(!(propType == PASS || propType == DONT_PASS) && 
		[sender viewType]==ALLOWS_ODDS && prevAmount > 0))
	{
		isOddsBet = YES;
	}
	
	if(isOddsBet)
	{
		*row = CRAPS_ODDSWAGER;
	}
	else
	{
		*row = CRAPS_FLATWAGER;
	}
*/	
	return self;
}


@end

