Commits

larry committed 0545ef5

* Made Norman smarter about playing end-of-limit: don't bother
if we're < 50 from the current limit, and don't bother if
we don't have any big distance cards. But still play one at
low priority (if we were only going to discard anyway).
* Added regression test: ensure that you get credit for a coup
fourre when the draw pile is empty.

Comments (0)

Files changed (1)

 #!/usr/bin/env python3
-##
-## TODO
-##
-## need regression test to check that my fix worked for:
-##   if the draw pile is empty, you don't get credit for a coup fourre
-##
 #
 # Copyright 2012 Larry Hastings
 #
 		return (isinstance(last_card, Hazard)
 			and (last_card.safety is self)
 			and (last_playee is playee)
-			and ((len(playee.hand.hand) == 6) or not deck)
+			and ((len(playee.hand.hand) == 6) or not hand.draw_pile)
 			)
 
 	def is_legal(self, player, playee, silent=False):
 		if self.is_coup_fouree(playee):
 			last_card = hand.plays[-1].card
 			pile = last_card.pile(playee)
-			assert pile and pile[-1] == last_card
+			assert pile and (pile[-1] == last_card)
 			del pile[-1]
 			series.discard.pile.append(last_card)
 			playee.hand.coups_fourres.append(self)
 		# shutout?  hot damn!
 		# don't chance the extend, take the points!
 		if our_score.shutout:
-			return True
+			return False
 
 		other_safeties = other.hand.safeties
 		extend_if = (
 		debug = self.debug
 
 		self.check_for_coup_fouree(debug)
-		drew = self.hand.draw()
-		debug("drew", drew)
+		# if we already have 7 cards,
+		# we are restarting after a crash
+		# where we already drew.
+		if len(self.hand.hand) <= 6:
+			drew = self.hand.draw()
+			debug("drew", drew)
 
 		safety_countdown = self.hand.safety_countdown
 		self.hand.safety_countdown = 0
 		play_safety = self.hand.play_safety
 		self.hand.play_safety = False
 
+		# first step: sort all our cards into these categories
 		preferred_hazards = []
 		hazards = []
 		stops = []
 					debug(card, "-> remedies")
 					remedies.append(card)
 			elif isinstance(card, Mileage):
+				which_discard = None
 				if (card.distance == 200) and maxed_out_200s:
+					which_discard = preferred_discards
+				elif (mileage_so_far + card.distance) > limit:
+					if limit == 1000:
+						which_discard = preferred_discards
+					else:
+						which_discard = tertiary_discards
+				if which_discard == preferred_discards:
 					debug(card, "-> preferred_discards")
 					preferred_discards.append(card)
-				elif (mileage_so_far + card.distance) > limit:
+				elif which_discard == tertiary_discards:
 					debug(card, "-> tertiary_discards")
 					tertiary_discards.append(card)
 				else:
 			if (extending and safeties_needed_to_win):
 				# if so, hold off on winning unless the
 				# other player is in striking distance of winning.
+				# (hoping for coup fourre here)
 				we_can_win = they_can_win
 			debug("P0.e", we_can_win)
 
 				self.play(mileage_25)
 				return
 		
-		##################################
-		##################################
+		############################################
+		############################################
 		##
-		## priority 4: remove speed limits
+		## priority 4: remove speed limits,
+		## but only if we have a > 50 mileage card
+		## and only if we're not near the limit
 		##
 
 		if not self.hand.autobahn() and end_of_limits:
 			debug("P4")
-			self.play(end_of_limit)
-			return
+			if not (mileage and mileage[0].distance > 50):
+				debug("P4.1")
+			elif (limit - self.hand.mileage()) <= 50:
+				debug("P4.2")
+			else:
+				debug("P4.3")
+				self.play(end_of_limit)
+				return
 		
 		###########################
 		###########################
 		###################################################
 		###################################################
 		##
-		## priority 7: discard a card we know we don't want
-		## P7.a, P7.b, P7.c
+		## priority 7: if we're under a speed limit,
+		## and we have an end of limit, might as well
+		## play it (as it's preferable to discarding)
+		##
+		if (not self.hand.autobahn()) and end_of_limits:
+			debug("P7")
+			self.play(end_of_limit)
+			return
+
+		###################################################
+		###################################################
+		##
+		## priority 8: discard a card we know we don't want
+		## P8.a, P8.b, P8.c
 		##
 
 		discard_preferences = (preferred_discards, discards, tertiary_discards)
 		for i, cards in enumerate(discard_preferences):
 			if cards:
-				randomly_discard_one(cards, "P7." + "abc"[i])
+				randomly_discard_one(cards, "P8." + "abc"[i])
 				return
 
 		###################################################
 		###################################################
 		##
-		## priority 8: discard a card we might someday want
+		## priority 9: discard a card we might someday want
 		##
 
 		remedy_preferences = (end_of_limits, remedies, rolls)
 
 		# remedies for which we have two or more
 		for cards in remedy_preferences:
-			if randomly_discard_one_if_duplicates(cards, "P8.a"):
+			if randomly_discard_one_if_duplicates(cards, "P9.a"):
 				return
 
 		# mileage
 			# if we're one card away from winning,
 			# don't discard it here
 			if card_to_win in mileage:
-				debug("P8.b.1")
+				debug("P9.b.1")
 				mileage.remove(card_to_win)
 			if mileage:
-				debug("P8.b.2")
+				debug("P9.b.2")
 				self.discard(mileage[0])
 				return
 
 		# remedies of which you have only one
 		if remedies:
-			randomly_discard_one(remedies, "P8.c")
+			randomly_discard_one(remedies, "P9.c")
 			return
 		
 		# hazards
 		for cards in hazard_preferences:
-			if randomly_discard_one_if_duplicates(cards, "P8.d"):
+			if randomly_discard_one_if_duplicates(cards, "P9.d"):
 				return
 		for cards in hazard_preferences:
 			if cards:
-				randomly_discard_one(cards, "P8.e")
+				randomly_discard_one(cards, "P9.e")
 				return
 
 		# I give up!
 		# slim, and this is a kindness to the human player.
 		if not hand.draw_pile:
 			for card in safeties:
-				debug("P9.a")
+				debug("P10.a")
 				self.play(card)
 			if not self.hand.hand:
 				return
 		# discard *something* none-safety-ish.
 		non_safeties = [card for card in self.hand.hand if not isinstance(card, Safety)]
 		if non_safeties:
-			randomly_discard_one(non_safeties, "P9.b")
-		
+			randomly_discard_one(non_safeties, "P10.b")
+
 		debug("no possible plays.")
 
 
 		def check_cards(player, allow_seven=False):
 			n = len(player.hand.hand)
 			if allow_seven:
-				assert n <= 7
+				assert n in (6, 7)
 			elif self.draw_pile:
 				assert n == 6
 			else:
 
 		self.p1.play(safety)
 		self.assertEqual(len(series.discard.pile), 1)
-		self.assertEqual(series.discard.pile[0], hazard)
+		self.assertEqual(series.discard.pile[-1], hazard)
 		self.assertIn(safety, self.p1.hand.safeties)
 		self.assertIn(safety, self.p1.hand.coups_fourres)
 
 		self.assertEqual(score.total, Score.perfect_hand)
 
 
+class TestEmptyDrawPile(TestBase):
+
+	def setUp(self):
+		super().setUp()
+
+	def empty_draw_pile(self):
+		series.discard.pile = hand.draw_pile
+		hand.draw_pile = []
+
+	def coup_fourre(self, hazard):
+		safety = hazard.safety
+		self.p1.draw(roll)		
+		self.p1.draw(safety)
+		self.p2.draw(hazard)
+		self.empty_draw_pile()
+
+		self.p1.play(roll)
+		self.p2.play(hazard, self.p1)
+
+		self.assertEqual(len(hand.draw_pile), 0)
+		self.p1.play(safety)
+		self.assertEqual(series.discard.pile[-1], hazard)
+		self.assertIn(safety, self.p1.hand.safeties)
+		self.assertIn(safety, self.p1.hand.coups_fourres)
+
+	def test_coup_fourre_accident(self):
+		self.coup_fourre(accident)
+
+	def test_coup_fourre_flat_tire(self):
+		self.coup_fourre(flat_tire)
+
+	def test_coup_fourre_out_of_gas(self):
+		self.coup_fourre(out_of_gas)
+
+	def test_coup_fourre_stop(self):
+		self.coup_fourre(stop)
+
+	def test_coup_fourre_speed_limit(self):
+		self.coup_fourre(speed_limit)
+
+
+
 import sys, tty, termios
 
 def getch(prompt, valid=None):