Exemplo n.º 1
0
def get_intel_options(G, card, *targets):
	opts = xset()
	# print('intel')
	if card.intelligence == 'Coup':
		
		for target in targets:
			topts = xset(G.objects.table[iid].nation for iid in G.players[target].influence)
			if len(topts):
				opts.add((target, topts))
	
	elif card.intelligence == 'Agent':
		for target in targets:
			topts = xset(unit.tile for unit in G.players[target].units.values())
			if len(topts):
				opts.add((target, topts))
	
	elif card.intelligence == 'Spy_Ring':
		opts.update(target for target in targets if len(G.players[target].hand))
	
	elif card.intelligence == 'Sabotage':
		opts.update(target for target in targets if G.players[target].tracks.IND > 0)
	
	elif card.intelligence == 'Code_Break':
		opts.update(target for target in targets if len(G.players[target].hand))
	
	elif card.intelligence == 'Mole':
		opts.update(target for target in targets if len(G.players[target].secret_vault))
	
	else:
		raise Exception('Unknown intelligence card type: {}'.format(card.intelligence))
	
	return opts
Exemplo n.º 2
0
def check_revealable(G, player):
    options = xset()
    faction = G.players[player]
    if len(faction.secret_vault):
        # options.add(('reveal', xset(faction.secret_vault)))
        options.add((xset(faction.secret_vault), ))
    return options
Exemplo n.º 3
0
def encode_government_actions(G):
    code = adict()

    active_player = G.game.turn_order[G.temp.active_idx]
    faction = G.players[active_player]

    hand = adict({ID: G.objects.table[ID] for ID in faction.hand})
    action_cards = adict(
        (k, v) for k, v in hand.items() if v.obj_type == 'action_card')
    invest_cards = adict(
        (k, v) for k, v in hand.items() if v.obj_type == 'investment_card')

    options = xset()

    # pass
    options.add(('pass', ))

    # diplomacy options
    for ID, card in action_cards.items():
        if 'top' in card:
            lopts = xset(nation for nation in [card.top, card.bottom]
                         if nation in G.diplomacy.neutrals)
            if len(lopts):
                options.add((ID, lopts))
        elif 'wildcard' in card:
            wopts = check_wildcard(G, active_player, card)
            if len(wopts):
                options.add((ID, wopts))
        else:
            raise Exception('Unknown action card properties: {}'.format(
                card.keys()))

    # factory upgrade options
    # print([(i, c.value) for i, c in invest_cards.items()])
    total_val = sum(c.value for c in invest_cards.values())
    if G.temp.past_upgrades[
            active_player] < 2 and total_val >= faction.stats.factory_cost:
        options.add(('factory_upgrade', ))
        # for combo in factory_upgrade_combos(invest_cards, faction.stats.factory_cost):
        # 	options.add(('factory_upgrade',) + combo)

    # tech options
    options.update(check_techs(G, active_player, invest_cards))

    # espionage options
    options.update(check_intelligence(G, active_player, invest_cards))

    # reveal techs from secret vault
    options.update(check_revealable(G, active_player))

    # removable inf
    rmb = removable_nations(G, active_player)
    if len(rmb):
        options.add(('remove', rmb))

    code[active_player] = options

    return code
Exemplo n.º 4
0
def check_declarations(G, player):

    options = xset()

    # wars
    options.add(
        (xset(name
              for name, war in G.players[player].stats.at_war_with.items()
              if not war), ))

    # neutral
    options.add((xset(G.diplomacy.neutrals.keys()), ))

    return options
Exemplo n.º 5
0
def encode_intel_response(G, intel_card, player, target):

    code = adict()

    options = xset()
    options.add(('accept', ))

    msg = None  #'{} plays {} and is targetting you'.format(player, intel_card.intelligence)

    for cid in G.players[target].hand:
        card = G.objects.table[cid]
        if 'intelligence' in card and card.intelligence == 'Double_Agent':

            opts = get_intel_options(G, intel_card, player)
            if len(opts):
                # msg = 'You may play your Double Agent to reverse the effect of {} played by {}'.format(intel_card.intelligence, player)
                msg = 'You may play your Double Agent to reverse the effect'
                options.add((cid, opts))

            break

    G.logger.write('{} played {} targetting {}, waiting for a response'.format(
        player, intel_card.intelligence, target))
    if msg is not None:
        G.logger.write(msg, player=target)

    code[target] = options

    # code[player] = xset(('cancel',)) # original player can cancel

    return code
