package deck import ( "fmt" "math/rand" "sort" ) //go:generate stringer -type=Suit,Rank // Suit represents the suit of a card: // one of Spades, Diamonds, Clubs, Hearts, // or Joker type Suit uint8 const ( Spades Suit = iota Diamonds Clubs Hearts Joker ) // Rank represents the rank of a card: // Ace, Two... Ten, Jack... King type Rank uint8 const ( _ Rank = iota Ace Two Three Four Five Six Seven Eight Nine Ten Jack Queen King ) // Card contains both a Suit and a Rank. // In the special case that Suit=Joker, // Rank is often not relevant. type Card struct { Suit Suit Rank Rank } // Formats the Card type as a string func (c Card) String() string { if c.Suit == Joker { return "Joker" } return fmt.Sprintf("%s of %s", c.Rank, c.Suit) } // Generates a new deck (slice of Cards) which contains // a full playing card deck sorted by Suit with // no jokers. // The function accepts functional arguments to // modify the deck during creation func New(opts ...func([]Card) []Card) []Card { deck := make([]Card, 52, 52) i := 0 for s := 0; s < 4; s++ { for v := 1; v < 14; v++ { deck[i] = Card{Suit: Suit(s), Rank: Rank(v)} i++ } } for _, opt := range opts { deck = opt(deck) } return deck } func lessBySuit(c1 Card, c2 Card) bool { if c1.Suit < c2.Suit { return true } if c1.Suit == c2.Suit && c1.Rank < c2.Rank { return true } return false } func lessByRank(c1 Card, c2 Card) bool { if c1.Rank < c2.Rank { return true } if c1.Rank == c2.Rank && c1.Suit < c2.Suit { return true } return false } // Sorts the deck (slice of Cards) by Suit, then by Rank func SortBySuit(deck []Card) []Card { sort.Slice(deck, func(i, j int) bool { return lessBySuit(deck[i], deck[j]) }) return deck } // Sorts the deck (slice of Cards) by Rank, then by Suit func SortByRank(deck []Card) []Card { sort.Slice(deck, func(i, j int) bool { return lessByRank(deck[i], deck[j]) }) return deck } // This returns a closure that adds n joker cards to the end // of a deck (slice of Cards). The returned function // can be passed as an option into New() func AddNJokers(n int) func(deck []Card) []Card { return func(deck []Card) []Card { toAdd := make([]Card, n, n) for i, _ := range toAdd { toAdd[i] = Card{Suit: Joker} } return append(deck, toAdd...) } } // Shuffle randomizes the order of cards in a slice func Shuffle(deck []Card) []Card { rand.Shuffle(len(deck), func(i, j int) { deck[i], deck[j] = deck[j], deck[i] }) return deck } // RemoveCards takes Card arguments, and returns a closure // that will remove all Cards matching one of the arguments // from the deck (slice of Cards). // The returned function can be passed as an option into New() func RemoveCards(cards ...Card) func([]Card) []Card { return func(deck []Card) []Card { output := make([]Card, 0, len(deck)) badKeys := make(map[Card]struct{}) for _, card := range cards { badKeys[card] = struct{}{} } for _, card := range deck { if _, ok := badKeys[card]; !ok { output = append(output, card) } } return output } } // RemoveSuit takes Suit arguments, and returns a closure // that will remove all Cards with a Suit equal to one of the // arguments from the deck (slice of Cards). // The returned function can be passed as an option into New() func RemoveSuit(suits ...Suit) func([]Card) []Card { return func(deck []Card) []Card { output := make([]Card, 0, len(deck)) badKeys := make(map[Suit]struct{}) for _, suit := range suits { badKeys[suit] = struct{}{} } for _, card := range deck { if _, ok := badKeys[card.Suit]; !ok { output = append(output, card) } } return output } } // RemoveRank takes Rank arguments, and returns a closure // that will remove all Cards with a Rank equal to one of the // arguments from the deck (slice of Cards). // The returned function can be passed as an option into New() func RemoveRank(ranks ...Rank) func([]Card) []Card { return func(deck []Card) []Card { output := make([]Card, 0, len(deck)) badKeys := make(map[Rank]struct{}) for _, rank := range ranks { badKeys[rank] = struct{}{} } for _, card := range deck { if _, ok := badKeys[card.Rank]; !ok { output = append(output, card) } } return output } } // AddDeck appends a default deck to the deck passed in by the user // (slice ef Cards), where the default deck is that created by New() // with no options. func AddDeck(deck []Card) []Card { newDeck := New() deck = append(deck, newDeck...) return deck } // Draw takes the top card from deck and appends it to hand, returning the // hand and the deck as they are after the card is moved. func Draw(hand []Card, deck []Card) (newHand []Card, newDeck []Card) { topCard := deck[0] newHand = append(hand, topCard) newDeck = deck[1:] return newHand, newDeck }