package main import ( "fmt" ) func PlayGame(numPlayers int) { currentPlayer := 0 for round := 3; round < 14; round++ { d := Deck() Shuffle(d) discard := []Card{} fmt.Printf("# Round %d\n\n", round) // Deal starting cards hands := make([][]Card, numPlayers) for p := 0; p < numPlayers; p++ { hands[p] = d[0:round] // Deal from the bottom (0) d = d[round:] fmt.Printf("P%d starting hand: %v\n", p, hands[p]) } // Deal one into the discard pile discard = append(discard, d[0]) d = d[1:] fmt.Printf("Discard stack: %v\n", discard) fmt.Printf("Deck has %d cards remaining\n", len(d)) roundEndsWhenPlayerIs := -1 for { if roundEndsWhenPlayerIs == currentPlayer { break } // Score our hand as if we pick up the discard and discard it immediately _, score := FindBestGrouping(hands[currentPlayer], round) baseScore := score bestDiscardBasedIdx := -1 bestDiscardBasedScore := score // Score our hand as if we pick up the discard card and discard each other card for cidx := 0; cidx < round; cidx++ { // Pick up the last discard card, and discard card@cidx theoretically := forkHand(hands[currentPlayer]) theoretically[cidx] = discard[len(discard)-1] _, score := FindBestGrouping(theoretically, round) if score < bestDiscardBasedScore { // TODO if the points are equal or near-equal, prefer not to discard a wild for the next player bestDiscardBasedIdx = cidx bestDiscardBasedScore = score } } // If taking the discard would let us win, or reduce our score by >5, take it if bestDiscardBasedScore == 0 || (baseScore-bestDiscardBasedScore) > 5 { fmt.Printf("P%d taking %v from the discard pile\n", currentPlayer, discard[len(discard)-1]) // We're going to win if bestDiscardBasedIdx == -1 { // Take the discard card + put it back again i.e. no change } else { // Take the discard card and discard cidx newCard := discard[len(discard)-1] discard = discard[0 : len(discard)-1] unwantedCard := hands[currentPlayer][bestDiscardBasedIdx] hands[currentPlayer][bestDiscardBasedIdx] = newCard discard = append(discard, unwantedCard) // On top = available for next player fmt.Printf("P%d discards %v\n", currentPlayer, unwantedCard) } } else { // The discard card is not going to make us win or significantly lower our // score // Take from the deck newCard := d[0] d = d[1:] hands[currentPlayer] = append(hands[currentPlayer], newCard) fmt.Printf("P%d taking %v from the deck\n", currentPlayer, newCard) bestRandomBasedIdx := -1 bestRandomBasedScore := (round * 50) + 51 // Worse than the worst possible score // Look at what we can now drop (hand size + 1 new card). We have to drop something for cidx := 0; cidx < round+1; cidx++ { // Assume we discard that card, theoretically := forkHand(hands[currentPlayer]) theoretically[cidx] = Card(-1) // Never matches + worth 0 points _, score := FindBestGrouping(theoretically, round) if score < bestRandomBasedScore { // TODO if the points are equal or near-equal, prefer not to discard a wild for the next player bestRandomBasedIdx = cidx bestRandomBasedScore = score } } // Discard our chosen card // Slice tricks: Copy end card into this slot and reslice bounds unwantedCard := hands[currentPlayer][bestRandomBasedIdx] fmt.Printf("P%d discards %v\n", currentPlayer, unwantedCard) hands[currentPlayer][bestRandomBasedIdx] = hands[currentPlayer][len(hands[currentPlayer])-1] hands[currentPlayer] = hands[currentPlayer][0 : len(hands[currentPlayer])-1] discard = append(discard, unwantedCard) // On top = available for next player } // Check, one more time, if we have a winning hand currentBestGrouping, currentScore := FindBestGrouping(hands[currentPlayer], round) if currentScore == 0 { // Declare victory fmt.Printf("P%d declares victory\n- Hand: %v\n- Groupings: %v\n", currentPlayer, hands[currentPlayer], currentBestGrouping) roundEndsWhenPlayerIs = currentPlayer } // Move to the next player currentPlayer = (currentPlayer + 1) % numPlayers } // The round has ended // Figure out what each player scored return } }