
#import "Baccarat.h"

#define BCT_NUMDECKS 8

#define BCT_BANKERHAND 0
#define BCT_PLAYERHAND 1

#define BCT_PLAYERVIEW 0
#define BCT_BANKVIEW 1
#define BCT_TIEVIEW 2

#define BCT_DRAWPAUSE 200000
#define BCT_LONGPAUSE 999999

#define BCT_MAXPLAYERS 4

#define BCT_PLAYERTAG 0
#define BCT_TIETAG 10
#define BCT_BANKTAG 20

#define BCT_NAMEFORDEFAULTS "NVBaccarat"

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

@implementation Baccarat
/***************************************************************************************
 *                                 Module Methods                                      *
 ***************************************************************************************/
+ initialize
{
    static NXDefaultsVector ourDefaults = {
        {"numDecks", "8"},
		{NULL, NULL}
    };
    
    NXRegisterDefaults(BCT_NAMEFORDEFAULTS, ourDefaults);

	return self;
}

- awakeFromNib
/*
	Sent to every object instantiated in a nib file.  A good place to perform some
	initialization...
*/
{
 	int i;
	
	// This may take a while, so lets inform the user
	[self say:"Pre-Drawing Cards..."];
	NXPing();
	[Card drawCardImages];

	player[0].tieView = p1TieView;
 	player[1].tieView = p2TieView;
 	player[2].tieView = p3TieView;
 	player[3].tieView = p4TieView;
 	player[0].bankView = p1BankerView;
 	player[1].bankView = p2BankerView;
 	player[2].bankView = p3BankerView;
 	player[3].bankView = p4BankerView;
 	player[0].playerView = p1PlayerView;
 	player[1].playerView = p2PlayerView;
 	player[2].playerView = p3PlayerView;
 	player[3].playerView = p4PlayerView;
	player[0].nameText = p1NameText;
	player[1].nameText = p2NameText;
	player[2].nameText = p3NameText;
	player[3].nameText = p4NameText;
	
	pile[BCT_BANKERHAND].view = bankerCardView;
	[[[bankerCardView setOffset:60.0 :0.0] setCoversOthers:YES] setDrawOutline:NO];
	pile[BCT_BANKERHAND].pile = [bankerCardView cardPile];
	pile[BCT_BANKERHAND].text = bankerTotalText;
	pile[BCT_BANKERHAND].title = "Banker";
	pile[BCT_PLAYERHAND].view = playerCardView;
	[[[playerCardView setOffset:60.0 :0.0] setCoversOthers:YES] setDrawOutline:NO];
	pile[BCT_PLAYERHAND].pile = [playerCardView cardPile];
	pile[BCT_PLAYERHAND].text = playerTotalText;
	pile[BCT_PLAYERHAND].title = "Player";
	
	for(i=0; i<BCT_MAXPLAYERS; i++)
	{
		[[[[[[[[player[i].tieView setNumRows:1] setNumCols:1] positionChips] 
							     setSoundEnabled:YES] setEnabled:YES]
							     setPayoff:9 cost:1] setTag:i+BCT_TIETAG]
								 setViewType:BCT_TIEVIEW];
		[[[[[[[player[i].bankView setNumRows:1] setNumCols:1] positionChips] 
							     setSoundEnabled:YES] setEnabled:YES]
								 setTag:i+BCT_BANKTAG] setViewType:BCT_BANKVIEW];
		[[[[[[[player[i].playerView setNumRows:1] setNumCols:1] positionChips] 
							       setSoundEnabled:YES] setEnabled:YES]
								   setTag:i+BCT_PLAYERTAG] setViewType:BCT_PLAYERVIEW];
	}
	
	[self revertPreferences:self];
	
	deckPile = [deckView cardPile];
	[deckView setUseShoe:YES];
	
	[bankerText setStringValue:""];
	[playerText setStringValue:""];
	
	[self checkPiles];
	
	[self getPlayers];
	
	[self enableDealButton];
	
	return self;
}