Exemplo n.º 6
0
def convert_from_saveable(data):
	if data is None:
		return None
	if isinstance(data, (str, int, float)):
		try:
			return int(data)
		except:
			pass
		return data
	if isinstance(data, dict):
		
		if len(data) == 1:
			key, val = next(iter(data.items()))
			if 'set' == key:
				return tset(convert_from_saveable(el) for el in val)
			if 'xset' == key:
				return xset(convert_from_saveable(el) for el in val)
			if 'tuple' == key:
				return tuple(convert_from_saveable(el) for el in val)
			if '_object_' == key[:8]:
				typ = eval(key[8:])
				obj = typ()
				obj.load_state(val)
				return obj
		
		if '_id' in data:
			return idict({convert_from_saveable(k): convert_from_saveable(v) for k, v in data.items()})
		
		return tdict({convert_from_saveable(k): convert_from_saveable(v) for k, v in data.items()})
	if isinstance(data, list):
		return tlist(convert_from_saveable(el) for el in data)
	try:
		return data.save_state()
	except AttributeError:
		raise Exception('Cannot save data of type: {}'.format(type(data)))
Exemplo n.º 7
0
def powers_present(G, tile):
    powers = xset()
    for uid in tile.units:
        unit = G.objects.table[uid]
        owner = G.nations.designations[unit.nationality]
        powers.add(owner)
    return powers
Exemplo n.º 8
0
def encode_movement(G):
    player = G.temp.order[G.temp.active_idx]
    faction = G.players[player]
    cmd = G.temp.commands[player]

    assert cmd.value > 0, 'No more commands - shouldve moved on to next player'

    options = xset()
    options.add(('pass', ))

    if len(G.temp.has_moved) == 0 and not (
            'emergency'
            in cmd):  # no units have been moved yet -> can make declarations
        options.update(check_declarations(G, player))

    for uid, unit in faction.units.items():
        if uid in G.temp.has_moved:
            continue
        locs = travel_options(G, unit)
        if len(locs):
            options.add((unit._id, locs))

    # reveal techs in secret vault
    options.update(check_revealable(G, player))

    code = adict()
    code[player] = options
    return code
Exemplo n.º 9
0
def increment_influence(G, player, nation):
	if nation not in G.diplomacy.influence:
		inf = idict()
		inf.value = 1
		inf.nation = nation
		inf.faction = player
		inf.obj_type = 'influence'
		inf.visible = xset(G.players.keys())
		
		G.players[player].influence.add(inf._id)
		G.diplomacy.influence[nation] = inf
		G.objects.table[inf._id] = inf
		G.objects.created[inf._id] = inf
		return
	
	inf = G.diplomacy.influence[nation]
	
	if player != inf.faction and inf.value == 1:
		del G.diplomacy.influence[nation]
		G.players[inf.faction].influence.remove(inf._id)
		del G.objects.table[inf._id]
		G.objects.removed[inf._id] = inf
		return
	
	delta = (-1) ** (player != inf.faction)
	
	inf.value += delta
	G.objects.updated[inf._id] = inf
Exemplo n.º 10
0
def get_removable_nations(G, player):

    nations = xset()

    for rival in G.players[player].stats.rivals:
        for iID in G.players[rival].influence:
            nations.add(G.objects.table[iID].nation)
    return nations
Exemplo n.º 11
0
def encode_options_for_next_battle(G, player):
	#player = G.temp.order[G.temp.active_idx]
	code = adict()
	options = xset()
	for b in G.temp.combat.battles_remaining:
		options.add((b,))
	#print('* * select next battles_to_fight', options)
	code[player] = options
	return code
