Cribbage scoring library
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

543 lines
15 KiB

package cribbage
import (
"testing"
. "code.rocketnine.space/tslocum/joker"
)
var (
testHandA = Cards{
Card{Face2, SuitSpades},
Card{Face3, SuitSpades},
Card{Face4, SuitSpades},
Card{Face5, SuitSpades}}
testHandB = Cards{
Card{FaceJack, SuitHearts},
Card{Face5, SuitDiamonds},
Card{Face5, SuitClubs},
Card{Face5, SuitSpades}}
testHandC = Cards{
Card{Face3, SuitHearts},
Card{Face3, SuitDiamonds},
Card{Face3, SuitClubs},
Card{Face3, SuitSpades}}
testHandD = Cards{
Card{Face5, SuitHearts},
Card{Face5, SuitDiamonds},
Card{Face5, SuitClubs},
Card{Face5, SuitSpades}}
testHandE = Cards{
Card{Face7, SuitHearts},
Card{Face8, SuitHearts},
Card{Face9, SuitHearts},
Card{Face10, SuitHearts}}
testHandF = Cards{
Card{Face3, SuitHearts},
Card{Face6, SuitHearts},
Card{Face5, SuitHearts},
Card{Face4, SuitHearts}}
testHandG = Cards{
Card{Face6, SuitHearts},
Card{Face5, SuitHearts},
Card{Face4, SuitHearts}}
testHandH = Cards{
Card{FaceAce, SuitHearts},
Card{Face3, SuitHearts},
Card{Face2, SuitHearts},
Card{Face4, SuitHearts}}
testHandI = Cards{
Card{FaceAce, SuitHearts},
Card{Face3, SuitHearts},
Card{FaceAce, SuitClubs},
Card{Face2, SuitHearts},
Card{Face4, SuitHearts},
Card{Face7, SuitHearts}}
testHandJ = Cards{
Card{Face7, SuitHearts},
Card{Face3, SuitHearts},
Card{Face2, SuitHearts},
Card{Face4, SuitHearts}}
testHandK = Cards{
Card{Face5, SuitHearts},
Card{Face5, SuitDiamonds},
Card{Face5, SuitClubs},
Card{FaceJack, SuitSpades}}
testHandL = Cards{
Card{Face2, SuitHearts},
Card{Face2, SuitDiamonds},
Card{Face7, SuitClubs},
Card{Face7, SuitSpades}}
testHandM = Cards{
Card{Face5, SuitHearts},
Card{Face5, SuitDiamonds},
Card{Face5, SuitClubs},
Card{FaceKing, SuitSpades}}
)
type expectedPegScore struct {
Hand Cards
Result []ScoreResult
}
var expectedPegScores = []expectedPegScore{
{testHandA, []ScoreResult{
{Type: ScoreRun, Points: 4, Cards: Cards{
Card{Face2, SuitSpades},
Card{Face3, SuitSpades},
Card{Face4, SuitSpades},
Card{Face5, SuitSpades}}}}},
{testHandB, []ScoreResult{
{Type: ScorePair, Points: 6, Cards: Cards{
Card{Face5, SuitDiamonds},
Card{Face5, SuitClubs},
Card{Face5, SuitSpades}}}}},
{testHandC, []ScoreResult{
{Type: ScorePair, Points: 12, Cards: Cards{
Card{Face3, SuitHearts},
Card{Face3, SuitDiamonds},
Card{Face3, SuitClubs},
Card{Face3, SuitSpades}}}}},
{testHandF, []ScoreResult{
{Type: ScoreRun, Points: 4, Cards: Cards{
Card{Face3, SuitHearts},
Card{Face4, SuitHearts},
Card{Face5, SuitHearts},
Card{Face6, SuitHearts}}}}},
{testHandG, []ScoreResult{
{Type: Score15, Points: 2, Cards: Cards{
Card{Face4, SuitHearts},
Card{Face5, SuitHearts},
Card{Face6, SuitHearts}}},
{Type: ScoreRun, Points: 3, Cards: Cards{
Card{Face4, SuitHearts},
Card{Face5, SuitHearts},
Card{Face6, SuitHearts}}}}},
{testHandH, []ScoreResult{
{Type: ScoreRun, Points: 4, Cards: Cards{
Card{FaceAce, SuitHearts},
Card{Face2, SuitHearts},
Card{Face3, SuitHearts},
Card{Face4, SuitHearts}}}}},
{testHandI, []ScoreResult{}},
{testHandJ, []ScoreResult{
{Type: ScoreRun, Points: 3}}},
{testHandK, []ScoreResult{}},
{testHandL, []ScoreResult{
{Type: ScorePair, Points: 2, Cards: Cards{
Card{Face7, SuitClubs},
Card{Face7, SuitSpades}}}}},
{testHandM, []ScoreResult{}},
}
type expectedShowScore struct {
Starter Card
Hand Cards
HandResult []ScoreResult
CribResult []ScoreResult
}
var expectedShowScores = []expectedShowScore{
{Card{FaceAce, SuitSpades},
testHandA, []ScoreResult{
{Type: Score15, Points: 2, Cards: Cards{
Card{FaceAce, SuitSpades},
Card{Face2, SuitSpades},
Card{Face3, SuitSpades},
Card{Face4, SuitSpades},
Card{Face5, SuitSpades}}},
{Type: ScoreRun, Points: 5, Cards: Cards{
Card{FaceAce, SuitSpades},
Card{Face2, SuitSpades},
Card{Face3, SuitSpades},
Card{Face4, SuitSpades},
Card{Face5, SuitSpades}}},
{Type: ScoreFlush, Points: 5, Cards: Cards{
Card{FaceAce, SuitSpades},
Card{Face2, SuitSpades},
Card{Face3, SuitSpades},
Card{Face4, SuitSpades},
Card{Face5, SuitSpades}}},
}, []ScoreResult{
{Type: Score15, Points: 2, Cards: Cards{
Card{FaceAce, SuitSpades},
Card{Face2, SuitSpades},
Card{Face3, SuitSpades},
Card{Face4, SuitSpades},
Card{Face5, SuitSpades}}},
{Type: ScoreRun, Points: 5, Cards: Cards{
Card{FaceAce, SuitSpades},
Card{Face2, SuitSpades},
Card{Face3, SuitSpades},
Card{Face4, SuitSpades},
Card{Face5, SuitSpades}}},
{Type: ScoreFlush, Points: 5, Cards: Cards{
Card{FaceAce, SuitSpades},
Card{Face2, SuitSpades},
Card{Face3, SuitSpades},
Card{Face4, SuitSpades},
Card{Face5, SuitSpades}}},
}},
{Card{FaceKing, SuitClubs},
testHandA, []ScoreResult{
{Type: Score15, Points: 2, Cards: Cards{
Card{Face5, SuitSpades},
Card{FaceKing, SuitClubs}}},
{Type: Score15, Points: 2, Cards: Cards{
Card{Face2, SuitSpades},
Card{Face3, SuitSpades},
Card{FaceKing, SuitClubs}}},
{Type: ScoreRun, Points: 4, Cards: Cards{
Card{Face2, SuitSpades},
Card{Face3, SuitSpades},
Card{Face4, SuitSpades},
Card{Face5, SuitSpades}}},
{Type: ScoreFlush, Points: 4, Cards: Cards{
Card{Face2, SuitSpades},
Card{Face3, SuitSpades},
Card{Face4, SuitSpades},
Card{Face5, SuitSpades}}},
}, []ScoreResult{
{Type: Score15, Points: 2, Cards: Cards{
Card{Face5, SuitSpades},
Card{FaceKing, SuitClubs}}},
{Type: Score15, Points: 2, Cards: Cards{
Card{Face2, SuitSpades},
Card{Face3, SuitSpades},
Card{FaceKing, SuitClubs}}},
{Type: ScoreRun, Points: 4, Cards: Cards{
Card{Face2, SuitSpades},
Card{Face3, SuitSpades},
Card{Face4, SuitSpades},
Card{Face5, SuitSpades}}},
}},
{Card{Face8, SuitClubs},
testHandE, []ScoreResult{
{Type: Score15, Points: 2, Cards: Cards{
Card{Face7, SuitHearts},
Card{Face8, SuitHearts}}},
{Type: Score15, Points: 2, Cards: Cards{
Card{Face7, SuitHearts},
Card{Face8, SuitClubs}}},
{Type: ScorePair, Points: 2, Cards: Cards{
Card{Face8, SuitHearts},
Card{Face8, SuitClubs}}},
{Type: ScoreRun, Points: 4, Cards: Cards{
Card{Face7, SuitHearts},
Card{Face8, SuitHearts},
Card{Face9, SuitHearts},
Card{Face10, SuitHearts}}},
{Type: ScoreRun, Points: 4, Cards: Cards{
Card{Face7, SuitHearts},
Card{Face8, SuitClubs},
Card{Face9, SuitHearts},
Card{Face10, SuitHearts}}},
{Type: ScoreFlush, Points: 4, Cards: Cards{
Card{Face7, SuitHearts},
Card{Face8, SuitHearts},
Card{Face9, SuitHearts},
Card{Face10, SuitHearts}}},
}, []ScoreResult{
{Type: Score15, Points: 2, Cards: Cards{
Card{Face7, SuitHearts},
Card{Face8, SuitHearts}}},
{Type: Score15, Points: 2, Cards: Cards{
Card{Face7, SuitHearts},
Card{Face8, SuitClubs}}},
{Type: ScorePair, Points: 2, Cards: Cards{
Card{Face8, SuitHearts},
Card{Face8, SuitClubs}}},
{Type: ScoreRun, Points: 4, Cards: Cards{
Card{Face7, SuitHearts},
Card{Face8, SuitHearts},
Card{Face9, SuitHearts},
Card{Face10, SuitHearts}}},
{Type: ScoreRun, Points: 4, Cards: Cards{
Card{Face7, SuitHearts},
Card{Face8, SuitClubs},
Card{Face9, SuitHearts},
Card{Face10, SuitHearts}}},
}},
{Card{Face5, SuitSpades},
testHandK, []ScoreResult{
{Type: Score15, Points: 2, Cards: Cards{
Card{Face5, SuitHearts},
Card{FaceJack, SuitSpades}}},
{Type: Score15, Points: 2, Cards: Cards{
Card{Face5, SuitDiamonds},
Card{FaceJack, SuitSpades}}},
{Type: Score15, Points: 2, Cards: Cards{
Card{Face5, SuitClubs},
Card{FaceJack, SuitSpades}}},
{Type: Score15, Points: 2, Cards: Cards{
Card{Face5, SuitSpades},
Card{FaceJack, SuitSpades}}},
{Type: Score15, Points: 2, Cards: Cards{
Card{Face5, SuitHearts},
Card{Face5, SuitDiamonds},
Card{Face5, SuitClubs}}},
{Type: Score15, Points: 2, Cards: Cards{
Card{Face5, SuitHearts},
Card{Face5, SuitDiamonds},
Card{Face5, SuitSpades}}},
{Type: Score15, Points: 2, Cards: Cards{
Card{Face5, SuitHearts},
Card{Face5, SuitClubs},
Card{Face5, SuitSpades}}},
{Type: Score15, Points: 2, Cards: Cards{
Card{Face5, SuitDiamonds},
Card{Face5, SuitClubs},
Card{Face5, SuitSpades}}},
{Type: ScorePair, Points: 12, Cards: Cards{
Card{Face5, SuitHearts},
Card{Face5, SuitDiamonds},
Card{Face5, SuitClubs},
Card{Face5, SuitSpades}}},
{Type: ScoreNobs, Points: 1, Cards: Cards{
Card{FaceJack, SuitSpades}}},
}, []ScoreResult{
{Type: Score15, Points: 2, Cards: Cards{
Card{Face5, SuitHearts},
Card{FaceJack, SuitSpades}}},
{Type: Score15, Points: 2, Cards: Cards{
Card{Face5, SuitDiamonds},
Card{FaceJack, SuitSpades}}},
{Type: Score15, Points: 2, Cards: Cards{
Card{Face5, SuitClubs},
Card{FaceJack, SuitSpades}}},
{Type: Score15, Points: 2, Cards: Cards{
Card{Face5, SuitSpades},
Card{FaceJack, SuitSpades}}},
{Type: Score15, Points: 2, Cards: Cards{
Card{Face5, SuitHearts},
Card{Face5, SuitDiamonds},
Card{Face5, SuitClubs}}},
{Type: Score15, Points: 2, Cards: Cards{
Card{Face5, SuitHearts},
Card{Face5, SuitDiamonds},
Card{Face5, SuitSpades}}},
{Type: Score15, Points: 2, Cards: Cards{
Card{Face5, SuitHearts},
Card{Face5, SuitClubs},
Card{Face5, SuitSpades}}},
{Type: Score15, Points: 2, Cards: Cards{
Card{Face5, SuitDiamonds},
Card{Face5, SuitClubs},
Card{Face5, SuitSpades}}},
{Type: ScorePair, Points: 12, Cards: Cards{
Card{Face5, SuitHearts},
Card{Face5, SuitDiamonds},
Card{Face5, SuitClubs},
Card{Face5, SuitSpades}}},
{Type: ScoreNobs, Points: 1, Cards: Cards{
Card{FaceJack, SuitSpades}}},
}},
{Card{Face5, SuitSpades},
testHandM, []ScoreResult{
{Type: Score15, Points: 2, Cards: Cards{
Card{Face5, SuitHearts},
Card{FaceKing, SuitSpades}}},
{Type: Score15, Points: 2, Cards: Cards{
Card{Face5, SuitDiamonds},
Card{FaceKing, SuitSpades}}},
{Type: Score15, Points: 2, Cards: Cards{
Card{Face5, SuitClubs},
Card{FaceKing, SuitSpades}}},
{Type: Score15, Points: 2, Cards: Cards{
Card{Face5, SuitSpades},
Card{FaceKing, SuitSpades}}},
{Type: Score15, Points: 2, Cards: Cards{
Card{Face5, SuitHearts},
Card{Face5, SuitDiamonds},
Card{Face5, SuitClubs}}},
{Type: Score15, Points: 2, Cards: Cards{
Card{Face5, SuitHearts},
Card{Face5, SuitDiamonds},
Card{Face5, SuitSpades}}},
{Type: Score15, Points: 2, Cards: Cards{
Card{Face5, SuitHearts},
Card{Face5, SuitClubs},
Card{Face5, SuitSpades}}},
{Type: Score15, Points: 2, Cards: Cards{
Card{Face5, SuitDiamonds},
Card{Face5, SuitClubs},
Card{Face5, SuitSpades}}},
{Type: ScorePair, Points: 12, Cards: Cards{
Card{Face5, SuitHearts},
Card{Face5, SuitDiamonds},
Card{Face5, SuitClubs},
Card{Face5, SuitSpades}}},
}, []ScoreResult{
{Type: Score15, Points: 2, Cards: Cards{
Card{Face5, SuitHearts},
Card{FaceKing, SuitSpades}}},
{Type: Score15, Points: 2, Cards: Cards{
Card{Face5, SuitDiamonds},
Card{FaceKing, SuitSpades}}},
{Type: Score15, Points: 2, Cards: Cards{
Card{Face5, SuitClubs},
Card{FaceKing, SuitSpades}}},
{Type: Score15, Points: 2, Cards: Cards{
Card{Face5, SuitSpades},
Card{FaceKing, SuitSpades}}},
{Type: Score15, Points: 2, Cards: Cards{
Card{Face5, SuitHearts},
Card{Face5, SuitDiamonds},
Card{Face5, SuitClubs}}},
{Type: Score15, Points: 2, Cards: Cards{
Card{Face5, SuitHearts},
Card{Face5, SuitDiamonds},
Card{Face5, SuitSpades}}},
{Type: Score15, Points: 2, Cards: Cards{
Card{Face5, SuitHearts},
Card{Face5, SuitClubs},
Card{Face5, SuitSpades}}},
{Type: Score15, Points: 2, Cards: Cards{
Card{Face5, SuitDiamonds},
Card{Face5, SuitClubs},
Card{Face5, SuitSpades}}},
{Type: ScorePair, Points: 12, Cards: Cards{
Card{Face5, SuitHearts},
Card{Face5, SuitDiamonds},
Card{Face5, SuitClubs},
Card{Face5, SuitSpades}}},
}},
}
func TestScorePeg(t *testing.T) {
t.Parallel()
for i, expected := range expectedPegScores {
if Sum(expected.Hand) > 31 {
t.Errorf("case %d: invalid peg hand sum: got %d, want <=31: %s", i, Sum(expected.Hand), expected.Hand)
}
pegPoints, pegResult := Score(Peg, expected.Hand, Card{})
if !resultsEqual(pegResult, expected.Result) {
t.Fatalf("case %d: incorrect peg result: got %s, want %s: %s", i, pegResult, expected.Result, expected.Hand)
}
var expectedPoints int
for _, r := range expected.Result {
expectedPoints += r.Points
}
if pegPoints != expectedPoints {
t.Fatalf("case %d: incorrect peg score: got %d, want %d: %s", i, pegPoints, expectedPoints, expected.Hand)
}
}
}
func TestScoreShowHand(t *testing.T) {
t.Parallel()
var expectedPoints int
for i, expected := range expectedShowScores {
handPoints, handResult := Score(ShowHand, expected.Hand, expected.Starter)
if !resultsEqual(handResult, expected.HandResult) {
t.Fatalf("case %d: incorrect hand result: got %s, want %s: %s - %s", i, handResult, expected.HandResult, expected.Starter, expected.Hand)
}
expectedPoints = 0
for _, r := range expected.HandResult {
expectedPoints += r.Points
}
if handPoints != expectedPoints {
t.Fatalf("case %d: incorrect hand score: got %d, want %d: %s - %s", i, handPoints, expectedPoints, expected.Starter, expected.Hand)
}
}
}
func TestScoreShowCrib(t *testing.T) {
t.Parallel()
var expectedPoints int
for i, expected := range expectedShowScores {
cribPoints, cribResult := Score(ShowCrib, expected.Hand, expected.Starter)
if !resultsEqual(cribResult, expected.CribResult) {
t.Fatalf("case %d: incorrect crib result: got %s, want %s: %s - %s", i, cribResult, expected.CribResult, expected.Starter, expected.Hand)
}
expectedPoints = 0
for _, r := range expected.CribResult {
expectedPoints += r.Points
}
if cribPoints != expectedPoints {
t.Fatalf("case %d: incorrect crib score: got %d, want %d: %s - %s", i, cribPoints, expectedPoints, expected.Starter, expected.Hand)
}
}
}
func BenchmarkScorePeg(b *testing.B) {
var (
score int
results ScoreResults
)
b.ResetTimer()
for i := 0; i < b.N; i++ {
for _, expected := range expectedPegScores {
score, results = Score(Peg, expected.Hand, Card{})
}
}
_, _ = score, results
}
func BenchmarkScoreShowHand(b *testing.B) {
var (
score int
results ScoreResults
)
b.ResetTimer()
for i := 0; i < b.N; i++ {
for _, expected := range expectedShowScores {
score, results = Score(ShowHand, expected.Hand, expected.Starter)
}
}
_, _ = score, results
}
func BenchmarkScoreShowCrib(b *testing.B) {
var (
score int
results ScoreResults
)
b.ResetTimer()
for i := 0; i < b.N; i++ {
for _, expected := range expectedShowScores {
score, results = Score(ShowCrib, expected.Hand, expected.Starter)
}
}
_, _ = score, results
}
func resultsEqual(a []ScoreResult, b []ScoreResult) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if a[i].Type != b[i].Type {
return false
} else if a[i].Points != b[i].Points {
return false
} else if len(a[i].Cards) != len(b[i].Cards) {
return false
}
for j := range a[i].Cards {
if !a[i].Cards[j].Equal(b[i].Cards[j]) {
return false
}
}
}
return true
}