- (BOOL)checkBets
{
	int i, tmax = [self tableMax], tmin = [self tableMin], amount;
	char buf[256];
	
	for(i=0; i<BCT_MAXPLAYERS; i++)
	{
		amount = [player[i].tieView amountAtRow:0 col:0];
		if(amount > tmax)
		{
			sprintf(buf, "%s, your $%d bet on TIE is over the $%d table maximum!",
				[player[i].player playerName], amount, tmax);
			[self say:buf]; NXPing();
			[PBoss() playSound:NV_WARNSOUND];
			return NO;
		}
		if(amount > 0 && amount < tmin)
		{
			sprintf(buf, "%s, your $%d bet on TIE is under the $%d table minimum!",
				[player[i].player playerName], amount, tmin);
			[self say:buf]; NXPing();
			[PBoss() playSound:NV_WARNSOUND];
			return NO;
		}
		amount = [player[i].bankView amountAtRow:0 col:0];
		if(amount > tmax)
		{
			sprintf(buf, "%s, your $%d bet on BANKER is over the $%d table maximum!",
				[player[i].player playerName], amount, tmax);
			[self say:buf]; NXPing();
			[PBoss() playSound:NV_WARNSOUND];
			return NO;
		}
		if(amount > 0 && amount < tmin)
		{
			sprintf(buf, "%s, your $%d bet on BANKER is under the $%d table minimum!",
				[player[i].player playerName], amount, tmin);
			[self say:buf]; NXPing();
			[PBoss() playSound:NV_WARNSOUND];
			return NO;
		}
		amount = [player[i].playerView amountAtRow:0 col:0];
		if(amount > tmax)
		{
			sprintf(buf, "%s, your $%d bet on PLAYER is over the $%d table maximum!",
				[player[i].player playerName], amount, tmax);
			[self say:buf]; NXPing();
			[PBoss() playSound:NV_WARNSOUND];
			return NO;
		}
		if(amount > 0 && amount < tmin)
		{
			sprintf(buf, "%s, your $%d bet on PLAYER is under the $%d table minimum!",
				[player[i].player playerName], amount, tmin);
			[self say:buf]; NXPing();
			[PBoss() playSound:NV_WARNSOUND];
			return NO;
		}
	}
	
	return YES;
}

- checkPiles
{
	if([deckPile cardCount] < 10)
	{
		[self newDeck:self];
	}
	
	[pile[BCT_BANKERHAND].pile empty];
	[pile[BCT_BANKERHAND].view display];
	[pile[BCT_BANKERHAND].text setIntValue:0];
	[pile[BCT_PLAYERHAND].pile empty];
	[pile[BCT_PLAYERHAND].view display];
	[pile[BCT_PLAYERHAND].text setIntValue:0];
	
	NXPing();
	
	return self;
}

- deal:sender
{
	if(dealTE)
		DPSRemoveTimedEntry(dealTE);
		
	if([self checkBets])
	{
		[dealButton setEnabled:NO];
	
		dealTE = DPSAddTimedEntry(.1, (DPSTimedEntryProc)BCT_RunGame, self, NX_BASETHRESHOLD);
	}
	
	return self;
}

- dealCardToHand:(int)hand
{
	id card;
	char buf[128];
	
	sprintf(buf, "Card For %s...", pile[hand].title);
	[self say:buf]; NXPing(); usleep(BCT_DRAWPAUSE);
	
	card = [deckPile cardAt:CS_TOP];
	[card setFaceUp:YES];
	
	[pile[hand].pile addCard:card];
	[deckPile removeCard:card];
	
	[deckView display];
	[pile[hand].view display];
	[self totalHand:hand];

	NXPing();
	[PBoss() playSound:NV_CARDSOUND];
	
	
	usleep(BCT_DRAWPAUSE);
	
	return self;
}

- enableDealButton
{
	int i;
	
	for(i=0; i<BCT_MAXPLAYERS; i++)
	{
		if([player[i].bankView amountAtRow:0 col:0] ||
		   [player[i].playerView amountAtRow:0 col:0] ||
		   [player[i].tieView amountAtRow:0 col:0])
		{
			[dealButton setEnabled:YES];
			return self;
		}
	}
	
	[dealButton setEnabled:NO];
	
	return self;
}

- getPlayers
{
	int i;
	id	thePlayer;
	
	for(i=0; i<BCT_MAXPLAYERS; i++)
	{
		if((thePlayer = [PBoss() playerAt:i]) != nil)
		{
			[player[i].nameText setStringValue:[thePlayer playerName]];
			player[i].player = thePlayer;
		}
		else
		{
			[player[i].nameText setStringValue:""];
			player[i].player = nil;
		}
	}
	
	return self;
}