Exemplo n.º 12
0
def check_declarations(G, player):

    options = xset()

    # wars
    options.add(
        (xset(name
              for name, war in G.players[player].stats.at_war_with.items()
              if not war and not name in G.temp.threats), ))

    # neutral
    nations = xset(nat for nat, info in G.nations.status.items()
                   if not info.is_armed and not nat in G.temp.threats)
    # nations -= G.players[player].diplomacy.violations
    #print('check_declarations nations ', nations)
    options.add((nations, ))

    return options
Exemplo n.º 13
0
def get_adjacent_nations(G, *players):
    nations = xset()
    for player in players:
        for tilename in G.players[player].territory:
            for neighbor in G.tiles[tilename].borders.keys():
                tile = G.tiles[neighbor]
                if 'alligence' in tile:
                    nations.add(tile.alligence)
    return nations
Exemplo n.º 14
0
def removable_nations(G, player):
    nations = xset()

    for iid in G.players[player].influence:

        inf = G.objects.table[iid]
        nations.add(inf.nation)

    return nations
Exemplo n.º 15
0
def encode_battles_to_select(G, player):
	#player = G.temp.order[G.temp.active_idx]
	code = adict()
	options = xset()
	options.add(('pass',))
	for b in G.temp.combat.battles_to_select:
		options.add((b,))
	#print('* * vor code[player]=options', options)
	code[player] = options
	return code
Exemplo n.º 16
0
def check_wildcard(G, player, card):
    name = card.wildcard
    info = G.cards.info.action[name]

    options = xset()

    if 'adjacent' in info:  # either guarantee or intimidation
        if info.from_self:  # intimidation
            options.update(get_adjacent_nations(G, player))
        else:  # guarantee
            options.update(
                get_adjacent_nations(G, *G.players[player].stats.rivals))
        options.discard('USA')

        for name in G.players:
            options -= G.nations.groups[name]

    elif 'options' in info:  # contains 'options'
        if 'from_self' in info:  # personalized options
            if info.from_self:  # TtB, ET, BF, BA, V
                options.update(info.options[player])
            else:  # F&L
                for rival in G.players[player].stats.rivals:
                    options.update(info.options[rival])
        else:  # isolationism
            options.update(info.options)
    elif name == 'Foreign_Aid':
        options.update(G.diplomacy.minors)
        options.update(G.diplomacy.majors)
    else:
        raise Exception('Unknown wildcard type: {}, {}'.format(
            name, list(info.keys())))

    options = options.intersection(G.diplomacy.neutrals)

    # filter out options depending on add/remove

    if 'add' in info and 'remove' in info:
        return options

    assert 'add' in info or 'remove' in info, 'Cant do anything, card info is missing something for {}'.format(
        name)

    removable = get_removable_nations(G, player)

    if 'remove' not in info:
        options -= removable

    if 'add' not in info:
        options *= removable

    return options
Exemplo n.º 17
0
def choose_2_from(all):

    options = xset()

    while len(all):
        all = all.copy()
        x = all.pop()
        if len(all) > 1:
            options.add((x, all))
        else:
            options.add((x, all.pop()))

    return options
Exemplo n.º 18
0
def check_intelligence(G, player, cards):

    options = xset()

    for cid, card in cards.items():
        if 'intelligence' not in card or card.intelligence == 'Double_Agent':
            continue

        opts = get_intel_options(G, card, *G.players[player].stats.rivals)

        if len(opts):
            options.add((cid, opts))

    return options
Exemplo n.º 19
0
def encode_command_card_phase(G):

    code = adict()

    player = G.temp.active_players[G.temp.active_idx]
    options = xset()
    options.add('pass')

    options.update(G.players[player].hand)
    # options.update(cid for cid in G.players[player].hand if 'action' in G.objects.table[cid].obj_type)

    code[player] = (options, )

    return code
Exemplo n.º 20
0
def encode_factory_upgrade_actions(G):
	code = adict()
	
	active_player = G.game.turn_order[G.temp.active_idx]
	faction = G.players[active_player]
	
	options = xset(ID for ID in faction.hand if G.objects.table[ID].obj_type == 'investment_card')
	
	# options -= G.temp.factory_upgrade.selects # can unselect cards
	
	options.add('cancel')
	
	code[active_player] = (options,)
	return code
