def getDiscard(self) -> CardObj: # Find deadwood of hand w/o each card. deadwoodArr = np.zeros(len(self.cards)) for i in range(len(self.cards)): # Cannot draw and discard face up card. if self.cards[i] == self.drawnCard and self.drawnCard == self.faceUpCard: continue remainingCards = list(self.cards) remainingCards.remove(self.cards[i]) bestMeldSets = GinRummyUtil.cardsToBestMeldSets(remainingCards) deadwood = GinRummyUtil.getDeadwoodPoints3(remainingCards) if len(bestMeldSets) == 0 \ else GinRummyUtil.getDeadwoodPoints1(bestMeldSets[0], remainingCards) deadwoodArr[i] = deadwood # Find available melds for each card. meldsArr = np.zeros(len(self.cards)) # parallel # Look at cards pairwise for availability of melds. for i in range(len(self.cards)): for j in range(i + 1, len(self.cards)): card1 = self.cards[i] card2 = self.cards[j] ways = self._waysCompleteMeld(card1, card2) meldsArr[i] += ways meldsArr[j] += ways linComb = meldsArr * self.alpha + deadwoodArr * self.beta minArg = np.argmin(linComb) if self.cards[minArg] == self.drawnCard and self.drawnCard == self.faceUpCard: return self.cards[np.argsort(linComb)[1]] return self.cards[minArg]
def getDiscard(self) -> Card: # Discard a random card (not just drawn face up) leaving minimal deadwood points. minDeadwood = float('inf') candidateCards = [] for card in self.cards: # Cannot draw and discard face up card. if card == self.drawnCard and self.drawnCard == self.faceUpCard: continue # Disallow repeat of draw and discard. drawDiscard = [self.drawnCard, card] if GinRummyUtil.cardsToBitstring( drawDiscard) in self.drawDiscardBitstrings: continue remainingCards = list(self.cards) remainingCards.remove(card) bestMeldSets = GinRummyUtil.cardsToBestMeldSets(remainingCards) deadwood = GinRummyUtil.getDeadwoodPoints3(remainingCards) if len(bestMeldSets) == 0 \ else GinRummyUtil.getDeadwoodPoints1(bestMeldSets[0], remainingCards) if deadwood <= minDeadwood: if deadwood < minDeadwood: minDeadwood = deadwood candidateCards.clear() candidateCards.append(card) # Prevent future repeat of draw, discard pair. discard = candidateCards[randint(0, len(candidateCards) - 1)] drawDiscard = [self.drawnCard, discard] self.drawDiscardBitstrings.append( GinRummyUtil.cardsToBitstring(drawDiscard)) return discard
def getLinComb(self, cards, alpha): # Find deadwood of hand w/o each card. deadwoodArr = np.zeros(len(cards)) for i in range(len(cards)): # Cannot draw and discard face up card. if cards[i] == self.drawnCard and self.drawnCard == self.faceUpCard: continue remainingCards = list(cards) remainingCards.remove(cards[i]) bestMeldSets = GinRummyUtil.cardsToBestMeldSets(remainingCards) deadwood = GinRummyUtil.getDeadwoodPoints3(remainingCards) if len(bestMeldSets) == 0 \ else GinRummyUtil.getDeadwoodPoints1(bestMeldSets[0], remainingCards) deadwoodArr[i] = deadwood # Find available melds for each card. meldsArr = np.zeros(len(cards)) # parallel probs = self._predictOpponentHand() # Look at cards pairwise for availability of melds. for i in range(len(cards)): for j in range(i + 1, len(cards)): card1 = cards[i] card2 = cards[j] ways = self._waysCompleteMeld(card1, card2, probs) meldsArr[i] += ways meldsArr[j] += ways return meldsArr * alpha + deadwoodArr * (1-alpha)
def getFinalMelds(self) -> List[List[CardObj]]: # Check if deadwood of maximal meld is low enough to go out. bestMeldSets = GinRummyUtil.cardsToBestMeldSets(self.cards) # List[List[List[Card]]] if not self.opponentKnocked and (len(bestMeldSets) == 0 or \ GinRummyUtil.getDeadwoodPoints1(bestMeldSets[0], self.cards) > \ GinRummyUtil.MAX_DEADWOOD): return None if len(bestMeldSets) == 0: return [] return bestMeldSets[randint(0, len(bestMeldSets)-1)]
def willDrawFaceUpCard(self, card: Card) -> bool: # Return true if card would be a part of a meld, false otherwise. self.faceUpCard = card newCards = list(self.cards) newCards.append(card) for meld in GinRummyUtil.cardsToAllMelds(newCards): if card in meld: return True return False
def getDiscard(self) -> CardObj: # Discard a random card (not just drawn face up) leaving minimal deadwood points. minArg = -1 minScore = 10**5 candidateCards = np.zeros(len(self.cards)) # parallel # Look at cards pairwise for availability of melds. for i in range(len(self.cards)): for j in range(i + 1, len(self.cards)): card1 = self.cards[i] card2 = self.cards[j] ways = self._waysCompleteMeld(card1, card2) candidateCards[i] += ways candidateCards[j] += ways # Reward cards on belongingness to melds. bestMeldSets = GinRummyUtil.cardsToBestMeldSets(self.cards) if len(bestMeldSets) == 0: minArg = np.argmin(candidateCards) if self.cards[minArg] == self.drawnCard and self.drawnCard == self.faceUpCard: return self.cards[np.argsort(candidateCards)[1]] return self.cards[minArg] for bestMeldSet in bestMeldSets: candidateCardsTemp = np.copy(candidateCards) for meldSet in bestMeldSet: for k in range(len(self.cards)): if self.cards[k] == self.drawnCard and self.drawnCard == self.faceUpCard: candidateCardsTemp[k] += 1000 elif self.cards[k] in meldSet: candidateCardsTemp[k] += 10 currMin = np.amin(candidateCardsTemp) if currMin < minScore: minScore = currMin minArg = np.argmin(candidateCardsTemp) if self.cards[minArg] == self.drawnCard and self.drawnCard == self.faceUpCard: return self.cards[np.argsort(candidateCardsTemp)[1]] return self.cards[minArg]
def getDiscard(self) -> CardObj: # Discard a random card (not just drawn face up) leaving minimal deadwood points. minDeadwood = float('inf') candidateCards = np.copy(self.ownCards) candidateCards *= 1000 if self.drawnCard == self.faceUpCard: candidateCards[self.drawnCard.getId()] = 0 bestMeldSets = GinRummyUtil.cardsToBestMeldSets(self.cards) if len(bestMeldSets) == 0: candidateCards = candidateCards / np.sum(candidateCards) discard = np.random.choice(52, 1, p=candidateCards)[0] for card in self.cards: if discard == card.getId(): return card # For each best meld set. Find the cards that do not belong to melds. for bestMeldSet in bestMeldSets: remainingCards = list(self.cards) for meldSet in bestMeldSet: for card in meldSet: candidateCards[card.getId()] -= 5 # reward card for being in meld remainingCards.remove(card) # Now, look at remaining cards pairwise. for i in range(len(remainingCards)): for j in range(i + 1, len(remainingCards)): card1 = remainingCards[i] card2 = remainingCards[j] ways = self._waysCompleteMeld(card1, card2) candidateCards[[card1.getId(), card2.getId()]] -= ways discard = np.argmax(candidateCards) for card in self.cards: if discard == card.getId(): return card
def play(self): scores = [0, 0] hands = [] hands.extend([[], []]) startingPlayer = random.randrange(2) # while game not over while scores[0] < GinRummyUtil.GOAL_SCORE and scores[ 1] < GinRummyUtil.GOAL_SCORE: currentPlayer = startingPlayer opponent = (1 if currentPlayer == 0 else 0) # get shuffled deck and deal cards deck = Deck.getShuffle(random.randrange(10**8)) hands[0] = [] hands[1] = [] for i in range(2 * GinRummyGame.HAND_SIZE): hands[i % 2] += [deck.pop()] for i in range(2): GinRummyGame.players[i].startGame(i, startingPlayer, hands[i]) if GinRummyGame.playVerbose: print("Player %d is dealt %s.\n" % (i, hands[i])) if GinRummyGame.playVerbose: print("Player %d starts.\n" % (startingPlayer)) discards = [] discards.append(deck.pop()) if GinRummyGame.playVerbose: print("The initial face up card is %s.\n" % (discards[len(discards) - 1])) firstFaceUpCard = discards[len(discards) - 1] turnsTaken = 0 knockMelds = None # tracking_pastdiscards = {0: np.zeros(52), 1: np.zeros(52)} # tracking_pastpickups = {0: np.zeros(52), 1: np.zeros(52)} # tracking_pastnonpickups = {0: np.zeros(52), 1: np.zeros(52)} # while the deck has more than two cards remaining, play round while len(deck) > 2: # DRAW drawFaceUp = False faceUpCard = discards[len(discards) - 1] # offer draw face-up iff not 3rd turn with first face up card (decline automatically in that case) if not (turnsTaken == 2 and faceUpCard == firstFaceUpCard): # both players declined and 1st player must draw face down drawFaceUp = GinRummyGame.players[ currentPlayer].willDrawFaceUpCard(faceUpCard) if GinRummyGame.playVerbose and not drawFaceUp and faceUpCard == firstFaceUpCard and turnsTaken < 2: print("Player %d declines %s.\n" % (currentPlayer, firstFaceUpCard)) if not (not drawFaceUp and turnsTaken < 2 and faceUpCard == firstFaceUpCard): # continue with turn if not initial declined option drawCard = discards.pop() if drawFaceUp else deck.pop() for i in range(2): to_report = drawCard if i == currentPlayer or drawFaceUp else None GinRummyGame.players[i].reportDraw( currentPlayer, to_report) # TRACKING # if i != currentPlayer: # player i is tracking currentPlayer # if drawFaceUp: # tracking_pastpickups[i][faceUpCard.getId()] = 1 # else: # tracking_pastnonpickups[i][faceUpCard.getId()] = 1 if GinRummyGame.playVerbose: print("Player %d draws %s.\n" % (currentPlayer, drawCard)) hands[currentPlayer].append(drawCard) # DISCARD discardCard = GinRummyGame.players[ currentPlayer].getDiscard() if not discardCard in hands[ currentPlayer] or discardCard == faceUpCard: print( "Player %d discards %s illegally and forfeits.\n" % (currentPlayer, discardCard)) return opponent hands[currentPlayer].remove(discardCard) for i in range(2): GinRummyGame.players[i].reportDiscard( currentPlayer, discardCard) # TRACKING # if i != currentPlayer: # player i is tracking currentPlayer # tracking_pastdiscards[i][discardCard.getId()] = 1 # tracking_pastpickups[i][discardCard.getId()] = 0 # tracking_hand = one_hot(GinRummyGame.players[currentPlayer].cards) # tracking_hand[discardCard.getId()] = 0 # tracking_hands.append(tracking_hand) # tracking_states.append(np.array([tracking_pastdiscards[i], tracking_pastpickups[i], tracking_pastnonpickups[i]])) # tracking_states2.append(np.array([tracking_pastdiscards[i], tracking_pastpickups[i], tracking_pastnonpickups[i], one_hot(GinRummyGame.players[i].cards)])) if GinRummyGame.playVerbose: print("Player %d discards %s.\n" % (currentPlayer, discardCard)) discards.append(discardCard) if GinRummyGame.playVerbose: unmeldedCards = hands[currentPlayer].copy() bestMelds = GinRummyUtil.cardsToBestMeldSets( unmeldedCards) if len(bestMelds) == 0: print( "Player %d has %s with %d deadwood.\n" % (currentPlayer, unmeldedCards, GinRummyUtil.getDeadwoodPoints3(unmeldedCards) )) else: melds = bestMelds[0] for meld in melds: for card in meld: unmeldedCards.remove(card) melds.extend(unmeldedCards) print( "Player %d has %s with %d deadwood.\n" % (currentPlayer, melds, GinRummyUtil.getDeadwoodPoints3(unmeldedCards) )) # CHECK FOR KNOCK knockMelds = GinRummyGame.players[ currentPlayer].getFinalMelds() if knockMelds != None: # player knocked; end of round break turnsTaken += 1 currentPlayer = 1 if currentPlayer == 0 else 0 opponent = 1 if currentPlayer == 0 else 0 if knockMelds != None: # round didn't end due to non-knocking and 2 cards remaining in draw pile # check legality of knocking meld handBitstring = GinRummyUtil.cardsToBitstring( hands[currentPlayer]) unmelded = handBitstring for meld in knockMelds: meldBitstring = GinRummyUtil.cardsToBitstring(meld) if (not meldBitstring in GinRummyUtil.getAllMeldBitstrings()) or ( (meldBitstring & unmelded) != meldBitstring): # non-meld or meld not in hand print("Player %d melds %s illegally and forfeits.\n" % (currentPlayer, knockMelds)) return opponent unmelded &= ~meldBitstring # remove successfully melded cards from # compute knocking deadwood knockingDeadwood = GinRummyUtil.getDeadwoodPoints1( knockMelds, hands[currentPlayer]) if knockingDeadwood > GinRummyUtil.MAX_DEADWOOD: print( "Player %d melds %s with greater than %d deadwood and forfeits.\n" % (currentPlayer, knockMelds, knockingDeadwood)) return opponent meldsCopy = [] for meld in knockMelds: meldsCopy.append(meld.copy()) for i in range(2): GinRummyGame.players[i].reportFinalMelds( currentPlayer, meldsCopy) if GinRummyGame.playVerbose: if knockingDeadwood > 0: print( "Player %d melds %s with %d deadwood from %s.\n" % (currentPlayer, knockMelds, knockingDeadwood, GinRummyUtil.bitstringToCards(unmelded))) else: print("Player %d goes gin with melds %s.\n" % (currentPlayer, knockMelds)) # get opponent meld opponentMelds = GinRummyGame.players[opponent].getFinalMelds() meldsCopy = [] for meld in opponentMelds: meldsCopy.append(meld.copy()) for i in range(2): GinRummyGame.players[i].reportFinalMelds( opponent, meldsCopy) # check legality of opponent meld opponentHandBitstring = GinRummyUtil.cardsToBitstring( hands[opponent]) opponentUnmelded = opponentHandBitstring for meld in opponentMelds: meldBitstring = GinRummyUtil.cardsToBitstring(meld) if (meldBitstring not in GinRummyUtil.getAllMeldBitstrings()) or ( (meldBitstring & opponentUnmelded) != meldBitstring): # non-meld or meld not in hand print("Player %d melds %s illegally and forfeits.\n" % (opponent, opponentMelds)) return currentPlayer opponentUnmelded &= ~meldBitstring # remove successfully melded cards from if GinRummyGame.playVerbose: print("Player %d melds %s.\n" % (opponent, opponentMelds)) # lay off on knocking meld (if not gin) unmeldedCards = GinRummyUtil.bitstringToCards(opponentUnmelded) if knockingDeadwood > 0: # knocking player didn't go gin cardWasLaidOff = False while True: # attempt to lay each card off cardWasLaidOff = False layOffCard = None layOffMeld = None for card in unmeldedCards: for meld in knockMelds: newMeld = meld.copy() newMeld.append(card) newMeldBitstring = GinRummyUtil.cardsToBitstring( newMeld) if newMeldBitstring in GinRummyUtil.getAllMeldBitstrings( ): layOffCard = card layOffMeld = meld break if layOffCard != None: if GinRummyGame.playVerbose: print("Player %d lays off %s on %s.\n" % (opponent, layOffCard, layOffMeld)) for i in range(2): GinRummyGame.players[i].reportLayoff( opponent, layOffCard, layOffMeld.copy()) unmeldedCards.remove(layOffCard) layOffMeld.append(layOffCard) cardWasLaidOff = True break if not cardWasLaidOff: break opponentDeadwood = 0 for card in unmeldedCards: opponentDeadwood += GinRummyUtil.getDeadwoodPoints2(card) if GinRummyGame.playVerbose: print("Player %d has %d deadwood with %s\n" % (opponent, opponentDeadwood, unmeldedCards)) # compare deadwood and compute new scores if knockingDeadwood == 0: # gin round win scores[ currentPlayer] += GinRummyUtil.GIN_BONUS + opponentDeadwood if GinRummyGame.playVerbose: print("Player %d scores the gin bonus of %d plus opponent deadwood %d for %d total points.\n" % \ (currentPlayer, GinRummyUtil.GIN_BONUS, opponentDeadwood, GinRummyUtil.GIN_BONUS + opponentDeadwood)) elif knockingDeadwood < opponentDeadwood: # non-gin round win: scores[ currentPlayer] += opponentDeadwood - knockingDeadwood if GinRummyGame.playVerbose: print( "Player %d scores the deadwood difference of %d.\n" % (currentPlayer, opponentDeadwood - knockingDeadwood)) else: # undercut win for opponent scores[ opponent] += GinRummyUtil.UNDERCUT_BONUS + knockingDeadwood - opponentDeadwood if GinRummyGame.playVerbose: print("Player %d undercuts and scores the undercut bonus of %d plus deadwood difference of %d for %d total points.\n" % \ (opponent, GinRummyUtil.UNDERCUT_BONUS, knockingDeadwood - opponentDeadwood, GinRummyUtil.UNDERCUT_BONUS + knockingDeadwood - opponentDeadwood)) startingPlayer = 1 if startingPlayer == 0 else 0 # starting player alternates # If the round ends due to a two card draw pile with no knocking, the round is cancelled. else: if GinRummyGame.playVerbose: print( "The draw pile was reduced to two cards without knocking, so the hand is cancelled." ) # report final hands for i in range(2): for j in range(2): GinRummyGame.players[i].reportFinalHand(j, hands[j].copy()) # score reporting if GinRummyGame.playVerbose: print("Player\tScore\n0\t%d\n1\t%d\n" % (scores[0], scores[1])) for i in range(2): GinRummyGame.players[i].reportScores(scores.copy()) if GinRummyGame.playVerbose: print("Player %s wins.\n" % (0 if scores[0] > scores[1] else 1)) return 0 if scores[0] >= GinRummyUtil.GOAL_SCORE else 1