- newDeck:sender
{
	id theMatrix, card;
	int numDecks, i;
	
	theMatrix = [[deckPrefButton target] itemList];
	numDecks = [[theMatrix selectedCell] tag]; 
	if(numDecks == 0) numDecks = 1;

	[self newDeck:numDecks forView:deckView];
			
	for(i=0; i<[deckPile cardCount]; i++)
	{
		card = [deckPile cardAt:i];
		
		switch([card value])
		{
			case CS_TEN:    [card setUserValue:0]; break;
			case CS_JACK:   [card setUserValue:0]; break;
			case CS_QUEEN:  [card setUserValue:0]; break;
			case CS_KING:   [card setUserValue:0]; break;
			default:  [card setUserValue:[card value]+1]; break;
		}
	}
	
	return self;
}

- runGame
{
	int ptotal, btotal, pthirdCard=-1, i;
	BOOL playerDrewThird=NO;
	
	DPSRemoveTimedEntry(dealTE);
	dealTE=0;
	
	[bankerText setStringValue:""];
	[playerText setStringValue:""];
	
	// reset piles, initialize deck if needed...
	[self checkPiles];
	
	// Deal two cards to each hand
	[self dealCardToHand:BCT_PLAYERHAND];
	[self dealCardToHand:BCT_BANKERHAND];
	[self dealCardToHand:BCT_PLAYERHAND];
	[self dealCardToHand:BCT_BANKERHAND];
	
	// Get the total for each hand
	ptotal = pile[BCT_PLAYERHAND].total;
	btotal = pile[BCT_BANKERHAND].total;
	
	// See if player needs third card
	if(btotal != 8 && btotal != 9)
	{
		if(ptotal==1 || ptotal==2 || ptotal==3 || ptotal==4 || ptotal==5 || ptotal==0)
		{
			[self dealCardToHand:BCT_PLAYERHAND];
			playerDrewThird = YES;
			pthirdCard = [[pile[BCT_PLAYERHAND].pile cardAt:CS_TOP] userValue];
		}
	}
	
	// See if banker needs third card
	if(ptotal != 8 && ptotal != 9)
	{
		if(btotal==0 || btotal==1 || btotal==2)
		{
			[self dealCardToHand:BCT_BANKERHAND];
		}
		else if(btotal==3 || btotal==4 || btotal==5 || btotal==6)
		{
			switch(btotal)
			{
				case 3:
					if(!playerDrewThird || pthirdCard != 8)
					{
						[self dealCardToHand:BCT_BANKERHAND];
					}
					break;
				case 4:
					if(!playerDrewThird || 	(pthirdCard != 0 && pthirdCard != 1 && 
											 pthirdCard != 8 && pthirdCard != 9))
					{
						[self dealCardToHand:BCT_BANKERHAND];
					}
					break;
				case 5:
					if(!playerDrewThird || 	(pthirdCard != 0 && pthirdCard != 1 && 
											 pthirdCard != 2 && pthirdCard != 3 &&
											 pthirdCard != 8 && pthirdCard != 9))
					{
						[self dealCardToHand:BCT_BANKERHAND];
					}
					break;
				case 6:
					if(!playerDrewThird || (pthirdCard == 6 || pthirdCard == 7))
					{
						[self dealCardToHand:BCT_BANKERHAND];
					}
					break;
			}
		}
	} 
	 
	
	// Get the total for each hand
	ptotal = pile[BCT_PLAYERHAND].total;
	btotal = pile[BCT_BANKERHAND].total;

	// Now see which hand won, and pay accordingly...
	if(btotal > ptotal)
	{
		[self say:"Banker Hand Wins!"]; NXPing(); usleep(BCT_LONGPAUSE);
		for(i=0; i<BCT_MAXPLAYERS; i++)
		{
			[bankerText setStringValue:"Winner!"];
			[player[i].bankView payBets];
			// 5% commission...
			[player[i].bankView multiplyBetsBy:.95];
			[player[i].playerView clearBets];
			[player[i].tieView clearBets];
		}
		[self updateWinText:BCT_BANKVIEW];
	}
	else if(ptotal > btotal)
	{
		[self say:"Player Hand Wins!"]; NXPing(); usleep(BCT_LONGPAUSE);
		for(i=0; i<BCT_MAXPLAYERS; i++)
		{
			[playerText setStringValue:"Winner!"];
			[player[i].bankView clearBets];
			[player[i].playerView payBets];
			[player[i].tieView clearBets];
		}
		[self updateWinText:BCT_PLAYERVIEW];
	}
	else if(ptotal == btotal)
	{
		[self say:"Tie Game!"]; NXPing(); usleep(BCT_LONGPAUSE);
		for(i=0; i<BCT_MAXPLAYERS; i++)
		{
			[bankerText setStringValue:"Tie"];
			[playerText setStringValue:"Tie"];
			[player[i].tieView payBets];
		}
		[self updateWinText:BCT_TIEVIEW];
	}
	
	[PBoss() playSound:NV_CHIPSOUND];
	[self say:"New Game... Place Your Bets!"];
	
	[self enableDealButton];
	
	return self;
}