Exemplo n.º 21
0
def supply_phase(G, player, action):

    for pl in G.players:
        faction = G.players[pl]

        goals = xset(faction.cities.SubCapitals)
        goals.add(faction.cities.MainCapital)

        if not faction.stats.at_war:
            G.logger.write(
                '{} is at peace, so all units are supplied'.format(pl))
            continue

        u_to_remove = []
        for uid, unit in faction.units.items():
            if unit.type == 'Fortress' or G.units.rules[unit.type].type != 'G':
                continue

            supplied = find_path(G,
                                 unit.tile,
                                 goals=goals,
                                 player=pl,
                                 isUnitLoc=True)
            tile = G.tiles[unit.tile]

            if not supplied:
                unit.cv -= 1
                if unit.cv == 0:
                    u_to_remove.append(unit)
                    msg = 'was eliminated'
                    #remove_unit(G, unit)
                    # TODO: check for forced retreats of ANS
                else:
                    msg = 'lost 1 cv'
                    G.objects.updated[uid] = unit

                G.logger.write(
                    '{} unit in {} {} due to lack of supplies'.format(
                        pl, unit.tile, msg))

            elif 'unsupplied' in tile and pl in tile.unsupplied:
                tile.unsupplied.remove(pl)
                if len(tile.unsupplied) == 0:
                    del tile.unsupplied
                G.objects.updated[tile._id] = tile
        for unit in u_to_remove:
            remove_unit(G, unit)

    raise PhaseComplete
Exemplo n.º 22
0
def format_out_message(player):

    if player not in WAITING_OBJS:
        return REPEATS[player]

    out = WAITING_OBJS[player]
    del WAITING_OBJS[player]

    if player in WAITING_ACTIONS:
        out.actions = WAITING_ACTIONS[player]
    else:
        out.waiting_for = xset(WAITING_ACTIONS.keys())

    out.log = G.logger.pull(player)

    REPEATS[player] = out
    return out
Exemplo n.º 23
0
def load_unit_rules(G,
                    unit_rules_path='config/units.yml',
                    unit_count_path='config/unit_count.yml'):

    unit_rules = load(unit_rules_path)
    unit_count = load(unit_count_path)

    G.units = adict()

    G.units.rules = unit_rules
    G.units.placeable = xset(name for name, rules in unit_rules.items()
                             if 'not_placeable' not in rules)
    G.units.priorities = [
        n for n, _ in sorted(unit_rules.items(), key=lambda x: x[1].priority)
    ]

    G.units.reserves = unit_count
Exemplo n.º 24
0
def placeable_units(G, player, nationality, tile_options):

    # Groups: in land, no fortress, not supplied,

    reserves = xset(ut for ut in G.units.placeable
                    if ut in G.units.reserves[nationality]
                    and G.units.reserves[nationality][ut] > 0)

    base = adict({
        'unsupplied':
        xset('Fortress'),
        (False, False):
        reserves,
        (True, False):
        xset(ut for ut in reserves if ut != 'Fortress'),
        (False, True):
        xset(ut for ut in reserves
             if G.units.rules[ut].type not in {'N', 'S'}),
        # in_land_no_fort = xset(ut for ut in in_land if ut in no_fortress),
    })
    base[True, True] = base[True, False].intersection(base[False, True])

    # make sure territory is undisputed

    options = adict()

    for tilename in tile_options:
        tile = G.tiles[tilename]

        if 'disputed' in tile:
            continue

        has_fortress = contains_fortress(G, tile)

        in_land = tile.type == 'Land'  # not including coast

        unsupplied = 'unsupplied' in tile and player in tile.unsupplied

        cond = has_fortress, in_land
        if unsupplied:
            cond = 'unsupplied'

        if unsupplied and has_fortress:
            continue

        # add new options based on cond
        if cond not in options:
            options[cond] = xset(), base[cond]
        options[cond][0].add(tilename)

    return xset(options.values())
