Init
commit
9cfa4b826c
@ -0,0 +1,214 @@
|
||||
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
|
||||
}
|
@ -0,0 +1,129 @@
|
||||
package deck
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func showDeck(d []Card) {
|
||||
fmt.Println("Showing whole deck")
|
||||
for _, card := range d {
|
||||
fmt.Println(card)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrintCard(t *testing.T) {
|
||||
tests := []struct{
|
||||
input Card
|
||||
want string
|
||||
}{
|
||||
{Card{Hearts, Queen}, "Queen of Hearts"},
|
||||
{Card{Suit: Joker}, "Joker"},
|
||||
}
|
||||
for _, test := range tests {
|
||||
if test.input.String() != test.want {
|
||||
t.Errorf("Got %s; wanted %s", test.input.String(), test.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
d := New()
|
||||
c := Card{Diamonds, Six}
|
||||
if d[18] != c {
|
||||
t.Errorf("Deck's 5th card should be %s, is %s", c, d[18])
|
||||
showDeck(d)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSortByRank(t *testing.T) {
|
||||
d := New(SortByRank)
|
||||
c := Card{Clubs, Five}
|
||||
if d[18] != c {
|
||||
t.Errorf("Deck's 18th card should be %s, is %s", c, d[18])
|
||||
showDeck(d)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSortBySuit(t *testing.T) {
|
||||
d := New()
|
||||
d = SortByRank(d)
|
||||
d = SortBySuit(d)
|
||||
c := Card{Diamonds, Six}
|
||||
if d[18] != c {
|
||||
t.Errorf("Deck's 5th card should be %s, is %s", c, d[18])
|
||||
showDeck(d)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddJokers(t *testing.T) {
|
||||
d := New()
|
||||
initLength := len(d)
|
||||
d = AddNJokers(17)(d)
|
||||
if initLength + 17 != len(d) {
|
||||
t.Errorf("Wrong number of cards in deck after adding Jokers")
|
||||
showDeck(d)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveCard(t *testing.T) {
|
||||
d := New(RemoveCards(Card{Spades, Ace}))
|
||||
for _, card := range d {
|
||||
if card == (Card{Spades, Ace}) {
|
||||
t.Error("The ace of spades is still in the deck after calling RemoveCard(Card{Spades, Ace})")
|
||||
}
|
||||
}
|
||||
before := len(d)
|
||||
d = RemoveCards(Card{Clubs, Jack})(d)
|
||||
if len(d) != (before - 1) {
|
||||
t.Error("Remove() didn't reduce card count by 1")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveRank(t *testing.T) {
|
||||
d := New(RemoveRank(Seven))
|
||||
for _, card := range d {
|
||||
if card.Rank == Seven {
|
||||
t.Error("There are still Sevens in the deck after calling RemoveRank(7)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveSuit(t *testing.T) {
|
||||
d := New(RemoveSuit(Hearts))
|
||||
for _, card := range d {
|
||||
if card.Suit == Hearts {
|
||||
t.Error("There are still Hearts in the deck after calling RemoveSuit(Hearts)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddDeck(t *testing.T) {
|
||||
d := New()
|
||||
before := len(d)
|
||||
d = AddDeck(d)
|
||||
if len(d) != before*2 {
|
||||
t.Error("AddDeck didn't double default deck size")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDraw(t *testing.T) {
|
||||
d := New()
|
||||
lenBefore := len(d)
|
||||
firstTwo := d[0:2]
|
||||
hand := make([]Card, 0, 5)
|
||||
hand, d = Draw(hand, d)
|
||||
hand, d = Draw(hand, d)
|
||||
if firstTwo[0] != hand[0] {
|
||||
t.Errorf("The first drawn card (got %s) should be the first in the deck (was %s)\n",
|
||||
hand[0], firstTwo[0])
|
||||
}
|
||||
if firstTwo[1] != hand[1] {
|
||||
t.Errorf("The second drawn card (got %s) should be the second in the deck (was %s)\n",
|
||||
hand[0], firstTwo[0])
|
||||
}
|
||||
if len(d) != lenBefore - 2 {
|
||||
t.Errorf(`Expected length of deck to be reduced by two when calling Draw() twice,
|
||||
length before is %d, and length after is %d`, lenBefore, len(d))
|
||||
}
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
// Code generated by "stringer -type=Suit,Rank"; DO NOT EDIT.
|
||||
|
||||
package deck
|
||||
|
||||
import "strconv"
|
||||
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[Spades-0]
|
||||
_ = x[Diamonds-1]
|
||||
_ = x[Clubs-2]
|
||||
_ = x[Hearts-3]
|
||||
_ = x[Joker-4]
|
||||
}
|
||||
|
||||
const _Suit_name = "SpadesDiamondsClubsHeartsJoker"
|
||||
|
||||
var _Suit_index = [...]uint8{0, 6, 14, 19, 25, 30}
|
||||
|
||||
func (i Suit) String() string {
|
||||
if i >= Suit(len(_Suit_index)-1) {
|
||||
return "Suit(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _Suit_name[_Suit_index[i]:_Suit_index[i+1]]
|
||||
}
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[Ace-1]
|
||||
_ = x[Two-2]
|
||||
_ = x[Three-3]
|
||||
_ = x[Four-4]
|
||||
_ = x[Five-5]
|
||||
_ = x[Six-6]
|
||||
_ = x[Seven-7]
|
||||
_ = x[Eight-8]
|
||||
_ = x[Nine-9]
|
||||
_ = x[Ten-10]
|
||||
_ = x[Jack-11]
|
||||
_ = x[Queen-12]
|
||||
_ = x[King-13]
|
||||
}
|
||||
|
||||
const _Rank_name = "AceTwoThreeFourFiveSixSevenEightNineTenJackQueenKing"
|
||||
|
||||
var _Rank_index = [...]uint8{0, 3, 6, 11, 15, 19, 22, 27, 32, 36, 39, 43, 48, 52}
|
||||
|
||||
func (i Rank) String() string {
|
||||
i -= 1
|
||||
if i >= Rank(len(_Rank_index)-1) {
|
||||
return "Rank(" + strconv.FormatInt(int64(i+1), 10) + ")"
|
||||
}
|
||||
return _Rank_name[_Rank_index[i]:_Rank_index[i+1]]
|
||||
}
|
Loading…
Reference in New Issue