master
joshua 2 years ago
commit 9cfa4b826c

@ -0,0 +1,3 @@
module git.clement.tel/joshua/09_deck
go 1.20

@ -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…
Cancel
Save