#!/usr/bin/env python # -*- Mode: Python ; tab-width: 4 -*- import sys import os import string import random import getopt debug = 0 trace = 0 default_max_card = 14 suits = 4 default_jokers = 2 war_downs = 3 bar_width = 50 stats_reps = 1000 stalemate_rounds = 5000 # Strategies SHUFFLE_TAKE = "Shuffle Take" SORT_TAKE = "Sort Take" SHUFFLE_HAND = "Shuffle Hand" SORT_HAND = "Sort Hand" GameOver = "GameOver" def usage(): sys.stderr.write( "Usage: war.py [--stats filename]\n" ) sys.stderr.write( " --stats = Run many games and gather stats into this filename.\n" ) sys.exit(1) def main(): (odict, args) = getOptions() if odict.has_key( "stats" ): s = Stats( odict["stats"] ) s.run() else: g = Game() g.play() class Stats: strategy_combos = [ (SHUFFLE_TAKE, [SORT_HAND, SHUFFLE_HAND, SORT_TAKE, SHUFFLE_TAKE]), (SORT_TAKE, [SORT_HAND, SHUFFLE_HAND, SORT_TAKE]), (SHUFFLE_HAND, [SORT_HAND, SHUFFLE_HAND]), (SORT_HAND, [SORT_HAND]), ] card_range = (6, 19, 2) # 6 to 18, step 2 joker_range = (0, 5, 2) # 0 to 4, step 2 def __init__( self, filename ): self.filename = filename def run( self ): stats_runs = [] # Run strategies for (strategy1, l) in self.strategy_combos: for strategy2 in l: params = StatsParams() params.strategy1 = strategy1 params.strategy2 = strategy2 stats = StatsRun( params ) stats_runs.append( stats ) stats.run() # Run deck sizes for max_card in apply( range, self.card_range ): for jokers in apply( range, self.joker_range ): params = StatsParams() params.max_card = max_card params.jokers = jokers stats = StatsRun( params ) stats_runs.append( stats ) stats.run() print "Exporting stats..." f = open( self.filename, "w" ) header = 1 for stats in stats_runs: if header: stats.printHeader( f ) header = 0 stats.printStats( f ) class StatsParams: def __init__( self ): self.strategy1 = SHUFFLE_TAKE self.strategy2 = SHUFFLE_TAKE self.max_card = default_max_card #self.suits = default_suits # Not implemented self.jokers = default_jokers #self.war_downs = default_war_downs # Not implemented def asString( self ): s = "%s vs. %s, %d cards, %d jokers" % (self.strategy1, self.strategy2, self.max_card, self.jokers) return s class StatsRun: def __init__( self, params ): self.params = params self.avg_rounds = 0.0 self.num_stalemates = 0 self.avg_wars = 0.0 self.pct_wars = 0.0 self.wins = [0, 0] self.avg_take_profit = 0.0 def run( self ): sum_rounds = 0 sum_wars = 0 sum_take_profit = 0 for i in range( stats_reps ): g = Game( self.params.strategy1, self.params.strategy2, max_card=self.params.max_card, jokers=self.params.jokers ) g.play() if g.isStalemate(): self.num_stalemates = self.num_stalemates + 1 else: sum_rounds = sum_rounds + g.getRounds() sum_wars = sum_wars + g.getWars() winner_index = g.getWinnerIndex() self.wins[winner_index] = self.wins[winner_index] + 1 sum_take_profit = sum_take_profit + g.getProfitAdvantage() self.avg_rounds = sum_rounds / float(stats_reps) self.avg_wars = sum_wars / float(stats_reps) self.pct_wars = ( sum_wars / float(sum_rounds) ) * 100.0 self.avg_take_profit = sum_take_profit / float(stats_reps) print "=" * 80 self.printStats() def printHeader( self, f ): fields = [ "Reps", "Strategy 1", "Strategy 2", "Max Card", "Jokers", "Stalemates", "Avg Rounds", "Avg Wars", "Pct Wars", "1 Wins", "2 Wins", "Avg Profit", ] f.write( string.join( fields, "\t" ) + "\n" ) def printStats( self, f=None ): if f: data = [ stats_reps, self.params.strategy1, self.params.strategy2, self.params.max_card, self.params.jokers, self.num_stalemates, "%.2f" % (self.avg_rounds), "%.2f" % (self.avg_wars), "%.2f%%" % (self.pct_wars), self.wins[0], self.wins[1], "%.2f" % (self.avg_take_profit), ] s = string.join( map( str, data ), "\t" ) f.write( s + "\n" ) else: print self.params.asString() print " Stalemates: %d" % (self.num_stalemates) print " Avg Rounds: %.2f" % (self.avg_rounds) print " Avg # Wars: %.2f, %.2f%%" % (self.avg_wars, self.pct_wars) print " Wins P1/P2: %d/%d" % (self.wins[0], self.wins[1]) print " Avg Profit: %.2f" % (self.avg_take_profit) class Game: def __init__( self, strategy1=SHUFFLE_TAKE, strategy2=SHUFFLE_TAKE, max_card=default_max_card, jokers=default_jokers ): self.players = [Player( "One", strategy1 ), Player( "Two", strategy2 )] self.max_card = max_card self.jokers = jokers self.round = 1 self.wars = 0 self.stalemate = 0 def play( self ): self.deal() while 1: rnd = Round( self.round, self.players[0], self.players[1] ) if (self.round == 0): rnd.printHeader() rnd.play() if rnd.isWar(): self.wars = self.wars + 1 if rnd.isGameOver(): self.gameOver() break self.round = self.round + 1 if (self.round > stalemate_rounds): self.gameStale() break def deal( self ): deck = [] for i in range( 1, self.max_card+1 ): for j in range( suits ): deck.append( i ) for i in range( self.jokers ): deck.append( self.max_card + 1 ) deck = randomize( deck ) hands = { 0 : [], 1 : [] } for i in range( len(deck) ): hands[i%2].append( deck[i] ) for i in range( 2 ): self.players[i].setHand( hands[i] ) def gameOver( self ): winner = self.players[self.getWinnerIndex()] print "Winner: Player %s in %d rounds, %d wars." % (winner.name, self.round, self.wars) def gameStale( self ): self.stalemate = 1 print "Stalemate in %d rounds, %d wars." % (self.round, self.wars) def isStalemate( self ): return self.stalemate def getRounds( self ): return self.round def getWars( self ): return self.wars def getWinnerIndex( self ): l = map( lambda x: x.getNumCards(), self.players ) return l.index( max(l) ) def getLoserIndex( self ): l = map( lambda x: x.getNumCards(), self.players ) return l.index( min(l) ) def getProfitAdvantage( self ): l = map( lambda x: x.getTakeProfit(), self.players ) return l[self.getWinnerIndex()] - l[self.getLoserIndex()] class Round: def __init__( self, round_num, p1, p2 ): self.round_num = round_num self.p1 = p1 self.p2 = p2 self.actives = [] self.others = [] self.is_war = 0 self.game_over = 0 def play( self ): if trace: print "Round %s" % (self.round_num) try: self.playInner() self.printStats() except GameOver: self.game_over = 1 def playInner( self ): c1 = self.p1.playCard() c2 = self.p2.playCard() self.actives = [c1, c2] if trace: print "Round %d: %s, %s" % (self.round_num, self.actives, self.others) if (c1 > c2): self.p1.takeRound( self ) elif (c2 > c1): self.p2.takeRound( self ) else: self.war() def war( self ): if trace: print "WAR!" self.is_war = 1 self.others = self.others + self.actives self.others = self.others + self.p1.playDowns() self.others = self.others + self.p2.playDowns() self.playInner() def isWar( self ): return self.is_war def isGameOver( self ): return self.game_over def printHeader( self ): spaces = " " * (bar_width + 1) print " Player 1 %s Player 2" % (spaces) print " Take Hand %s Hand Take" % (spaces) print "Round Prof Avg %s Avg Prof" % (spaces) print "----- ---- ----- %s ----- ----" % (spaces) def printStats( self ): count1 = self.p1.getNumCards() count2 = self.p2.getNumCards() avg1 = self.p1.getAvgCard() avg2 = self.p2.getAvgCard() profit1 = self.p1.getTakeProfit() profit2 = self.p2.getTakeProfit() total_count = count1 + count2 w1 = int( round( count1 / float(total_count) * bar_width ) ) w2 = int( round( count2 / float(total_count) * bar_width ) ) if self.is_war: div = "*" else: div = "|" s = ("-" * w1) + div + ("-" * w2) if debug: print "%4d: %4.2f %5.2f %s %5.2f %4.2f" % (self.round_num, profit1, avg1, s, avg2, profit2) class Player: def __init__( self, name, strategy=SHUFFLE_TAKE ): self.name = name self.strategy = strategy self.hand = [] self.take = [] self.take_profit = 0 self.num_takes = 0 def setHand( self, l ): if trace: print "%s.setHand(): %s" % (self.name, l) self.take = l self.recycleHand() def playCard( self ): if not self.hand: self.recycleHand() if not self.hand: raise GameOver card = self.hand.pop( 0 ) if trace: print "%s.playCard(): %s, %s, %s" % (self.name, card, self.hand, self.take) return card def playDowns( self ): l = [] downs = min( self.getNumCards(), war_downs+1 ) - 1 # 1 is for the last up card if (downs > 0): for i in range( downs ): card = self.playCard() l.append( card ) elif (downs < 0): raise GameOver return l def takeRound( self, rnd ): take = rnd.others + rnd.actives profit = max( rnd.actives ) - min( rnd.actives ) #profit = max( rnd.actives ) - sum( rnd.others + [min( rnd.actives )] ) #profit = max( rnd.actives ) - max( rnd.others + [min( rnd.actives )] ) self.take_profit = self.take_profit + profit self.num_takes = self.num_takes + 1 if (self.strategy == SORT_TAKE): take.sort() take.reverse() else: take = randomize( take ) self.take = self.take + take if trace: print "%s.takeRound(): %s, profit=%d" % (self.name, take, profit) def recycleHand( self ): self.hand = self.take if (self.strategy == SHUFFLE_HAND): self.hand = randomize( self.hand ) elif (self.strategy == SORT_HAND): self.hand.sort() self.hand.reverse() self.take = [] if trace: print "%s.recycleHand(): %s" % (self.name, self.hand) def getNumCards( self ): return len(self.hand) + len(self.take) def getAvgCard( self ): l = self.hand + self.take if not l: return 0.0 avg = float(sum(l)) / float(len(l)) return avg def getTakeProfit( self ): if self.num_takes: return self.take_profit / float(self.num_takes) else: return 0.0 def randomize( l ): new_l = [] while l: i = random.randint( 0, len(l)-1 ) new_l.append( l.pop(i) ) return new_l def sum( l ): return reduce( lambda x, y: x + y, l ) def getOptions(): try: (tt, args) = getopt.getopt( sys.argv[1:], "", ["stats="] ) except getopt.error: usage() odict = {} for t in tt: s = t[0] while (s[0] == "-"): # Strip leading '-'s s = s[1:] odict[s] = t[1] if args: usage() return (odict, args) #------------------------------------------------------------------------------ if __name__ == "__main__": #------------------------------------------------------------------------------ main ( )