def _is_card_between_values(card, center_card, pay_off): " returns 1 if cards value is between center_card and pay_off card, 0 otherwise " # get the value of the card if we have a string of the card if type(card) in [str, unicode]: card = Card.to_numeric_value(card) if type(center_card) in [str, unicode]: center_card = Card.to_numeric_value(center_card) if type(pay_off) in [str, unicode]: pay_off = Card.to_numeric_value(pay_off) # if center is above pay_off, move center if center_card > pay_off: center_card -= 11 return (center_card < card < pay_off)
def _distance_between_values(pile_card, play_card): " find the distance between the pile card, and the play card values" # make sure we have numeric values if type(pile_card) in [str, unicode]: pile_card = Card.to_numeric_value(pile_card) if type(play_card) in [str, unicode]: play_card = Card.to_numeric_value(play_card) if pile_card == None: return play_card if play_card > pile_card: return play_card - pile_card if play_card == pile_card: return 11 return 11 - pile_card + play_card
def _successors(self, node): """ Return a list of successor nodes for the current node. Each node is a valid move. """ log.debug("Generating successors for %s" % node) node_list = [] # opponent plays card on center if node.player == StateNode.OTHER or (node.action and node.action.to_pile == DISCARD): swap_player = (node.player == StateNode.SELF) for pile_name, pile_len in [(PAY_OFF,1), (DISCARD,4)]: for pile_id in range(pile_len): for action in self._get_center_move_from(node.state, pile_name, pile_id, swap_player): new_state = ComputerPlayer._new_state_from_action(node.state, action, swap_player) new_node = StateNode(new_state, action, node, player=StateNode.OTHER) node_list.append(new_node) return node_list # moves to center for pile_name, pile_len in [(HAND,1), (PAY_OFF,1), (DISCARD,4)]: for pile_id in range(pile_len): for action in self._get_center_move_from(node.state, pile_name, pile_id): new_state = ComputerPlayer._new_state_from_action(node.state, action) new_node = StateNode(new_state, action, node) node_list.append(new_node) # moves to discard for card in node.state.get_player()[HAND]: # can't discard kings if Card.to_numeric_value(card) == 13: continue # only create moves for different discard pile states discard_pile_values = [] discard_pile_ids = [] for i in range(len(node.state.get_player()[DISCARD])): value = None if len(node.state.get_player()[DISCARD][i]): value = Card.to_numeric_value(node.state.get_player()[DISCARD][i][-1]) if value not in discard_pile_values: discard_pile_values.append(value) discard_pile_ids.append(i) # find the moves for pile_id in discard_pile_ids: action = PlayerMove(card, from_pile=HAND, to_pile=DISCARD, to_id=pile_id) new_state = ComputerPlayer._new_state_from_action(node.state, action) new_node = StateNode(new_state, action, node) node_list.append(new_node) return node_list
def place_card(self, player_move): """ Place a card onto a stack. Inputs are: card is the card to be placed from_location is a tuple of the pile (HAND, DISCARD or PAY_OFF) and the id of the pile to_location is a tuple of the pile (DISCARD or CENTER), and the id of the pile Returns the id of the player who gets to play next. """ card = player_move.card if not Card.is_valid(card): raise InvalidMove("Unknown card %s." % (card)) if player_move.from_pile not in (HAND, DISCARD, PAY_OFF): raise InvalidMove("from_location incorrect: %s " % (player_move.from_pile)) if player_move.to_pile not in (DISCARD, CENTER) or player_move.to_id < 0 \ or player_move.to_id >= self.NUM_STACKS: raise InvalidMove("to_location incorrect: %s." % (player_move.to_pile)) if player_move.to_pile == CENTER and \ not self.can_place_card_in_center(self.center_stacks[player_move.to_id], card): raise InvalidMove("Can not place card(%s) on center stack %s." % (card, player_move.to_id)) # can not discard kings if card[0] == 'K' and player_move.to_pile == DISCARD: raise InvalidMove("Can not DISCARD card(%s)" % (card)) # can not move from discard to discard if player_move.to_pile == player_move.from_pile: raise InvalidMove("Can not move to same pile %s" % player_move.to_pile) # remove it from old location player = self.players[self.active_player] if player_move.from_pile == HAND and card in player[HAND]: player[HAND].remove(card) elif player_move.from_pile == DISCARD and card in player[DISCARD][player_move.from_id]: player[DISCARD][player_move.from_id].remove(card) elif player_move.from_pile == PAY_OFF and card == player[PAY_OFF][-1]: if player_move.to_pile != CENTER: raise InvalidMove("Can not move PAY_OFF to %s" % player_move.to_pile) else: player[PAY_OFF].pop() else: raise InvalidMove("Could not find card(%s) in %s." % (card, player_move.from_pile)) # place it in new location if player_move.to_pile == CENTER: self.center_stacks[player_move.to_id].append(card) elif player_move.to_pile == DISCARD: player[DISCARD][player_move.to_id].append(card)
def can_place_card_in_center(cls, pile, card): """ Determins if card can be played on the center stack pile. Returns true if it can be placed, false otherwise. """ value = card[0] # king can be placed on anything if value == 'K': return True # ace can be played on empty piles if value == 'A': return (len(pile) == 0) # convert letters into numeric values value = Card.to_numeric_value(card) top_value = len(pile) return (value - top_value == 1)
def _utility(self, node): """ Calculate the utility value for this state node. """ ALL = 'all' # FIXME: Balance points so that placing on center only when necesarry (discard full, or no closer to po for op) points = { # to discard pile DISCARD: (10, { 'on_same': 50, # Discard on same value card 'on_empty': 30, # Discard on empty pile 'common_in_hand': 10, # Each time the discard card occures in the hard 'least_essential': 5, # Discard least essential card 'bury_least': 2, # Discard buries the least essential card }), # to center CENTER: (0, { 'pay_off': 1000, # Play the pay off card }), # from hand HAND: (0, { 'empty_hand': 120, # Empty hand without a discard }), # All moves ALL: { 'op_dist_po': 30, # Each point away the closest center is from opponents pay off (max *12) }, # Opponent play StateNode.OTHER: (0, { 'from_discard': -10, # Opponent plays from discard 'from_pay_off': -1000, # Opponent plays pay_off card }) } # shortcut vars center_values = [] for pile in node.state.center_stacks: center_values.append(len(pile)) if node.player == StateNode.SELF: myself = node.state.get_player() other = node.state.get_player(True) else: myself = node.state.get_player(True) other = node.state.get_player() value = 0 # each point away the closest center is from opponents pay off if len(other[PAY_OFF]): value += points[ALL]['op_dist_po'] * \ self._find_min_center_distance(center_values, other[PAY_OFF][-1]) if node.player == StateNode.SELF: if node.action.to_pile == DISCARD: value += points[DISCARD][0] # discard on empty pile if len(myself[DISCARD][node.action.to_id]) == 1: value += points[DISCARD][1]['on_empty'] # discard on same value card if len(myself[DISCARD][node.action.to_id]) > 1 \ and Card.to_numeric_value(myself[DISCARD][node.action.to_id][-1]) == \ Card.to_numeric_value(myself[DISCARD][node.action.to_id][-2]): value += points[DISCARD][1]['on_same'] # each time the discard cards value ocurs in the hand value += points[DISCARD][1]['common_in_hand'] * \ map(lambda c: Card.to_numeric_value(c), myself[HAND]).count( Card.to_numeric_value(node.action.card)) # discard least essential card if 0 == self._find_least_essential_card( center_values, [node.action.card] + myself[HAND], myself[PAY_OFF][-1]): value += points[DISCARD][1]['least_essential'] # discard buries least essential card if len(myself[DISCARD][node.action.to_id]) >= 1: discard_piles = self._build_pre_play_discard_piles(node) if node.action.to_id == self._find_least_essential_card(center_values, discard_piles, myself[PAY_OFF][-1]): value += points[DISCARD][1]['bury_least'] elif node.action.to_pile == CENTER: value += points[CENTER][0] # pay off played if node.action.from_pile == PAY_OFF: value += points[CENTER][1]['pay_off'] if node.action.from_pile == HAND: value += points[HAND][0] # empty hand without a discard if len(myself[HAND]) == 0 and node.action.to_pile != DISCARD: value += points[HAND][1]['empty_hand'] # opponents plays else: value += points[StateNode.OTHER][0] if node.action.from_pile == PAY_OFF: value += points[StateNode.OTHER][1]['from_pay_off'] if node.action.from_pile == DISCARD: value += points[StateNode.OTHER][1]['from_discard'] # cumulative utils log.debug("Util %d " % (value)) return value + node.parent_node.util_value
def run(self): " Lets go. " # find out which player goes first if Card.to_numeric_value(self.model.players[0][PAY_OFF][-1]) > Card.to_numeric_value( self.model.players[1][PAY_OFF][-1] ): self.model.active_player = 0 else: self.model.active_player = 1 prev_active = None while True: active_player = self.model.active_player other_player = int(not self.model.active_player) # special case for both human players, blank the screen if it's a new players turn if ( type(self.players[active_player]) == type(self.players[other_player]) == HumanPlayer and prev_active != active_player ): self.view.wait_screen() prev_active = active_player # draw the board for the human player(s) if type(self.players[active_player]) == HumanPlayer: self.view.draw_board(active_player) elif type(self.players[other_player]) == HumanPlayer: self.view.draw_board(other_player) # get the next move if type(self.players[active_player]) == HumanPlayer: player_move = self.players[active_player].play_card(self.view.select_group, self.view.target_group) else: game_state = self.model.build_view_for_player() player_move = self.players[active_player].play_card(game_state) if player_move == None: log.warn("Got None move. Ending") return # play the move try: self.model.place_card(player_move) except InvalidMove, inv: self.view.show_error(inv) continue # check for win if self.model.is_won(): self.view.game_over() return # mix completed stacks back in self.model.mix_into_stock() # fill player hand if empty, and not a discard hand = self.model.players[active_player][HAND] if player_move.to_pile != DISCARD and len(hand) == 0: self.model.fill_hand() # swap players if move was a discard if player_move.to_pile == DISCARD: # tell view and model to end the round self.model.swap_players() self.model.fill_hand()