Exemplo n.º 25
0
def format_out_message(player):
	
	if player not in WAITING_OBJS:
		return REPEATS[player]
	
	out = WAITING_OBJS[player]
	del WAITING_OBJS[player]
	
	if player in WAITING_ACTIONS:
		out.actions = WAITING_ACTIONS[player]
	else: #@playerChange
		out.waiting_for = xset(WAITING_ACTIONS.keys())
	
	out.log = G.logger.pull(player)
	if 'temp' in G:
		out.temp = G.temp #@@@ added this to test extra battle info!!!!!!!!!!
	else:
		out.temp = adict()
	
	REPEATS[player] = out
	return out
Exemplo n.º 26
0
def encode_post_gov_actions(G):

    code = adict()

    for player, is_active in G.temp.move_to_post.items():
        faction = G.players[player]

        handsize = len(faction.hand)
        if not is_active and handsize <= faction.stats.handlimit:
            continue

        options = xset()

        if handsize > faction.stats.handlimit:

            for cid in faction.hand:
                options.add((cid, ))

            # options.update(faction.hand.copy())
            #
            # options = xset((options,))

        if is_active:
            options.add(('accept', ))

            # reveal techs from secret vault
            options.update(check_revealable(G, player))

            # removable nations
            options.add((removable_nations(G, player), ))

        code[player] = options

    for player in G.players:
        if player not in code and player in G.temp.move_to_post:
            del G.temp.move_to_post[player]

    return code
Exemplo n.º 27
0
def encode_setup_actions(G):

    code = adict()

    for faction, nationality, tilenames in seq_iterate(G.temp.setup,
                                                       [None, 'cadres', None],
                                                       end=True):
        # if player is not None and faction != player:
        # 	continue

        options = placeable_units(G, faction, nationality, tilenames)

        if len(options) == 0:
            continue

        if faction not in code:
            code[faction] = xset()

        code[faction].add((nationality, options))

    if len(code) == 0:
        raise PhaseComplete

    return code
Exemplo n.º 28
0
def add_battles_to_reveal(G, player):
	#turn all units to be visible on each tile in G.temp.battles_to_reveal
	#moves revealed battles from battles_to_reveal to battles_to_fight
	#print('battles are revealed...')
	c = G.temp.combat
	for tile in c.battles_to_reveal:
		# atk = c.battles_to_reveal[tile]
		#print('...', tile, type(tile))
		units = G.tiles[tile].units
		c.battles[tile] = adict()
		c.battles_remaining.append(tile)
		b = c.battles[tile]
		b.tilename = tile
		b.tile = G.tiles[tile]
		b.isSeaBattle = b.tile.type in ['Sea', 'Ocean']
		#find owners of units on that tile
		#TODO: replace by powers_present!!!
		owners = [k for k in powers_present(G, b.tile)]

		if len(owners) != 2:
			#TODO implement 3 way battles
			#if this is the defender, needs to choose who to attack
			#for now assume that there must be exactly 2 parties in a combat
			c.ERROR = 'NOT SUPPORTED: COMBAT WITH POWERS != 2 ' + b.tilename
			pass
		
		b.owner = b.tile.owner if 'owner' in tile else None
		uidsList = [k for k in units]
		if not b.owner:
			uid = uidsList[0]
			b.owner = find_unit_owner(G, G.objects.table[uid])

		b.intruder = owners[1] if b.owner == owners[0] else owners[0]
		
		assert G.temp.attacker == player, 'ATTACKER != PLAYER!!!!!!'
		b.attacker = G.temp.attacker  #GANZ SICHER RICHTIG
		b.defender = owners[1] if b.attacker == owners[0] else owners[0]
		b.units = []

		for id in units:
			unit = G.objects.table[id]
			unit.visible = xset(G.players.keys())
			G.objects.updated[id] = unit
			attacker = b.attacker
			defender = b.defender
			uowner = find_unit_owner(G, unit)
			uopponent = defender if uowner == attacker else attacker
			utype = unit.type
			ugroup = G.units.rules[utype].type
			upriority = G.units.rules[utype].priority
			udamage = G.units.rules[utype]
			uid = id

			u_battle_group = None
			if b.isSeaBattle and b.tilename in G.temp.battle_groups and uid in G.temp.battle_groups[b.tilename]:
				u_battle_group = G.temp.battle_groups[b.tilename][uid]
			
			uff = False
			prec_bomb = False
			air_def_radar = False
			sonar = False

			u = adict()
			u.unit = unit
			u.owner = uowner
			u.group = ugroup
			u.priority = upriority
			u.rules = udamage
			u.id = uid
			u.battle_group = u_battle_group
			#modifying cards
			uff = hasFirstFire(G, uowner, utype)

			if uowner in G.players:
				tech = G.players[player].technologies

				#determine IND damage of AirForce:
				if utype == 'AirForce' and 'Jets' in tech:
					prec_bomb = True

				#	air defense radar:
				#Air Forces in Friendly Territory (1.14) Fire doubledice (two dice/CV) at Enemy Air
				if uowner == b.owner and utype == 'AirForce' and 'Air_Defense_Radar' in tech:
					air_def_radar = True

				#sonar
				if utype == 'Fleet' and 'Sonar' in tech:
					sonar = True

			u.ff = uff
			u.sonar = sonar
			u.air_def_radar = air_def_radar
			u.prec_bomb = prec_bomb
			u.type = unit.type

			#if owner of this unit is defender he goes first by default
			#u.turn 0: this unit goes before same type of opponent, otherwise 1
			u.turn = 0
			opp_has_ff = hasFirstFire(G, uopponent, utype)

			uHasFF = False
			if uowner == 'Minor':
				if not opp_has_ff:
					uHasFF = True
			elif uopponent == 'Minor':
				if u.ff:
					uHasFF = True
			elif uowner == attacker and G.players[uowner].stats.DoW[uopponent] and not opp_has_ff:
				uHasFF = True
			elif uowner == attacker and u.ff and not opp_has_ff:
				uHasFF = True
			elif uowner == defender and (u.ff or (not opp_has_ff and not G.players[uopponent].stats.DoW[uowner])):
				uHasFF = True
			u.turn = 0 if uHasFF else 1

			b.units.append(u)

		unitsSorted = sorted(b.units, key=lambda u: u.battle_group if u.battle_group else 'zzz') if b.isSeaBattle else b.units
		b.fire_order = sorted(unitsSorted, key=lambda u: u.priority * 10 + u.turn)
	c.battles_to_reveal.clear()
