"""Blackjack Flashcards(c) 2001, Steven F. LottThis displays sample dealer card and hand, you choose to hit, stand, double or splitit will correct your play.""""""Note:The flashcards approach is superior to playing out real shoes of real cards.The distribution of plays is as follows:	Hard Hands	1716	78.11%	Splits		 169	 7.69%	Soft Hands	 312	14.20%The splits and soft hands with doubling lead to larger bets and require morein-depth knowledge of how to play.Within hard hands, BTW, the break down of actual plays is	Double	144	 8.39%	Hit		758	44.17%	Stand	814	47.44%	Since the money's involved in doubling and splitting, flashcards presents thesecritical decisions more often than they occur in real play.""""""	TODO:		separate hard, soft and split rules to allow user to focus on specific issues		play out full hand doing hits and splits"""from Tkinter import *import tkMessageBox;import random;import types;import string;import time;import sys;class Trainer:	"""A Trainer has a set of playing strategies that it presents.  It makes individual cards	and pairs of cards to make concrete examples of each strategy.  It presents a randomly selected	strategy and scores the trainee's responses as correct or incorrect."""	def __init__( self ):		"Create the complete set of strategies."		self.strat= [			# rule name, you have, ( dealer has ), action, suggestion, explanation			("<=8", 5, (2,3,4,5,6,7,8,9,10,11),	"hit",	"need to meet or beat 17", "always hit <= 8" ),			("<=8", 6, (2,3,4,5,6,7,8,9,10,11),	"hit",	"need to meet or beat 17", "always hit <= 8" ),			("<=8", 7, (2,3,4,5,6,7,8,9,10,11),	"hit",	"need to meet or beat 17", "always hit <= 8" ),			("<=8", 8, (2,3,4,5,6,7,8,9,10,11),	"hit",	"need to meet or beat 18", "always hit <= 8" ),			(  "9", 9, (3,4,5,6),			"double","dealer may bust with 3-6", "double on 9 when dealer weak (3-6), hoping for win" ),			(  "9", 9, (2,7,8,9,10,11),		"hit",	"need to meet or beat 19", "hit 9 when dealer strong (2,>=7), hoping for tie" ),			( "10", 10, (2,3,4,5,6,7,8,9),		"double","dealer weaker with 2-9", "double on 10 when dealer has less" ),			( "10", 10, (10,11),			"hit",	"need to meet or beat 20", "hit 10 when dealer may have 20 or 21" ),			( "11", 11, (2,3,4,5,6,7,8,9,10),	"double","dealer weaker with 2-10", "double on 11 when dealer has less" ),			( "11", 11, (11,),			"hit",	"need to meet 21", "hit 11 when dealer may have 21" ),			( "12", 12, (2,3),			"hit",	"need to meet or beat 17", "hit 12 against dealer's medium 2 or 3" ),			( "12", 12, (4,5,6),			"stand","play it safe, dealer weak", "stand on 12 when dealer weak (4-6)" ),			( "12", 12, (7,8,9,10,11),		"hit",	"need to meet or beat 17", "hit 12 when dealer strong (>=7)" ),			("13-16",13, (2,3,4,5,6),		"stand","play it safe, dealer weak", "stand on 13 when dealer weak (2-6)" ),			("13-16",13, (7,8,9,10,11),		"hit",	"need to meet or beat 17", "hit 13 when dealer strong (>=7)" ),			("13-16",14, (2,3,4,5,6),		"stand","play it safe, dealer weak", "stand on 14 when dealer weak (2-6)" ),			("13-16",14, (7,8,9,10,11),		"hit",	"need to meet or beat 17", "hit 14 when dealer strong (>=7)" ),			("13-16",15, (2,3,4,5,6),		"stand","play it safe, dealer weak", "stand on 15 when dealer weak (2-6)" ),			("13-16",15, (7,8,9,10,11),		"hit",	"need to meet or beat 17", "hit 15 when dealer strong (>=7)" ),			("13-16",16, (2,3,4,5,6),		"stand","play it safe, dealer weak", "stand on 16 when dealer weak (2-6)" ),			("13-16",16, (7,8,9,10,11),		"hit",	"need to meet or beat 17", "hit 16 when dealer strong (>=7)" ),			("17-19",17, (2,3,4,5,6,7,8,9,10,11),"stand","good hand", "stand, excellent hand" ),			("17-19",18, (2,3,4,5,6,7,8,9,10,11),"stand","good hand", "stand, excellent hand" ),			("17-19",19, (2,3,4,5,6,7,8,9,10,11),"stand","good hand", "stand, excellent hand" ),			("A2",(11,2), (2,3,4,7,8,9,10,11),	"hit",	"dealer 12-14, >=17 beats your 13", "hit unless dealer weak with 5 or 6" ),			("A2",(11,2), (5,6),			"double","dealer may bust with 5-6", "double when dealer weak with 5 or 6" ),			("A3",(11,3), (2,3,4,7,8,9,10,11),	"hit",	"dealer 12-14, >=17 beats your 14", "hit unless dealer weak with 5 or 6" ),			("A3",(11,3), (5,6),			"double","dealer may bust with 5-6", "double when dealer weak with 5 or 6" ),			("A45",(11,4), (2,3,7,8,9,10,11),	"hit",	"dealer 12-13, >=17 beats your 15", "hit unless dealer weak with 4-6" ),			("A45",(11,4), (4,5,6),			"double","dealer may bust with 4-6", "double when dealer weak with 4-6" ),			("A45",(11,5), (2,3,7,8,9,10,11),	"hit",	"dealer 12-13, >=17 beats your 16", "hit unless dealer weak with 4-6" ),			("A45",(11,5), (4,5,6),			"double","dealer may bust with 4-6", "double when dealer weak with 4-6" ),			("A6",(11,6), (2,7,8,9,10,11),		"hit",	"dealer 12, >=17 beats your 16", "hit unless dealer weak with 3-6" ),			("A6",(11,6), (3,4,5,6),		"double","dealer may bust with 3-6", "double when dealer weak with 3-6" ),			("A7",(11,7), (2,7,8),			"stand","likely to get hard 18", "stand, excellent hand when dealer medium 2,7,8" ),			("A7",(11,7), (9,10,11),		"hit",	"dealer has 19-21", "hit when dealer strong with 9-A" ),			("A7",(11,7), (3,4,5,6),		"double","dealer may bust with 3-6", "double when dealer weak with 3-6" ),			("A89",(11,8), (2,3,4,5,6,7,8,9,10,11), "stand", "soft 19", "stand, excellent hand" ),			("A89",(11,9), (2,3,4,5,6,7,8,9,10,11), "stand", "soft 20", "stand, excellent hand" ),			("22",(2,2),  (2,3,4,5,6,7),		"split","dealer may bust with 2-6, win with 7", "2 12's best against weak dealer with 2-7" ),			("22",(2,2),  (8,9,10,11),		"hit",	"dealer 18-21 beats your hard 14", "hit 4 to beat dealer with >=8" ),			("33",(3,3),  (2,3,4,5,6,7),		"split","dealer may bust with 2-6, win with 7", "2 13's best against weak dealer with 2-7" ),			("33",(3,3),  (8,9,10,11),		"hit",	"dealer 18-21 beats your hard 16", "hit 6 to beat dealer with >=8" ),			("44",(4,4),  (5,6),			"split","dealer may bust with 5-6", "2 14's best against very weak dealer with 5-6" ),			("44",(4,4),  (2,3,4,7,8,9,10,11),	"hit",	"dealer 12-14, >=17 may beat your 8", "hit 8 to beat dealer with 2-4,>=8" ),			("55",(5,5),  (2,3,4,5,6,7,8,9),	"double","likely to get 20", "double on 10 when dealer has less" ),			("55",(5,5),  (10,11),			"hit",	"likely to get 20", "hit 10 when dealer may have 20 or 21" ),			("66",(6,6),  (2,3,4,5,6),		"split","dealer may bust with 2-6", "2 16's best against weak dealer with 2-6" ),			("66",(6,6),  (7,8,9,10,11),		"hit",	"dealer 17-21 beats your 12", "hit 12 to beat strong dealer with >=7" ),			("77",(7,7),  (2,3,4,5,6,7),		"split","dealer may bust with 2-6, win with 7", "2 17's best against weak dealer with 2-7" ),			("77",(7,7),  (8,9,10,11),		"hit",	"dealer >= 18 may beat your 14", "hit 14 to beat strong dealer with >=8" ),			("88",(8,8),  (2,3,4,5,6,7,8,9,10,11), "split", "two 18's", "2 18's are excellent hands" ),			("99",(9,9),  (2,3,4,5,6,8,9), 		"stand","dealer may bust with 2-6, lose with 8,9", "stand on 18, excellent hand with dealer 2-6,8,9" ),			("99",(9,9),  (7,10,11), 		"split","dealer may lose with 17, win with 10,A", "2 19's best against dealer with 7,10,A" ),			("1010",(10,10),(2,3,4,5,6,7,8,9,10,11),"stand", "hard 20", "stand, excellent hand" ),			("AA",(11,11),(2,3,4,5,6,7,8,9,10,11),	"split","two 21's", "2 21's are excellent hands" ) 		];		self.gotWrong = {};		self.presented = {};		self.currentStrat= None;		self.mistakes= 0;	def longestMessage( self ):		"Scan the strategie for the longest suggestion or explanation."		mxLen= 0;		for name,you,dealer,action,suggestion,explanation in self.strat:			mxLen= max( len(suggestion)+10, len(explanation)+10, mxLen );		return mxLen;	def makeCard( self, number ):		"Make an example playing card."		suit= ["S","D","C","H"][random.randrange(4)];		if( number == 11 ): return "A%s" % suit;		elif( number == 10 ): return "%s%s" % ( random.choice( [ "10", "J", "Q", "K" ] ), suit );		return "%d%s" % (number,suit);	def make2Cards( self, number ):		"Make a pair of cards with a given hard total, avoiding pairs and aces."		# If a split or soft hand, player's cards already coded as a pair.		if( type(number) == types.TupleType ):			return ( self.makeCard(number[0]), self.makeCard(number[1]) );		# For a hard hand, create two cards that add to the required number.		# Pick a value, c1, such that 2 <= c1 <= int((number-1)/2).		# C1 is provably less than number/2, so we won't get a pair.		# Also, 2 <= c1 <= 10 and 2 <= number-c1 <= 10; must be changed when number > 12.		# For number > 12, must raise the lower limit above 2 to satisfy the <= 10 constraint 		# (wouldn't want to generate 4H, 12D to make a hard 16!)		upper= int((number-1)/2)+1;		if( number - 10 < 2 ): lower= 2;		else: lower= number-10;		c1= random.randrange(lower,upper);		return ( self.makeCard(c1), self.makeCard(number-c1) );	def newRound( self, ui ):		"Start a new round of training: pick a strategy, create examples and reset counts to zero."		self.currentStrat= random.choice( self.strat );		self.mistakes= 0;		#print self.currentStrat, self.strat[s];		name,you,dealer,action,suggestion,explanation= self.currentStrat;		self.dealerHas= random.randrange(len(dealer));		ui.dealer( self.makeCard( dealer[self.dealerHas] ) );		ui.player( self.make2Cards( you ) );		self.presented.setdefault(name,0);		self.gotWrong.setdefault(name,0);		self.presented[name] += 1;		return TrainerViewPresent().enter(ui);	def wrong( self, name ):		"Track trainees a wrong choices."		self.gotWrong[name] += 1;		self.mistakes += 1;	def hit( self, ui ):		"Respond when the trainee asks to make a hit."		ns= None		name,you,dealer,action,suggestion,explanation= self.currentStrat;		if( action == "hit" ):			ui.message( "Yes! " + explanation );			ns= ui.state.right(ui);		else:			ui.message( "no, " + suggestion );			self.wrong( name );			ns= ui.state.wrong(ui);		return ns;	def stand( self, ui ):		"Respond when the trainee asks to stand."		ns= None		name,you,dealer,action,suggestion,explanation= self.currentStrat;		if( action == "stand" ):			ui.message( "Yes! " + explanation );			ns= ui.state.right(ui);		else:			ui.message( "no, " + suggestion );			self.wrong( name );			ns= ui.state.wrong(ui);		return ns;	def split( self, ui ):		"Respond when the trainee asks to split a pair."		ns= None		name,you,dealer,action,suggestion,explanation= self.currentStrat;		if( action == "split" ):			ui.message( "Yes! " + explanation );			ns= ui.state.right(ui);		else:			ui.message( "no, " + suggestion );			self.wrong( name );			ns= ui.state.wrong(ui);		return ns;	def double( self, ui ):		"Respond when the trainee asks to double down."		ns= None		name,you,dealer,action,suggestion,explanation= self.currentStrat;		if( action == "double" ):			ui.message( "Yes! " + explanation );			ns= ui.state.right(ui);		else:			ui.message( "no, " + suggestion );			self.wrong( name );			ns= ui.state.wrong(ui);		return ns;	def suggest( self, ui ):		"Show the suggestion associated with the strategy selected."		name,you,dealer,action,suggestion,explanation= self.currentStrat;		ui.message( suggestion );	def explain( self, ui ):		"Show the explanation associated with the strategy selected."		name,you,dealer,action,suggestion,explanation= self.currentStrat;		ui.message( explanation );	def fullRule( self, rule ):		"Decode a rule table entry for this strategy."		code = { "hit":"H", "stand":"S", "double":"D", "split":"P" };		clauses= [0]*12;		for name,you,dealer,action,suggestion,explanation in self.strat:			if( name == rule ):				for d in dealer:					clauses[int(d)] = code[action];		return "%5s %s" % ( rule, str(clauses[2:12]) );	def scores( self ):		"Show the scores for the trainee."		t= 0;		s= [];		for rule,shown in self.presented.items():			t += shown;			if( self.gotWrong[rule] > 0 ):				s.append( "%2.0f%% %s" % ( 100.0*(shown-self.gotWrong[rule])/shown, self.fullRule(rule) ) );		if( len(s) == 0 ): return "Perfect responses to %d problems" % t;		return "    you   " + str(['2','3','4','5','6','7','8','9','X',"A"]) + "\n" + string.join( s, "\n" );	def writeScore( self ):		"Save the scores to a file."		t= 0;		for rule,shown in self.presented.items():			t += shown;		f= open( "ProblemHands.txt", "a" );		f.writelines( time.asctime( time.localtime(time.time()) ), " - %d hands" % t, "\n" );		f.writelines( self.scores(), "\n" );		f.close();		if( sys.platform == "mac" ):			import macfs;			fs= macfs.FSSpec( "ProblemHands.txt" );			fs.SetCreatorType('BOBO','TEXT');class TrainerViewState:	def __init__( self ):		pass;	def enter( self, ui ):		pass;	def right( self, ui ):		pass;	def wrong( self, ui ):		pass;class TrainerViewPresent (TrainerViewState):	def __init__( self ):		TrainerViewState.__init__( self );	def enter( self, ui ):		ui.next.config(state=DISABLED);		ui.hit.config(state=NORMAL);		ui.stand.config(state=NORMAL);		ui.split.config(state=NORMAL);		ui.double.config(state=NORMAL);		# TODO: only enable split when actually allowed		ui.playmenu.entryconfig(0,state=DISABLED);		ui.playmenu.entryconfig(2,state=NORMAL);		ui.playmenu.entryconfig(3,state=NORMAL);		ui.playmenu.entryconfig(4,state=NORMAL);		ui.playmenu.entryconfig(5,state=NORMAL);		return self;	def right( self, ui ):		return TrainerViewRight().enter(ui);	def wrong( self, ui ):		return TrainerViewWrong().enter(ui);class TrainerViewRight (TrainerViewState):	def __init__( self ):		TrainerViewState.__init__( self );	def enter( self, ui ):		ui.next.config(state=NORMAL);		ui.hit.config(state=DISABLED);		ui.stand.config(state=DISABLED);		ui.split.config(state=DISABLED);		ui.double.config(state=DISABLED);		ui.playmenu.entryconfig(0,state=NORMAL);		ui.playmenu.entryconfig(2,state=DISABLED);		ui.playmenu.entryconfig(3,state=DISABLED);		ui.playmenu.entryconfig(4,state=DISABLED);		ui.playmenu.entryconfig(5,state=DISABLED);		return self;	def right( self, ui ):		return self;	def wrong( self, ui ):		return TrainerViewWrong().enter(ui);class TrainerViewWrong (TrainerViewState):	def __init__( self ):		TrainerViewState.__init__( self );	def enter( self, ui ):		ui.next.config(state=DISABLED);		ui.hit.config(state=NORMAL);		ui.stand.config(state=NORMAL);		ui.split.config(state=NORMAL);		ui.double.config(state=NORMAL);		ui.playmenu.entryconfig(0,state=DISABLED);		ui.playmenu.entryconfig(2,state=NORMAL);		ui.playmenu.entryconfig(3,state=NORMAL);		ui.playmenu.entryconfig(4,state=NORMAL);		ui.playmenu.entryconfig(5,state=NORMAL);		return self;	def right( self, ui ):		return TrainerViewRight().enter(ui);	def wrong( self, ui ):		return self;class TrainerView:	def __init__(self, master, trainer):		self.master= master;		self.trainer= trainer				self.menuBar= Menu(self.master)		self.master.config(menu=self.menuBar)		self.master.title( "BJ Flashcards" );				self.frame = Frame(self.master)	# centered in the master frame by default		self.frame.grid(row=0,column=0)			self.filemenu= Menu(self.menuBar, tearoff= 0)		self.menuBar.add_cascade(label="File",underline=0,menu=self.filemenu)		self.filemenu.add_command(label="Scores",underline=0,command=self.showScore)		self.filemenu.add_command(label="Quit",underline=0,command=self.done)				self.playmenu= Menu(self.menuBar, tearoff= 0)		self.menuBar.add_cascade(label="Play",underline=0,menu=self.playmenu)		self.playmenu.add_command(label="Next",underline=0,command=self.nextRound)		self.playmenu.add_separator()		self.playmenu.add_command(label="Hit",underline=0,command=self.hit)		self.playmenu.add_command(label="Stand",underline=0,command=self.stand)		self.playmenu.add_command(label="sPlit",underline=1,command=self.split)		self.playmenu.add_command(label="Double",underline=0,command=self.double)				self.helpmenu= Menu(self.menuBar, tearoff= 0)		self.menuBar.add_cascade(label="Help",underline=0,menu=self.helpmenu)		self.helpmenu.add_command(label="Suggest...",underline=0,command=self.suggest)		self.helpmenu.add_command(label="Explain...",underline=0,command=self.explain)		self.helpmenu.add_separator()		self.helpmenu.add_command(label="About...",underline=0,command=self.showHelp)		dl= Label( self.frame, text= "dealer: ")		dl.grid(row=0,column=0,pady=2, sticky= E)		self.dealerCard= Entry( self.frame )		self.dealerCard.grid(row=0,column=1,columnspan=4,pady=2)				yl= Label( self.frame, text= "you: " )		yl.grid(row=1,column=0,pady=2, sticky= E);		self.playerCards= Entry( self.frame )		self.playerCards.grid(row=1,column=1,columnspan=4,pady=2);				mxLen= self.trainer.longestMessage();		self.outcome= Label( self.frame, width=mxLen );		self.outcome.grid( row=3,column= 0, columnspan= 5,pady=2);				self.explain= Label( self.frame, width=mxLen );		self.explain.grid( row=4,column= 0, columnspan= 5,pady=2);				self.next= Button( self.frame, text="Next", command= self.nextRound );		self.next.grid(row=5,column=0,padx=2,pady=2)		self.hit = Button( self.frame, text="Hit", fg="red", command=self.hit)		self.hit.grid(row=5, column=1,padx=2,pady=2)		self.stand = Button( self.frame, text="Stand", fg="yellow", command=self.stand)		self.stand.grid(row=5, column=2,padx=2,pady=2)		self.split = Button( self.frame, text="sPlit", fg="green", command=self.split)		self.split.grid(row=5, column=3,padx=2,pady=2)		self.double = Button( self.frame, text="Double", fg="blue", command=self.double)		self.double.grid(row=5, column=4,padx=2,pady=2)				self.state= self.trainer.newRound( self );	def dealer( self, card ):		self.dealerCard.delete( 0, END );		self.dealerCard.insert( END, card );		self.explain.config(text="");		def player( self, cards ):		self.playerCards.delete( 0, END );		self.playerCards.insert( END, cards );		def message( self, msg ):		self.explain.config(text=msg);	def hit(self):		self.trainer.hit( self );	def stand(self):		self.trainer.stand( self );	def split(self):		self.trainer.split( self );	def double(self):		self.trainer.double( self );	def nextRound(self):		self.trainer.newRound(self);	def showHelp( self ):		tkMessageBox.Message( title= "About", message= __doc__, icon= tkMessageBox.INFO, type= tkMessageBox.OK ).show();	def showScore( self ):		score= self.trainer.scores();		tkMessageBox.Message( title= "Score", message= score, icon= tkMessageBox.INFO, type= tkMessageBox.OK ).show();	def done( self ):		self.showScore();		self.trainer.writeScore();		self.frame.quit();	def suggest( self ):		msg= self.trainer.suggest( self );			def explain( self ):		msg= self.trainer.explain( self );		def main():	theTrainer= Trainer();	root = Tk()	app = TrainerView(root,theTrainer)	root.mainloop()def dump():	theTrainer= Trainer();	for s in theTrainer.strat:		print string.join( map( lambda x: str(x), s ), "\t" );		 main();