- totalHand:(int)hand
{
	int i, total=0;
	
	for(i=0; i<[pile[hand].pile cardCount]; i++)
	{
		total += [[pile[hand].pile cardAt:i] userValue];
	}
		
	if(total >=20) total -=20;
	else if(total >= 10) total -= 10;
	
	pile[hand].total = total;
	[pile[hand].text setIntValue:total];
	
	return self;
}

- updateWinText:(int)viewToUpdate
{
	id view = nil;
	
	switch(viewToUpdate)
	{
		case BCT_PLAYERVIEW:	view = playerWinText;  break;
		case BCT_BANKVIEW:		view = bankWinText; break;
		case BCT_TIEVIEW:		view = tieText; break;
	}
	
	[view setIntValue:[view intValue]+1];
	
	return self;
}


/***************************************************************************************
 *                            Inspector And Rules Methods                              *
 ***************************************************************************************/
- (BOOL)hasRules
/*
	NO if the module does not have a Rules Window, YES if it does.  The default
	method returns NO.
*/
{
	return YES;
}

- inspectorWillBeRemoved:sender
/*
	Sent when this module's inspector is about to be replace with another one.
*/
{
	return self;
}

- inspectorWasInstalled:sender
/*
	Sent just after this module's inspector has been installed.
*/
{
	return self;
}

/***************************************************************************************
 *                                  Player Methods                                     *
 ***************************************************************************************/
- (int)collectAllBetsForPlayer:(int)playerNum
{
	int total=0;
	
	total = [player[playerNum].tieView collectBetAtRow:0 col:0];
	total += [player[playerNum].bankView collectBetAtRow:0 col:0];
	total += [player[playerNum].playerView collectBetAtRow:0 col:0];
	
	[self enableDealButton];

	return total;
}

- playerDidBet:aPlayer
{
	return self;
}

- playerDidClose:aPlayer
/*
 *  Sent after a player has closed and is no longer part of the game.  No messages
 *  should be sent to aPlayer.  This method should just do some internal cleanup
 *  if necessary.
 */
{
	[self getPlayers];
	
	return self;
}

- playerDidJoin:player
{
	[self getPlayers];
	
	return self;
}

- (BOOL)playerWillClose:aPlayer
/*
 *  Should return YES if it is okay to close this player, NO if it is not.  The
 *  default implementation returns YES.
 */
{
    return YES;
}

- (BOOL)playerWillJoin:sender
{
	return YES;
}

/***************************************************************************************
 *                                  Table Methods                                      *
 ***************************************************************************************/
- view:aView wasLoadedOnTable:tableObject
{
	int i;
	
	[super view:aView wasLoadedOnTable:tableObject];
	
	[self say:"Welcome to Baccarat!  Place Your Bets!"];
	
	for(i=0; i<BCT_MAXPLAYERS; i++)
	{
		[player[i].tieView setTrackingRectForWindow:[PBoss() tableWindow]];
		[player[i].bankView setTrackingRectForWindow:[PBoss() tableWindow]];
		[player[i].playerView setTrackingRectForWindow:[PBoss() tableWindow]];
	}
	
	return self;
}


- revertPreferences:sender
{
	int numDecks = atoi(NXGetDefaultValue(BCT_NAMEFORDEFAULTS, "numDecks"));
	
	[self selectCellWithTag:numDecks forPopUpButton:deckPrefButton];
	
	return self;
}

- setPreferences:sender
{
    static NXDefaultsVector newDefaults = {
        {"numDecks", "8"},
		{NULL, NULL}
    };
	int numDecks = [[[[deckPrefButton target] itemList] selectedCell] tag];

    newDefaults[0].value = alloca(256);

	sprintf(newDefaults[0].value, "%d", numDecks);

    NXWriteDefaults(BCT_NAMEFORDEFAULTS, newDefaults);
       
	return self;
}


/***************************************************************************************
 *                               Other Misc. Methods                                   *
 ***************************************************************************************/
- finishSessionAndClose
/*
	This message is sent when a new game is being loaded.  If there is any clean up
	to do, this is where to do it.
*/
{
	int i;
	
	for(i=0; i<BCT_MAXPLAYERS; i++)
	{
		[player[i].tieView discardTrackingRect];
		[player[i].bankView discardTrackingRect];
		[player[i].playerView discardTrackingRect];
	}
	
	[self setPreferences:self];
	
	return self;
}