Exemplo n.º 29
0
def get_enemies(G, player):
    enemies = xset(['Minor', 'Major'])
    wars = G.players[player].stats.at_war_with
    enemies.update([p for p in wars if wars[p]])
    return enemies
Exemplo n.º 30
0
def governmnet_phase(G, player, action):  # play cards

    if action is None:  # prep influence

        G.logger.write('Beginning Government Phase')

        if 'temp' in G:
            del G.temp

        G.temp = tdict()
        G.temp.diplomacy = tdict()
        G.temp.diplomacy_cards = tset()
        G.temp.intel = tdict()
        G.temp.past_upgrades = tdict()
        for player in G.players:
            G.temp.past_upgrades[player] = 0

        G.temp.passes = 0

        G.temp.active_idx = 0
        return encode_government_actions(G)

    if 'move_to_post' in G.temp:  # after phase has ended and only clean up is necessary
        return government_post_phase(G, player, action)

    # TODO: make sure cards that should now be visible stay visible
    if player in G.temp.intel:  # hide any temporarily visible objects from intel cards
        for ID, obj in G.temp.intel[player].items():
            obj.visible.discard(player)
            G.objects.updated[ID] = obj
        del G.temp.intel[player]

    if 'hack' in G.temp:

        if player == G.temp.hack.source:
            raise NotImplementedError
            assert action == (
                'cancel', ), 'Misunderstood action: {}'.format(action)

            G.players[player].hand.add(
                G.temp.hack.card._id
            )  # make sure card is back in sources's ownership
            del G.temp.hack

            return encode_government_actions(G)

        else:
            actions = resolve_intel(G, player, action)

            if actions is not None:
                return actions

    elif 'mole' in G.temp:
        if action != ('accept', ):

            _, tech, _ = action
            G.logger.write('{} uses their mole to achieve {}'.format(
                player, tech),
                           player=G.temp.mole)

            achieve_tech(G, player, *action)

        del G.temp.mole

    elif 'factory_upgrade' in G.temp:

        if action == ('cancel', ):
            G.logger.write('Cancelled factory upgrade', player=player)
            del G.temp.factory_upgrade
            return encode_government_actions(G)

        ID, = action

        val = G.objects.table[ID].value

        if ID in G.temp.factory_upgrade.selects:
            val = -val
            G.temp.factory_upgrade.selects.discard(ID)
            G.logger.write('Unselected {}'.format(ID), end='', player=player)
        else:
            G.temp.factory_upgrade.selects.add(ID)
            G.logger.write('Selected {}'.format(ID), end='', player=player)

        G.temp.factory_upgrade.value += val

        G.logger.write(' (value so far: {}/{})'.format(
            G.temp.factory_upgrade.value,
            G.players[player].stats.factory_cost),
                       player=player)

        # print(G.temp.factory_upgrade.value)

        if G.temp.factory_upgrade.value < G.players[player].stats.factory_cost:
            return encode_factory_upgrade_actions(G)

        # factory upgrade complete
        G.players[player].tracks.IND += 1
        G.temp.past_upgrades[player] += 1

        G.players[player].hand -= G.temp.factory_upgrade.selects

        G.logger.write(
            '{} upgrades their IND to {} with factory card values of: {}'.
            format(
                player, G.players[player].tracks.IND, ', '.join(
                    str(G.objects.table[ID].value)
                    for ID in G.temp.factory_upgrade.selects)))

        discard_cards(G, *G.temp.factory_upgrade.selects)

        del G.temp.factory_upgrade

        G.temp.passes = 0

    elif action == ('pass', ):
        G.logger.write('{} passes'.format(player))
        G.temp.passes += 1
        if G.temp.passes == len(G.players):
            G.logger.write(
                'All players have passed consecutively - moving on to Government resolution'
            )
            G.temp.move_to_post = tdict()  # for handsize limit options
            for name, faction in G.players.items():
                handsize = len(faction.hand)
                diff = handsize - faction.stats.handlimit
                if diff > 0:
                    G.logger.write('{} must discard {} cards'.format(
                        name, diff))
                G.temp.move_to_post[name] = True
            return government_post_phase(G)
    else:

        # execute action
        head, *tail = action

        if head == 'factory_upgrade':
            G.temp.factory_upgrade = tdict()
            G.temp.factory_upgrade.value = 0
            G.temp.factory_upgrade.selects = tset()
            G.logger.write('Select the cards to use for the factory upgrade',
                           player=player)
            return encode_factory_upgrade_actions(G)
        elif head in G.players[
                player].secret_vault:  # reveal tech from secret vault
            reveal_tech(G, player, head)
            return encode_government_actions(G)
        elif head == 'remove':
            nation, = tail
            decrement_influence(G, nation)
            G.logger.write('{} removes one of their influence from {}'.format(
                player, nation))
            return encode_government_actions(G)
        elif head in {'open', 'secret'}:
            G.temp.passes = 0

            achieve_tech(G, player, head, *tail)

        else:
            G.temp.passes = 0
            card = G.objects.table[head]

            if 'wildcard' in card:

                nation, = tail

                increment_influence(G, player, nation)

                extra = ''
                if card.wildcard == 'Foreign_Aid':  # pay IND
                    G.players[player].tracks.IND -= 1
                    extra = ' (decreasing IND to {})'.format(
                        G.players[player].tracks.IND)

                G.logger.write(
                    '{} plays {} adding/removing influence in {}{}'.format(
                        player, card.wildcard, nation, extra))

                discard_cards(G, head)

            elif 'intelligence' in card:

                discard_cards(G, head)

                return play_intel(G, player, card, *tail)

            else:
                nation, = tail

                play_diplomacy(G, player, nation)

                G.temp.diplomacy_cards.add(head)
                card.visible = xset(G.players.keys())  # visible to everyone
                G.players[card.owner].hand.discard(card._id)
                del card.owner
                G.objects.updated[head] = card
                # discard_cards(G, head)

    G.temp.active_idx += 1
    G.temp.active_idx %= len(G.players)
    return encode_government_actions(G)