All pastes #2051963 Raw Edit

actions.py from Doomtown CCG on

public python v1 · immutable
#2051963 ·published 2011-04-29 17:59 UTC
rendered paste body
#---------------------------------------------------------------------------# Constants#---------------------------------------------------------------------------import rephases = [    'It is currently in the Pre-game Setup Phase',    "It is now GAMBLIN' time. Play Lowball!",    "The time has come to pay your UPKEEP",    "It is now HIGH NOON",    "NIGHTFALL has come."]### Highlight Colours ###DoesntUnbootColor = "#ffffff"AttackColor = "#ff0000"DefendColor = "#0000ff"DisputedColor = "#cc6633"DrawHandColor = "#000000"### Markers ###WantedMarker = ("wanted", "0a5fabc8-fe56-481a-b45a-a9ad6917d0d9")HarrowedMarker = ("harrowed", "661f2771-6b73-414b-b0b1-3e2b50386b71")HNActivatedMarker = ("High Noon Ability", "836dfd81-805b-489a-a3d6-b55d68ff5a71")SHActivatedMarker = ("Shootout Ability", "197d8de5-d63f-4455-9144-7a106b192a02")InfluencePlusMarker = ("+1 Influence", "43a4a6ba-d63b-46fd-8305-f9ffedf74f6d")InfluenceMinusMarker = ("-1 Influence", "b59df5b4-708b-481a-8b38-2d50eac48465")ControlPlusMarker = ("+1 Control", "6dbe2df7-4e9f-4e52-ab7d-c80afd7356ae")ControlMinusMarker = ("-1 Control", "474f95a6-f1a9-4e23-ba4a-8eaba8b7cc0b")ProdPlusMarker = ("+1 Production", "ddba0f0a-0c34-48b5-b7ea-ad1e1ab07c12")ProdMinusMarker = ("-1 Production", "869eebe2-f503-4d9f-8fa3-af708c7ad70c")JailbreakMarker = ("jailbroken", "692a1ab1-9aa9-49da-aff5-114644da921f")### Misc ###loud = 'loud' # So that I don't have to use the quotes all the time in my function callssilent = 'silent' # Same as above	#---------------------------------------------------------------------------# Global variables#---------------------------------------------------------------------------phaseIdx = 0 # This variable records on which phase we are.ShootoutActive = 0 # A variable to keep track if we are in a shootout phaseplayerside = None # Variable to keep track on which side each player isstrikeCount = 0 # Used to automatically place strikestopSideCount = 0 # Used to automatically place in-town deeds bottomSideCount = 0 # Same as abovehandsize = 5 # Used when automatically refilling your handplayerOutfit = None # Variable to keep track of the player's outfit.#---------------------------------------------------------------------------# General functions#---------------------------------------------------------------------------def fullsuit(suit): # This function simply returns the full suit of the various cards so that notifications read easily.   if suit == "C": return "Clubs"   elif suit == "S": return "Spades"   elif suit == "D": return "Diamonds"   elif suit == "H": return "Hearts"   else: return ""   def fullrank(rank): # This function simply returns the full rank of non-numeral cards so that notifications read easily   if rank == "Q": return "Queen of"   elif rank == "J": return "Jack of"   elif rank == "K": return "King of"   elif rank == "A": return "Ace of"   elif rank == "W": return "Joker"   else: return rank   def chooseSide(): # Called from many functions to check if the player has chosen a side for this game.   mute()   global playerside   if playerside == None:  # Has the player selected a side yet? If not, then...      if confirm("Will you play on the right side?"): # Ask which side they want         playerside = 1 # This is used to swap between the two halves of the X axis of the play field. Positive is on the right.      else:         playerside = -1 # Negative is on the left.         def num (s): # This function reads the value of a card and returns an integer. For some reason integer values of cards are not processed correctly# see bug 373 https://octgn.16bugs.com/projects/3602/bugs/188805# This function will also return 0 if a non-integer or an empty value is provided to it as it is required to avoid crashing your functions.   if s == '+*' or s == '*': return 0   if not s: return 0   try:      return int(s)   except exceptions.ValueError:      return 0#---------------------------------------------------------------------------# Table group actions#---------------------------------------------------------------------------def Pass(group, x = 0, y = 0): # Player says pass. A very common action.   notify('{} Passes.'.format(me))def showCurrentPhase(group, x = 0, y = 0): # Just say a nice notification about which phase you're on.   notify(phases[phaseIdx].format(me))def test(group, x = 0, y = 0): # Testing function.   notify('Counter is {}'.format(globals.Phase))   def nextPhase(group, x = 0, y = 0):  # Function to take you to the next phase. # Unfortunately, this is per-player. So I can use the phases for control unless I find a way to have a variable shared with both players.   global phaseIdx    if phaseIdx == 4: phaseIdx = 1 # In case we're on the last phase (Nightfall), go back to the first game phase (Gamblin')   else: phaseIdx += 1 # Otherwise, just move up one phase   showCurrentPhase(group)	def goToGamblin(group, x = 0, y = 0): # Go directly to the gamblin' phase   global phaseIdx   phaseIdx = 1   showCurrentPhase(group)def goToUpkeep(group, x = 0, y = 0): # Go directly to the Upkeep phase   global phaseIdx   phaseIdx = 2   showCurrentPhase(group)	def goToHighNoon(group, x = 0, y = 0): # Go directly to the High Noon phase   global phaseIdx   phaseIdx = 3   showCurrentPhase(group)def goToNightfall(group, x = 0, y = 0): # Go directly to the Nightfall phase   global phaseIdx   phaseIdx = 4   showCurrentPhase(group)   def goToSetup(group, x = 0, y = 0):  # Go back to the Pre-Game Setup phase.# This phase is not rotated with the nextPhase function as it is a way to basically restart the game.# It also serves as a control, so as to avoid a player by mistake using the setup function during play.   global phaseIdx, ShootoutActive, playerside, strikeCount, topSideCount, bottomSideCount, handsize, playerOutfit    # Import all our global variables and reset them.   ShootoutActive = 0   playerside = None   strikeCount = 0   topSideCount = 0   bottomSideCount = 0   handsize = 5   phaseIdx = 0   playerOutfit = None   showCurrentPhase(group)	def goToShootout(group, x = 0, y = 0): # Start or End a Shootout Phase    global ShootoutActive    if ShootoutActive == 0: # The shootout phase just shows a nice notification when it starts and does nothing else.       notify("A shootout has broken out.".format(me))       ShootoutActive = 1    else: # When the shootout ends however, any card.highlights for attacker and defender are quickly cleared.       notify("The shootout has ended.".format(me))       ShootoutActive = 0       cards = (card for card in table                     if card.highlight == DefendColor                     or card.highlight == AttackColor)       for card in cards: card.highlight = None           def boot(card, x = 0, y = 0): # Boot or Unboot a card. I.e. turn it 90 degrees sideways or set it straight.   mute()   card.orientation ^= Rot90 # This function rotates the card +90 or -90 degrees depending on where it was.   if card.orientation & Rot90 == Rot90: # if the card is now at 90 degrees, then announce the card was booted      notify('{} boots {}'.format(me, card))   else: # if the card is now at 0 degrees (i.e. straight up), then announce the card was unbooted      notify('{} unboots {}'.format(me, card))		def ace(cards, x = 0, y = 0): # Ace a card. I.e. kill it and send it to the boot hill (i.e.graveyard)   mute()   for c in cards:	# This function can be used at more than one card as the same time. Useful for sending dudes and attached cards to the boot hill quickly.      c.moveTo(me.piles['Boot Hill'])      notify("{} has aced {}.".format(me, c))      if c.markers[WantedMarker] == 1: notify("{} was wanted! Don't forget your bounty.".format(c)) # Remind the player to take a bounty for wanted dudes.def discard(cards, x = 0, y = 0): # Discard a card.   mute()   for c in cards:	# Can be done at more than one card at the same time, since attached cards follow their parent always.      c.moveTo(me.piles['Discard Pile'])      notify("{} has discarded {}.".format(me, c))  def upkeep(group, x = 0, y = 0): # Automatically receive production and pay upkeep costs# This function goes through each of your cards and checks if it provides production or requires upkeep, then automatically removes it from your bank.   mute()   gr = 0 # Variable used to track each cards production/upkeep   concat_prod = '' # A string which we will use to provide a succint notification at the end   concat_upk = '' # Same as above   prod = 0 # Variable to track total production received.   upk = 0 # Variable to track total upkeep paid.   cards = (card for card in table # Create a group with all the cards you own and control on the table.                 if card.owner == me # you cannot pay or produce from cards you do not own.                 and card.controller == me # you cannot pay or produce from cards you do not control.                 and not card.highlight == DisputedColor) # Just another way to signify lost control.   for card in cards: # For each card...      gr = num(card.Production) # Grab it's production (usually 0 for most non-deeds)      gr += card.markers[ProdPlusMarker] # Add any +production markers you have on the card.      gr -= card.markers[ProdMinusMarker] # Remove any -production markers you have on the card.      gr -= 2 * card.markers[JailbreakMarker] # Remove 2 production for each jailbroken marker you have on the card.      if gr > 0: # If we still have any production left,                  # then we increment the total production we have for this turn                  # and add the name of the card to our concatenated string of all the cards that produced this turn                 # This IF statement is required to avoid adding the names of cards that do not produce, like most dudes and goods, into our string.         prod += gr         concat_prod += str(gr) # This is where the concatenation happens         concat_prod += ' GR from '         concat_prod += card.name         concat_prod += '. '      gr = num(card.Upkeep) # Now grab it's upkeep (Usually 0 for most deeds)      if gr > 0: # Much like production, if only add the name to the string if it's having any upkeep         upk += gr         concat_upk += str(gr)         concat_upk += ' GR for '         concat_upk += card.name         concat_upk += '. '   notify("{} has produced {} ghost rock this turn: {}".format(me, prod, concat_prod)) # Inform the players how much they produced and from where.   me.GhostRock += prod # Then add the money to their Ghost Rock counter.                        # Note that you can only modify counters with a single-string name due to bug 372                        # See https://octgn.16bugs.com/projects/3602/bugs/188803   if upk > 0: # If we need to pay any upkeep, we do it after receiving production for the turn.      if me.GhostRock < upk: # If we cannot pay with the money we have, then let the player decide what to do.                             # I could have made their bank account negative and let them modify it manually, but I think this way is better.         notify("{} has {}GR in their bank but needs to pay {}GR for upkeep ({}). No GR has been taken but please discard enough cards with upkeep and reduce your remaining Ghost Rock manually.".format(me, me.GhostRock, upk, concat_upk))      else: # If we can pay the upkeep, do so.         notify("{} has paid {} upkeed in total this turn. {}".format(me, upk, concat_upk)) #Inform the players how much they paid and for what.         me.GhostRock -= upk # Then take the money out of their bank         def HNActivate(card, x = 0, y = 0): # A function to add or remove High Noon (HN) markers.                                     # Those markers are used to signify when a high noon ability has been used,                                     # as printed abilities can only ever be used once per turn, even if they do not require booting.                                    # These markers are only removed at the end of the turn.   mute()   if card.markers[HNActivatedMarker] == 0: # If we have no HN markers, add one      notify("{} uses {}'s High Noon ability.".format(me, card))      card.markers[HNActivatedMarker] += 1   else: # If we have have such a marker, assume the player did it by mistake and inform everyone.      notify("{} Wanted to use {}'s High Noon ability but it has already been used this turn. Did they use a card effect?".format(me, card))	  def SHActivate(card, x = 0, y = 0): # Same process as the HN markers above   mute()   if card.markers[SHActivatedMarker] == 0:      notify("{} uses {}'s Shootout ability.".format(me, card))      card.markers[SHActivatedMarker] += 1   else:      notify("{} Wanted to use {}'s Shootout ability but it has already been used this turn.".format(me, card))		def nightfall(card, x = 0, y = 0): # This function "refreshes" each card for nightfall.                                   # This practically means that we remove any High Noon and Shootout ability markers and unboot the card                                   # But only if it's not marked as a card that does not unboot   mute()   card.markers[HNActivatedMarker] -= 1 # Remove the markers.   card.markers[SHActivatedMarker] -= 1   if card.highlight != DoesntUnbootColor : card.orientation = Rot0 # And if we can unboot the card, turn it to 0 degrees.def NightfallUnboot(group, x = 0, y = 0): # This function simply runs all the cards the player controls through the nigtfall function.   mute()   if not confirm("Have you remembered to discard any cards you don't want from your play hand?"): return   refill()   cards = (card for card in table                 if card.controller == me)   for card in cards: nightfall(card)   notify("Nightfall refreshes {} cards and refills their hand back to {}.".format(me, handsize))   def doesNotUnboot(card, x = 0, y = 0): # Mark a card as "Does not unboot" or unmark it. We use a card highlight to do this.   if card.highlight == DoesntUnbootColor: # If it's already marked, remove highlight from it and inform.      card.highlight = None      notify("{}'s {} can now unboot during Nightfall.".format(me, card))   else:      card.highlight = DoesntUnbootColor # Otherwise highlight it and inform.       notify("{}'s {} will not unboot during Nightfall.".format(me, card))      def spawnTokenDude(group, x = 0, y = 0): # Simply put a fake card in the game.   table.create("88e598eb-65e0-4ae3-8416-52df58a5538f", x, y, 1)    def inspectCard(card, x = 0, y = 0): # This function shows the player the card text, to allow for easy reading until High Quality scans are procured.   confirm("{}".format(card.text)) #---------------------------------------------------------------------------# Marker functions#---------------------------------------------------------------------------def plusControl(card, x = 0, y = 0, notification = 'loud'): # Adds an extra control marker to cards (usually deeds)    mute()    if notification == 'loud' :      notify("{} marks that {} provides one more control point (Their total has been adjusted accordingly).".format(me, card))    if ControlMinusMarker in card.markers: # If we have a -CP counter already, just remove on of those.        if num(card.Control) - card.markers[ControlMinusMarker] >= 0:            modControl() # This function takes care of modifying the player's control counter. In this case it increases it by 1.                        # But we only go through with it if the -CP markers didn't actually take the cards' CP below 0.        card.markers[ControlMinusMarker] -= 1    else: # Otherwise just add an extra +CP        card.markers[ControlPlusMarker] += 1        modControl()         def minusControl(card, x = 0, y = 0, notification = 'loud'): # Similar to adding Control but we remove instead.   mute()   if notification == 'loud' :      notify("{} marks that {} provides one less control point (Their total has been adjusted accordingly).".format(me, card))   if ControlPlusMarker in card.markers:      card.markers[ControlPlusMarker] -= 1      modControl(-1)   else:      if num(card.Control) - card.markers[ControlMinusMarker] > 0: modControl(-1) # We only reduce a players totals if the control was above 0                                                                                  # As the minimum CP on a card is always 0.      card.markers[ControlMinusMarker] += 1     def plusInfluence(card, x = 0, y = 0): # The same as pluControl but for influence   mute()   notify("{} marks that {} is now more influential ({}'s total has been adjusted automatically)".format(me, card, me))   if InfluenceMinusMarker in card.markers:      if num(card.Influence) - card.markers[InfluenceMinusMarker] >= 0:             modInfluence()      card.markers[InfluenceMinusMarker] -= 1   else:      card.markers[InfluencePlusMarker] += 1               modInfluence()        def minusInfluence(card, x = 0, y = 0): # The same as minusContorl but for influence   mute()   notify("{} marks that {} is now less influential ({}'s total has been adjusted automatically).".format(me, card, me))   if InfluencePlusMarker in card.markers:      card.markers[InfluencePlusMarker] -= 1      modInfluence(-1)   else:      if num(card.Influence) - card.markers[InfluenceMinusMarker] > 0: modInfluence(-1)      card.markers[InfluenceMinusMarker] += 1         def plusProd(card, x = 0, y = 0): # Very much like plus Influence and control, but we don't have to worry about modifying the player's totals   mute()   notify("{} marks that {}'s production has increased by 1GR (This will be automatically taken into account during upkeep).".format(me, card))   if ProdMinusMarker in card.markers:      card.markers[ProdMinusMarker] -= 1   else:      card.markers[ProdPlusMarker] += 1                 def minusProd(card, x = 0, y = 0):    mute()   notify("{} marks that {}'s production has decreased by 1GR (This will be automatically taken into account during upkeep).".format(me, card))   if ProdPlusMarker in card.markers:      card.markers[ProdPlusMarker] -= 1   else:      card.markers[ProdMinusMarker] += 1  def addMarker(cards, x = 0, y = 0): # A simple function to manually add any of the available markers.   mute()   marker, quantity = askMarker() # Ask the player how many of the same type they want.   if quantity == 0: return   for c in cards: # Then go through their cards and add those markers to each.      c.markers[marker] += quantity      notify("{} adds {} {} counter to {}.".format(me, quantity, marker[0], c))	#---------------------------------------------------------------------------# Deed actions#---------------------------------------------------------------------------      def modDisputed(card, x = 0, y = 0): # Set a deed as disputed. This is marked via a card highlight. Same process as doesNotUnboot but only for deeds.   mute()   if card.type == "Deed":      if card.highlight == DisputedColor:         card.highlight = None         notify("{}'s control has returned to {}".format(card, card.owner))      else:         card.highlight = DisputedColor         notify("{} has lost control of {}".format(card.owner, card))         def locationTarget(card, x = 0, y = 0): # A function to let others know where you are moving.                                         # Unfortunately one cannot initiate card actions on cards they do not control.                                        # Which prevents us from doing much with this.                                         # At the future I'd like to automatically read the locations coordinates and move dudes to an appropriate.                                        # location, but this requires that one can init actions on cards they do not control.   mute()   if card.type == "Deed" or card.type == "Outfit":          notify("{} announces {} as the location.".format(me, card))      card.target        def addJailbreakMarker(card, x = 0, y = 0): # Jailbreak is the result of a specific action card in the game that is permanent and wipes all the actions from the card so we need to keep track of it.# It also reduces CP and production. For the reduction of CP we use the minusControl function silently, so that it works better with any +CP markers# But for the reduced production, we simply take care of it during upkeep.   mute()   if card.type == "Deed":      notify("{} has been severely damaged.".format(card))      card.markers[JailbreakMarker] += 1      minusControl(card, x, y, silent)#---------------------------------------------------------------------------# Dude actions#---------------------------------------------------------------------------def modWantedMarker(card, x = 0, y = 0): # Similar to the doesNotUnboot function but with markers. Adds or removes the wanted marker from a dude.   mute()   if card.type == "Dude":      if card.markers[WantedMarker] == 0:         notify("{} is now wanted by the law.".format(card))         card.markers[WantedMarker] += 1      else:         notify("The name of {} is cleared.".format(card))         card.markers[WantedMarker] -= 1	         def addHarrowedMarker(card, x = 0, y = 0): # Same as the modWantedMarker but you cannot remove it. You get a notification instead.   mute()   if card.type == "Dude":      if card.markers[HarrowedMarker] == 1 or re.search(r'\bHarrowed\b', card.text):         notify("Is already harrowed! There's only space for one manitou in thar.".format(card))      else:         notify("{} has come back from the grave as one of the Harrowed.".format(card))         card.markers[HarrowedMarker] += 1def callout(card, x = 0, y = 0): # Notifies that this dude is calling someone out.   mute()   if card.type == "Dude":      notify("{} is calling someone out.".format(card))      if card.orientation == Rot90: notify("(Remember that you need a card effect to call out someone while booted)".format(card))def move(card, x = 0, y = 0): # Notifies that this dude is moving without booting   mute()   if card.type == "Dude":      notify("{} is moving without booting.".format(card))      if card.orientation == Rot90: notify("(Remember that you need a card effect to move while booted)".format(card))      def moveBoot(card, x = 0, y = 0): # Notifies that this dude is moving by booting   mute()   if card.orientation == Rot0 and card.type == "Dude":         notify("{} is booting to move.".format(card))         card.orientation = Rot90def goods(card, x = 0, y = 0): # Notifies that this dude is about to receive some goods, either by trading or by playing from your hand                               # In the future, I want to make this function provide a "lock" for incoming goods, and then once you select the goods                               # ...or play them from hand, they will be automatically moved below the dude with their title showing.   mute()   if card.type == "Dude":      notify("{} is receiving some goods.".format(card))      if card.orientation == Rot90: notify("(Remember that you need a card effect to receive goods while booted)".format(card))               def tradeGoods(cards, x = 0, y = 0): # Notified that this dude is giving away some goods.                                      # Allows one to target dude and goods at the same time for quick use.   mute()   for c in cards:	      if c.type == "Dude":         notify("{} is trading away some of their goods.".format(c))      if c.type == "Goods":         notify("{} is being traded.".format(c))#---------------------------------------------------------------------------# Posse actions#---------------------------------------------------------------------------        def joinAttack(card, x = 0, y = 0): # Informs that this dude joins an attack posse and highlights him accordingly.                                     # This is to help track who is shooting it out. The highlights are cleared by the goToShootout function.   if card.type == "Dude" : # This is something only dudes can do       mute ()        notify("{} is joining the attacking posse.".format(card))       card.highlight = AttackColordef joinDefence(card, x = 0, y = 0): # Same as above, but about defensive posse.   if card.type == "Dude" :       mute ()      notify("{} is joining the defending posse.".format(card))      card.highlight = DefendColor   def acceptCallout(card, x = 0, y = 0): # Same as the defending posse but with diferent notification.   if card.type == "Dude" :       mute ()      notify("{} has accepted the call out.".format(card))      card.highlight = DefendColor   def refuseCallout(card, x = 0, y = 0): # Boots the dude and moves him to your home or informs you if they cannot refuse.   global playerside   if card.type == "Dude" :       chooseSide()      mute ()      if card.orientation == Rot90: # If the dude is booted, they cannot refuse         notify ("Booted Dudes cannot refuse a Call Out!")      else:         notify("{} has turned yella and run home to hide.".format(card))         card.orientation = Rot90 # If they refure boot them...         card.moveToTable(playerside * 190, 0) # ...and move them where we expect the player's home to be.def runAway(card, x = 0, y = 0): # Same as above pretty much but also clears the shootout highlights.   global playerside   if card.type == "Dude" :       chooseSide()      mute ()      notify("{} is running away from the shootout.".format(card))      card.orientation = Rot90      card.moveToTable(playerside * 190, 0)      card.highlight = None      def posseReady (group, x = 0, y = 0):   notify("{}'s Posse is Ready to throw down!".format(me))     #---------------------------------------------------------------------------# Hand and Deck actions#---------------------------------------------------------------------------def modInfluence(count = 1, notification = 'silent'): # A function to modify the players influence counter. Can also notify.   count = num(count) # We need to make sure we get an integer or we will fail horribly. OCTGN doesn't seem to respect its own definitions.   me.Influence += count # Now increase the influence by the amount passed to us.   if notification == 'loud' and count > 0: notify("{}'s influence has increased by {}. New total is {}".format(me, count, me.Influence))     # We only notify if the function is called as "loud" and we actually modify anything.def modControl(count = 1, notification = 'silent'): # Same as above but for Control Points   count = num(count)   me.Control += count   if notification == 'loud' and count > 0: notify("{}'s control points have increased by {}. New total is {}".format(me, count, me.Control))         def payCost(count = 1, notification = 'silent'): # Same as above for Ghost Rock. However we also check if the cost can actually be paid.   count = num(count)   if me.GhostRock < count: # If we don't have enough Ghost Rock in the bank, we assume card effects or mistake and notify the player that they need to do things manually.      if notification == 'loud' and count > 0: notify("{} was supposed to pay {} Ghost Rock but only has {} in their bank. Assuming card effect used. **No GR has been taken!** Please modify your bank manually as necessary".format(me, count, me.GhostRock))      else: # Otherwise, just take the money out and inform that we did if we're "loud".      me.GhostRock -= num(count)      if notification == 'loud' and count > 0: notify("{} has paid {} Ghost Rock. {} is left their bank".format(me, count, me.GhostRock))     def playcard(card): # This is the function to play cards from your hand. It's one of the core functions# It will automatically pay the cost of cards if you can, or inform you if you cannot.# If the card being played has influence or Control points, those will automatically be added to the player's total.# Dudes and deeds will be placed at default locations to facilitate quicker play.   global playerside, strikeCount, topSideCount, bottomSideCount # Import some variables to know where the player is seated and how much they've built.   mute()   chooseSide()   if card.type == "Dude" :       card.moveToTable(playerside * 200, 0) # Move the dude next to where we expect the player's home card to be.      notify("{} has hired {}.".format(me, card)) # Inform of the new hire         elif card.type == "Deed" :         if re.search('Strike', card.Text) or re.search('Out of Town', card.Text): # Check if we're bringing out an out of town deed         card.moveToTable(playerside * 300, -200 + strikeCount * 90) # If we do, put it over and behind the player's home area.         strikeCount += 1 # Increment this counter. Extra out of town deeds will be placed below the previous ones.      else:         if confirm("Do you want to place this deed on the bottom side of your street?"): # If it's a city deed, then ask the player where they want it.            bottomSideCount += 1 #If it's on the bottom, increment the counter...            card.moveToTable(playerside * 150, bottomSideCount * 90) # ...and put the deed below all other deeds already there.         else:            topSideCount += 1 # Same as above but going upwards from home.            card.moveToTable( playerside * 150, -1 * (topSideCount * 90))       notify("{} has acquired the deed to {}.".format(me, card))   elif card.type == "Goods" : # If we're bringing in any goods, just remind the player to pull for gadgets.      if re.search('Gadget', card.Text): notify("{} is trying to create a {}. Don't forget to pull!".format(me, card))      else: notify("{} has purchased {}.".format(me, card))      card.moveToTable(0,0)   elif card.type == "Spell" : # For spells, just change the notification text.      card.moveToTable(0,0)      notify("One of {}'s dudes has learned {}.".format(me, card))         elif card.type == "Improvement" : # For improvements, just change the notification text.      card.moveToTable(0,0)      notify("{} is improving one of his Deeds with {}.".format(me, card))         else:       card.moveToTable(0,0) # For anything else, just say they play it.      notify("{} plays {} from their hand.".format(me, card))   modControl(card.Control, loud) # Increase control, if the new card provides is any.   modInfluence(card.Influence, loud) # Increase influence, if the new card provides any.   payCost(card.Cost, loud) # Take cost out of the bank, if there is any.    def setup(group):# This function is usually the first one the player does. It will setup their home and cards on the left or right of the playfield # It will also setup the starting Ghost Rock for the player according to the cards they bring in play, as well as their influence and CP.   if phaseIdx == 0: # First check if we're on the pre-setup game phase.                      # As this function will play your whole hand and wipe your counters, we don't want any accidents.      global playerside, playerOutfit # Import some necessary variables we're using around the game.      mute()      chooseSide() # The classic place where the players choose their side.      dudecount = 0      concat_dudes = 'and has the following starting dudes: ' # A string where we collect the names of the dudes we bring in      concat_home = '' # A string to remember our home's name      concat_other = '' # A string to remember any other card (like sweetrock's mine)      me.Deck.shuffle() # First let's shuffle our deck now that we have the chance.      me.GhostRock = 0 # Wipe the counters      me.Influence = 0      me.Control = 0      for card in group: # For every card in the player's hand... (which should be an outfit and a bunch of dudes usually)         if card.type == "Outfit" :  # If it's the outfit...            card.moveToTable(playerside * 150, 0) # We move it to one side depending on what side the player chose.            me.GhostRock += num(card.properties['Ghost Rock']) # Then we add its starting Ghost Rock to the bank            playerOutfit = card.Outfit # We make a note of the outfit the player is playing today (used later for upkeep)            concat_home += card.name # And we save the name.         elif card.type == "Dude" : # If it's a dude...            card.moveToTable(playerside * 220 + playerside * (dudecount * 70), 0) # We move them behind the house            dudecount += 1 # This counter increments per dude, ad we use it to move each other dude further back.            payCost(card.Cost) # Pay the cost of the dude            modInfluence(card.Influence, silent) # Add their influence to the total            concat_dudes += card.name # And prepare a concatenated string with all the names.            concat_dudes += '. '         else: # If it's any other card...            card.moveToTable(playerside * 200, playerside * -90) # We move the card around the player's area.            payCost(card.Cost) # We pay the cost             modControl(card.Control) # Add any control to the total            modInfluence(card.Influence) # Add any influence to the total            concat_other = ', brings ' # And we create a special concat string to use later for the notification.            concat_other += card.name #             concat_other += ' into play'         if dudecount == 0: concat_dudes = 'and has no starting dudes. ' # In case the player has no starting dudes, we change the notification a bit.      refill() # We fill the player's play hand to their hand size (usually 5)      notify("{} is playing {} {} {}Starting Ghost Rock is {} and starting influence is {}.".format(me, concat_home, concat_other, concat_dudes, me.GhostRock, me.Influence))        # And finally we inform everyone of the player's outfit, starting dudes & other cards, starting ghost rock and influence.   else: confirm('You can only setup your starting cards during the Pre-Game setup phase') # If this function was called outside the pre-game setup phase                                                                                           # We assume a mistake and stop.    def shuffle(group): # A simple function to shuffle piles   group.shuffle()def reshuffle(group = me.piles['Discard Pile']): # This function reshuffles the player's discard pile into their deck.   mute()   Deck = me.Deck # Just to save us some repetition   for c in group: c.moveTo(Deck) # Move the player's cards from the discard to their deck one-by-one.   random = rnd(100, 10000) # Bug 105 workaround. This delays the next action until all animation is done.                            # see https://octgn.16bugs.com/projects/3602/bugs/102681   Deck.shuffle() # Then use the built-in shuffle action   notify("{} moved his {} into his Deck.".format(me, group.name)) # And inform everyone.def draw(group = me.Deck): # Draws one card from the deck into the player's hand.   mute()   if len(group) == 0: # In case the deck is empty, invoke the reshuffle function.      notify("{}'s Deck empty. Will reshuffle discard pile".format(me))      reshuffle()   group.top().moveTo(me.hand)   notify("{} draws a card.".format(me))      def Pull(group = me.Deck, x = 0, y = 0): # Draws one card from the deck into the discard pile and announces its value.   mute()   Deck = me.Deck   if len(Deck) == 0: # In case the deck is empty, invoke the reshuffle function.      notify("{}'s Deck empty. Will reshuffle discard pile".format(me))      reshuffle()      random = rnd(100, 10000) # Bug workaround. We wait a bit so that we are sure the cards are there.   Deck.top().moveTo(me.piles['Discard Pile']) # Move the top card from the deck into the discard pile   random = rnd(100, 10000) # Wait a bit more, as in multiplayer games, things are slower.   rank = fullrank(me.piles['Discard Pile'].top().rank) # Save the card's rank   suit = fullsuit(me.piles['Discard Pile'].top().suit) # Save the card's suit   notify("{} Pulled a {} {}.".format(me, rank, suit))  # Announce them nicely to everyone.def drawMany(group, count = None, notification = 'loud'): # This function draws a variable number cards into the player's hand.   mute()   if len(group) == 0: reshuffle() # If the deck is empty, reshuffle.   if count == None: count = askInteger("Draw how many cards to your Play Hand?", 5) # Ask the player how many cards they want.   for c in group.top(count): c.moveTo(me.hand) # Then move them one by one into their play hand.   if notification == 'loud' : notify("{} draws {} cards to their play hand.".format(me, count)) # And if we're "loud", notify what happened.def setHandSize(group): # A function to increase a player's hand size. This is used during nighfall when refilling the player's hand automatically.   global handsize   handsize = askInteger("What is your current hand size?", handsize)   def refill(group = me.hand): # Refill the player's hand to its hand size.   global handsize   playhand = len(me.hand) # count how many cards there are currently there.   if playhand < handsize: drawMany(me.Deck, handsize - playhand, silent) # If there's less cards than the handsize, draw from the deck until it's full.	def handDiscard(card, x = 0, y = 0): # Discard a card from your hand.   mute()   card.moveTo(me.piles['Discard Pile'])   notify("{} has discarded {}.".format(me, card))  def randomDiscard(group): # Discard a card from your hand randomly.   mute()   card = group.random() # Select a random card   if card == None: return # If hand is empty, do nothing.   notify("{} randomly discards a card.".format(me)) # Inform that a random card was discarded   card.moveTo(me.piles['Discard Pile']) # Move the card in the discard pile.	def moveIntoDeck(group):    mute()   Deck = me.Deck   for c in group: c.moveTo(Deck)   notify("{} moves his {} into his Deck.".format(me, group.name))   def drawhandMany(group, count = None, silent = 'No'): #Same as drawMany, but puts the cards into the player's Draw Hand pile.   if len(group) == 0: reshuffle()   mute()   if count == None: count = askInteger("Draw how many cards to your Draw Hand?", 5)   for c in group.top(count): c.moveTo(me.piles['Draw Hand'])   if silent == 'No' : notify("{} draws {} cards to their draw hand.".format(me, count))   def discardDrawHand(group = me.piles['Draw Hand']): # Discards the player's whole Draw Hand.   mute()   Discard = me.piles['Discard Pile']   notify("{} moved his {} ({} cards) to his discard pile.".format(me, group.name, len(group)))       for c in group: c.moveTo(Discard)   def revealShootoutHand(group = me.piles['Draw Hand']): # This function moves 5 cards from the player's Draw Hand pile into the table (normally there should be only 5 there when this function is invoked)# It also highlights those cards, so that they are not confused with the cards in play# The cards are moved to the table relevant to the player's side and they are placed next to each other so that their suit&ranks are read easily# Finally their suit and rank are announced   global playerside # So that we know where to place them on the table   chooseSide() # Just in case no side was chosen.   mute()   i = 0    rank = ['','','','',''] # We create some empty tables for the suits and ranks.   suit = ['','','','','']   for card in group: # For each card in the Draw Hand pile...      card.moveToTable(playerside * 100 + i*20, 200) # Move the card to the table, 20 pixels to the right of any other cards from this hand      card.highlight = DrawHandColor # Highlight them      rank[i] = fullrank(card.rank) # save their rank into the table      suit[i] = fullsuit(card.suit) # save their suit into the table      i += 1 # prepare for the next card.   notify("{}'s Shootout hand is: {} {}, {} {}, {} {}, {} {}, {} {}. ".format(me, rank[0], suit[0], rank[1], suit[1], rank[2], suit[2], rank[3], suit[3], rank[4], suit[4]))   # Finally, inform the players on what the hand is. In the future this is going to be modified to actually state what poker hand this is.   def revealLowballHand(group = me.piles['Draw Hand']): # Same as revealShootoutHand but the cards are placed on the bottom side of the play field and the notification changes a bit.   global playerside   chooseSide()   mute()   i = 0   rank = ['','','','','']   suit = ['','','','','']   for card in group:       card.moveToTable(playerside * 100 + i*20, -200)      card.highlight = DrawHandColor      rank[i] = fullrank(card.rank)      suit[i] = fullsuit(card.suit)      i += 1   notify("{}'s Lowball Hand is: {} {}, {} {}, {} {}, {} {}, {} {}. ".format(me, rank[0], suit[0], rank[1], suit[1], rank[2], suit[2], rank[3], suit[3], rank[4], suit[4]))