Initial commit

This commit is contained in:
Trevor Slocum 2020-01-14 04:45:33 -08:00
commit ad184a65b2
13 changed files with 709 additions and 0 deletions

14
.builds/amd64_freebsd.yml Normal file
View File

@ -0,0 +1,14 @@
arch: amd64
environment:
PROJECT_NAME: 'cards-cribbage'
CGO_ENABLED: '1'
GO111MODULE: 'on'
image: freebsd/latest
packages:
- go
sources:
- https://git.sr.ht/~tslocum/cards-cribbage
tasks:
- test: |
cd $PROJECT_NAME
go test ./...

View File

@ -0,0 +1,14 @@
arch: x86_64
environment:
PROJECT_NAME: 'cards-cribbage'
CGO_ENABLED: '1'
GO111MODULE: 'on'
image: alpine/edge
packages:
- go
sources:
- https://git.sr.ht/~tslocum/cards-cribbage
tasks:
- test: |
cd $PROJECT_NAME
go test ./...

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
.idea/
*.sh

2
CHANGELOG Normal file
View File

@ -0,0 +1,2 @@
0.1.0:
- Initial release

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 Trevor Slocum <trevor@rocketnine.space>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

17
README.md Normal file
View File

@ -0,0 +1,17 @@
# cards-cribbage
[![GoDoc](https://godoc.org/git.sr.ht/~tslocum/cards-cribbage?status.svg)](https://godoc.org/git.sr.ht/~tslocum/cards-cribbage)
[![builds.sr.ht status](https://builds.sr.ht/~tslocum/cards-cribbage.svg)](https://builds.sr.ht/~tslocum/cards-cribbage)
[![Donate](https://img.shields.io/liberapay/receives/rocketnine.space.svg?logo=liberapay)](https://liberapay.com/rocketnine.space)
Cribbage rules library
## Documentation
Documentation is available via [godoc](https://godoc.org):
- [cards](https://godoc.org/git.sr.ht/~tslocum/cards)
- [cards-cribbage](https://godoc.org/git.sr.ht/~tslocum/cards-cribbage)
## Support
Please share issues/suggestions [here](https://todo.sr.ht/~tslocum/cards-cribbage).

12
card.go Normal file
View File

@ -0,0 +1,12 @@
package cribbage
import "git.sr.ht/~tslocum/cards"
// Value returns the cribbage value of a card.
func Value(c cards.Card) int {
v := int(c.Face)
if v > 10 {
v = 10
}
return v
}

18
cards.go Normal file
View File

@ -0,0 +1,18 @@
package cribbage
import "git.sr.ht/~tslocum/cards"
// Sum returns the total cribbage value of the supplied cards.
func Sum(c cards.Cards) int {
var v int
for _, card := range c {
v += Value(card)
}
return v
}
func compareCards(i, j interface{}) bool {
icard := i.(cards.Card)
jcard := j.(cards.Card)
return icard.Value() < jcard.Value()
}

8
go.mod Normal file
View File

@ -0,0 +1,8 @@
module git.sr.ht/~tslocum/cards-cribbage
go 1.13
require (
git.sr.ht/~tslocum/cards v0.1.1-0.20200114033738-f9b90e62c278
github.com/fighterlyt/permutation v0.0.0-20170407093504-ac78aa5051ae
)

4
go.sum Normal file
View File

@ -0,0 +1,4 @@
git.sr.ht/~tslocum/cards v0.1.1-0.20200114033738-f9b90e62c278 h1:WjWEU2uvliiJVeoUpjkZV/hS9f4X9qnOIS53n8lucdQ=
git.sr.ht/~tslocum/cards v0.1.1-0.20200114033738-f9b90e62c278/go.mod h1:ABrbSXnsABGTkuJbWZsI+oGtGTOMlAC+HZy3AysOe7c=
github.com/fighterlyt/permutation v0.0.0-20170407093504-ac78aa5051ae h1:wdS91f8H+bGgcjlx5G4LEUVXkmt/uz0VYkc6lZMIjD4=
github.com/fighterlyt/permutation v0.0.0-20170407093504-ac78aa5051ae/go.mod h1:KqCsX+AbfYLoAjwmUkE6ocQHwto7ibjZvTY/c5QhgZg=

325
score.go Normal file
View File

@ -0,0 +1,325 @@
package cribbage
import (
"log"
"sort"
. "git.sr.ht/~tslocum/cards"
"github.com/fighterlyt/permutation"
)
// ScoringType represents a set of scoring rules.
type ScoringType int
// Scoring types
const (
Peg ScoringType = 1
ShowHand ScoringType = 2
ShowCrib ScoringType = 3
)
func (t ScoringType) String() string {
switch t {
case Peg:
return "Peg"
case ShowHand:
return "ShowHand"
case ShowCrib:
return "ShowCrib"
default:
return "?"
}
}
// ScoreType represents a type of score.
type ScoreType int
// Score types and their point values
const (
Score15 ScoreType = 1 // 2
ScorePair ScoreType = 2 // 2
ScoreRun ScoreType = 3 // 1/card
ScoreFlush ScoreType = 4 // 1/card
ScoreNibs ScoreType = 5 // 2
ScoreNobs ScoreType = 6 // 1
Score31 ScoreType = 7 // 2
ScoreGo ScoreType = 8 // 1
)
func (t ScoreType) String() string {
switch t {
case Score15:
return "15"
case ScorePair:
return "Pair"
case ScoreRun:
return "Run"
case ScoreFlush:
return "Flush"
case ScoreNibs:
return "Nibs"
case ScoreNobs:
return "Nobs"
case Score31:
return "31"
case ScoreGo:
return "Go"
default:
return "?"
}
}
// Score returns the score of a pegging play or shown hand.
func Score(scoringType ScoringType, c Cards, starter Card) (int, ScoreResults) {
if (scoringType == ShowHand || scoringType == ShowCrib) && (starter.Face == 0 || starter.Suit == 0) {
return 0, nil
}
var points int
var results ScoreResults
if c.Len() == 0 {
return points, results
}
var scoreCards Cards
if scoringType == Peg {
scoreCards = c.Reverse()
} else {
scoreCards = append(c.Copy(), starter).Sort()
}
// Score 15s
fifteenscore := 0
fifteenvalue := 0
if scoringType != Peg {
var allusedcards []Cards
var usedcards Cards
perm, err := permutation.NewPerm(scoreCards, compareCards)
if err != nil {
log.Panicf("failed to generate a permutation of cards %s", scoreCards)
}
SCORE15:
for permhand, err := perm.Next(); err == nil; permhand, err = perm.Next() {
fifteenvalue = 0
permhand := permhand.(Cards)
usedcards = Cards{}
for _, card := range permhand {
usedcards = append(usedcards, card)
fifteenvalue += Value(card)
if fifteenvalue >= 15 {
if fifteenvalue == 15 {
alreadyused := false
sort.Sort(usedcards)
for _, pastusedcards := range allusedcards {
if usedcards.Equal(pastusedcards) {
alreadyused = true
break
}
}
if !alreadyused {
allusedcards = append(allusedcards, usedcards)
fifteenscore++
results = append(results, ScoreResult{Type: Score15, Cards: usedcards.Sort(), Points: 2})
}
}
continue SCORE15
}
}
}
} else {
if Sum(scoreCards) == 15 {
results = append(results, ScoreResult{Type: Score15, Cards: scoreCards.Sort(), Points: 2})
}
}
// Score pairs
if scoringType != Peg {
var faces []CardFace
SCOREPAIR:
for _, card := range scoreCards {
for _, face := range faces {
if face == card.Face {
continue SCOREPAIR
}
}
var paircards Cards
for _, compcard := range scoreCards {
if compcard.Face != card.Face {
continue
}
paircards = append(paircards, compcard)
}
if len(paircards) > 1 {
pairmultiplier := 1
if len(paircards) == 3 {
pairmultiplier = 2
} else if len(paircards) == 4 {
pairmultiplier = 3
}
results = append(results, ScoreResult{Type: ScorePair, Cards: paircards.Sort(), Points: len(paircards) * pairmultiplier})
}
faces = append(faces, card.Face)
}
} else {
if len(scoreCards) > 0 {
var paircards Cards
for _, compcard := range scoreCards[1:] {
if compcard.Face != scoreCards[0].Face {
break
}
paircards = append(paircards, compcard)
}
pairmultiplier := 1
if len(paircards) == 2 {
pairmultiplier = 2
} else if len(paircards) == 3 {
pairmultiplier = 3
}
if paircards != nil {
results = append(results, ScoreResult{Type: ScorePair, Cards: append(paircards, scoreCards[0]).Sort(), Points: (len(paircards) + 1) * pairmultiplier})
}
}
}
// Score runs
var allRunCards []Cards
var runCards Cards
var runScore int
if scoringType == Peg {
var compHand Cards
var compScore int
var runValue int
runScore = 1
// Examine the pile for a run by shortening the checked pile one card
// after each iteration.
SCOREPEGRUN:
for complen := 0; complen < len(scoreCards); complen++ {
compHand = scoreCards[0 : len(scoreCards)-complen]
compScore = 1
runCards = nil
for i, compcard := range compHand.Sort() {
if i > 0 {
if int(compcard.Face) == (runValue + 1) {
compScore++
} else {
continue SCOREPEGRUN
}
}
runValue = int(compcard.Face)
}
if compScore > runScore {
runScore = compScore
if runScore == len(scoreCards) {
runCards = compHand.Sort()
break SCOREPEGRUN
}
}
}
if runScore >= 3 {
results = append(results, ScoreResult{Type: ScoreRun, Cards: runCards, Points: runScore})
}
} else {
for runLength := 6; runLength > 3; runLength-- {
perm, err := permutation.NewPerm(scoreCards, compareCards)
if err != nil {
log.Panicf("failed to generate a permutation of cards %s", scoreCards)
}
SCOREHANDRUN:
for permhand, err := perm.Next(); err == nil; permhand, err = perm.Next() {
permhand := permhand.(Cards)
runCards = Cards{}
runScore = 0
for i := range permhand {
if i > 0 && permhand[i].Face != permhand[i-1].Face-1 {
break
}
runScore++
runCards = append(runCards, permhand[i])
}
if runScore != runLength {
continue
}
runCards = runCards.Sort()
for _, rc := range allRunCards {
containsAll := true
for _, runCard := range runCards {
if !rc.Contains(runCard) {
containsAll = false
break
}
}
if containsAll {
continue SCOREHANDRUN
}
}
results = append(results, ScoreResult{Type: ScoreRun, Cards: runCards, Points: runScore})
allRunCards = append(allRunCards, runCards)
}
}
}
// Score flushes
if scoringType != Peg {
for _, suit := range StandardSuits {
suitvalue := 0
var flushCards Cards
for _, card := range c {
if card.Suit == suit {
suitvalue++
flushCards = append(flushCards, card)
}
}
if starter.Suit == suit {
suitvalue++
flushCards = append(flushCards, starter)
}
if suitvalue == 5 || (suitvalue == 4 && scoringType == ShowHand) {
results = append(results, ScoreResult{Type: ScoreFlush, Cards: flushCards.Sort(), Points: suitvalue})
break
}
}
}
// Score nobs
if scoringType != Peg {
rightJack := Card{FaceJack, starter.Suit}
if c.Contains(rightJack) {
results = append(results, ScoreResult{Type: ScoreNobs, Cards: Cards{rightJack}, Points: 1})
}
}
// Score 31
if scoringType == Peg && Sum(scoreCards) == 31 {
results = append(results, ScoreResult{Type: Score31, Cards: scoreCards.Sort(), Points: 2})
}
for _, r := range results {
points += r.Points
}
sort.Sort(results)
return points, results
}

47
score_result.go Normal file
View File

@ -0,0 +1,47 @@
package cribbage
import (
"fmt"
. "git.sr.ht/~tslocum/cards"
)
// ScoreResult is a score from pegging or showing a hand.
type ScoreResult struct {
Type ScoreType
Cards Cards
Points int
}
func (r ScoreResult) String() string {
return fmt.Sprintf("%s for %d with %s", r.Type, r.Points, r.Cards)
}
// ScoreResults is a slice of scores from pegging or showing a hand.
type ScoreResults []ScoreResult
func (r ScoreResults) Len() int {
return len(r)
}
func (r ScoreResults) Swap(i, j int) {
r[i], r[j] = r[j], r[i]
}
func (r ScoreResults) Less(i, j int) bool {
if r[i].Type != r[j].Type {
return r[i].Type < r[j].Type
} else if r[i].Points != r[j].Points {
return r[i].Points < r[j].Points
} else if len(r[i].Cards) != len(r[j].Cards) {
return len(r[i].Cards) < len(r[j].Cards)
}
for k := len(r[i].Cards) - 1; k >= 0; k-- {
if r[i].Cards[k].Value() != r[j].Cards[k].Value() {
return r[i].Cards[k].Value() < r[j].Cards[k].Value()
}
}
return i < j
}

225
score_test.go Normal file
View File

@ -0,0 +1,225 @@
package cribbage
import (
"testing"
. "git.sr.ht/~tslocum/cards"
)
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 TestPegScoring(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 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
}