326 lines
7.0 KiB
Go
326 lines
7.0 KiB
Go
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
|
|
}
|