@end

@implementation Baccarat(BetViewDelegate)

- player:aPlayer willBet:(int)betType onView:sender
{
	int amount, prevAmount, pnum=-1;
	char buf[128];
	id thePlayer;
	
	switch([sender viewType])
	{
		case BCT_PLAYERVIEW:	pnum = [sender tag] - BCT_PLAYERTAG; break;
		case BCT_BANKVIEW:	pnum = [sender tag] - BCT_BANKTAG; break;
		case BCT_TIEVIEW:	pnum = [sender tag] - BCT_TIETAG; break;
	}
	
	thePlayer = [PBoss() playerAt:pnum];
	
	if(thePlayer != nil)
	{
		amount = [thePlayer selectedChip];
		
		prevAmount = [sender amountAtRow:0 col:0];

		switch(betType)
		{
			case NV_BET:
				if(amount == DEFAULT)
				{
					amount = [self tableMin];
				}
				if(amount > [thePlayer amountInBank]) 
				{
					NXBeep();
					[self say:"You can't wager more than you have!"];
					return nil;
				}
				if(prevAmount+amount > [self tableMax])
				{
					NXBeep();
					[self say:"That bet would put you over the table max!"];
					return nil;
				}
				[sender bet:amount atRow:0 col:0];
				[thePlayer removeChip:amount];
				sprintf(buf, "Dealer accepts %s's $%d wager.", 
					[thePlayer playerName], prevAmount+amount);
				[self say:buf];
				break;
			
			case NV_REMOVEBET:
				if(prevAmount == 0) return nil;
				if(amount == DEFAULT)
					amount = prevAmount;
				if(amount > prevAmount) 
				{
					amount = prevAmount;
				}

				[sender removeBet:amount atRow:0 col:0];
				[thePlayer addChip:amount];
				if(prevAmount-amount > 0)
				{
					sprintf(buf, "Dealer accepts %s's $%d.", 
						[aPlayer playerName], prevAmount-amount);
				}
				else
					sprintf(buf, "Player %s removed his wager", [aPlayer playerName]);
				[self say:buf];
				break;
			
			case NV_REMOVEOTHER:
				if(prevAmount == 0) return nil;
				
				amount = prevAmount;
				[sender removeBet:amount atRow:0 col:0];
				[thePlayer addChip:amount];
				if(prevAmount-amount > 0)
				{
					sprintf(buf, "Dealer accepts %s's $%d.", 
						[aPlayer playerName], prevAmount-amount);
				}
				else
					sprintf(buf, "Player %s removed his wager", [aPlayer playerName]);
				[self say:buf];
				break;
			
			default:
				NXBeep();
				[self say:"That action is not allowed in this game..."];
				break;
		}
	}
	
	[self enableDealButton];

	return self;
}

- mouseEnteredView:sender
{
	char buf[256], wintext[96], ownertext[96];
	int pnum = [sender tag];
	
	switch([sender viewType])
	{
		case BCT_BANKVIEW:	strcpy(wintext, "the Banker will WIN"); 
							pnum -= BCT_BANKTAG;
							break;
		case BCT_PLAYERVIEW:	strcpy(wintext, "the Player will WIN"); 
								pnum -= BCT_PLAYERTAG;
								break;
		case BCT_TIEVIEW:	strcpy(wintext, "the Banker and Player will TIE"); 
							pnum -= BCT_TIETAG;
							break;
	}
	
	if(player[pnum].player)
		strcpy(ownertext, [player[pnum].player playerName]);
	else
		sprintf(ownertext, "Player #%d", pnum+1);
	
	sprintf(buf, "This is %s's area to bet that %s.", ownertext, wintext); 
	
	[self say:buf];
	
	return self;
}

- mouseExitedView:sender
{
	return self;
}

- (BOOL)playerWillDragChipAtRow:(int)row andCol:(int)col fromView:sender
{
	return YES;
}

- playerDraggedChipAtRow:(int)row andCol:(int)col fromView:sender
{
	[self enableDealButton];
	
	return self;
}

- (BOOL)playerWillDropChipAtRow:(int *)row andCol:(int *)col InView:sender;
{	
	return YES;
}

- playerDroppedChipAtRow:(int)row andCol:(int)col inView:sender
{
	[self enableDealButton];
	
	return self;
}

@end



