2020-01-14 12:45:33 +00:00
|
|
|
package cribbage
|
|
|
|
|
|
|
|
import (
|
|
|
|
"sort"
|
|
|
|
|
2021-05-19 00:38:16 +00:00
|
|
|
"code.rocketnine.space/tslocum/joker"
|
2020-01-14 12:45:33 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// 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.
|
2020-10-02 02:23:01 +00:00
|
|
|
func Score(scoringType ScoringType, c joker.Cards, starter joker.Card) (int, ScoreResults) {
|
2020-01-14 12:45:33 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2020-10-02 02:23:01 +00:00
|
|
|
var scoreCards joker.Cards
|
|
|
|
var permutations []joker.Cards
|
2020-01-14 12:45:33 +00:00
|
|
|
if scoringType == Peg {
|
2020-01-15 16:07:55 +00:00
|
|
|
scoreCards = c.Reversed()
|
2020-01-14 12:45:33 +00:00
|
|
|
} else {
|
2020-01-15 16:07:55 +00:00
|
|
|
scoreCards = append(c.Copy(), starter)
|
|
|
|
sort.Sort(scoreCards)
|
2020-01-16 14:36:49 +00:00
|
|
|
permutations = scoreCards.Permutations()
|
2020-01-14 12:45:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Score 15s
|
|
|
|
fifteenscore := 0
|
|
|
|
fifteenvalue := 0
|
|
|
|
if scoringType != Peg {
|
2020-10-02 02:23:01 +00:00
|
|
|
var allusedcards []joker.Cards
|
|
|
|
var usedcards joker.Cards
|
2020-01-14 12:45:33 +00:00
|
|
|
SCORE15:
|
2020-01-16 14:36:49 +00:00
|
|
|
for _, permhand := range permutations {
|
2020-01-14 12:45:33 +00:00
|
|
|
fifteenvalue = 0
|
2020-10-02 02:23:01 +00:00
|
|
|
usedcards = joker.Cards{}
|
2020-01-14 12:45:33 +00:00
|
|
|
|
|
|
|
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++
|
|
|
|
|
2020-01-15 16:07:55 +00:00
|
|
|
sort.Sort(usedcards)
|
|
|
|
results = append(results, ScoreResult{Type: Score15, Cards: usedcards, Points: 2})
|
2020-01-14 12:45:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
continue SCORE15
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if Sum(scoreCards) == 15 {
|
2020-01-15 16:07:55 +00:00
|
|
|
results = append(results, ScoreResult{Type: Score15, Cards: scoreCards.Sorted(), Points: 2})
|
2020-01-14 12:45:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Score pairs
|
|
|
|
if scoringType != Peg {
|
2020-10-02 02:23:01 +00:00
|
|
|
var faces []joker.CardFace
|
2020-01-14 12:45:33 +00:00
|
|
|
SCOREPAIR:
|
|
|
|
for _, card := range scoreCards {
|
|
|
|
for _, face := range faces {
|
|
|
|
if face == card.Face {
|
|
|
|
continue SCOREPAIR
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-02 02:23:01 +00:00
|
|
|
var paircards joker.Cards
|
2020-01-14 12:45:33 +00:00
|
|
|
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
|
|
|
|
}
|
2020-01-15 16:07:55 +00:00
|
|
|
sort.Sort(paircards)
|
|
|
|
results = append(results, ScoreResult{Type: ScorePair, Cards: paircards, Points: len(paircards) * pairmultiplier})
|
2020-01-14 12:45:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
faces = append(faces, card.Face)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if len(scoreCards) > 0 {
|
2020-10-02 02:23:01 +00:00
|
|
|
var pairCards joker.Cards
|
2020-01-14 12:45:33 +00:00
|
|
|
for _, compcard := range scoreCards[1:] {
|
|
|
|
if compcard.Face != scoreCards[0].Face {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
2020-01-15 16:07:55 +00:00
|
|
|
pairCards = append(pairCards, compcard)
|
2020-01-14 12:45:33 +00:00
|
|
|
}
|
|
|
|
pairmultiplier := 1
|
2020-01-15 16:07:55 +00:00
|
|
|
if len(pairCards) == 2 {
|
2020-01-14 12:45:33 +00:00
|
|
|
pairmultiplier = 2
|
2020-01-15 16:07:55 +00:00
|
|
|
} else if len(pairCards) == 3 {
|
2020-01-14 12:45:33 +00:00
|
|
|
pairmultiplier = 3
|
|
|
|
}
|
2020-01-15 16:07:55 +00:00
|
|
|
if pairCards != nil {
|
|
|
|
pairCards = append(pairCards, scoreCards[0])
|
|
|
|
sort.Sort(pairCards)
|
|
|
|
results = append(results, ScoreResult{Type: ScorePair, Cards: pairCards, Points: len(pairCards) * pairmultiplier})
|
2020-01-14 12:45:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Score runs
|
2020-10-02 02:23:01 +00:00
|
|
|
var allRunCards []joker.Cards
|
|
|
|
var runCards joker.Cards
|
2020-01-14 12:45:33 +00:00
|
|
|
var runScore int
|
|
|
|
if scoringType == Peg {
|
2020-10-02 02:23:01 +00:00
|
|
|
var compHand joker.Cards
|
2020-01-14 12:45:33 +00:00
|
|
|
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:
|
2020-01-15 16:07:55 +00:00
|
|
|
for complen := len(scoreCards); complen > 0; complen-- {
|
|
|
|
compHand = scoreCards[0:complen].Sorted()
|
2020-01-14 12:45:33 +00:00
|
|
|
compScore = 1
|
|
|
|
runCards = nil
|
|
|
|
|
2020-01-15 16:07:55 +00:00
|
|
|
for i, compcard := range compHand {
|
2020-01-14 12:45:33 +00:00
|
|
|
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) {
|
2020-01-15 16:07:55 +00:00
|
|
|
runCards = compHand
|
2020-01-14 12:45:33 +00:00
|
|
|
break SCOREPEGRUN
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if runScore >= 3 {
|
|
|
|
results = append(results, ScoreResult{Type: ScoreRun, Cards: runCards, Points: runScore})
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
for runLength := 6; runLength > 3; runLength-- {
|
|
|
|
SCOREHANDRUN:
|
2020-01-16 14:36:49 +00:00
|
|
|
for _, permhand := range permutations {
|
2020-10-02 02:23:01 +00:00
|
|
|
runCards = joker.Cards{}
|
2020-01-14 12:45:33 +00:00
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2020-01-15 16:07:55 +00:00
|
|
|
sort.Sort(runCards)
|
2020-01-14 12:45:33 +00:00
|
|
|
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 {
|
2020-10-02 02:23:01 +00:00
|
|
|
for _, suit := range joker.StandardSuits {
|
2020-01-14 12:45:33 +00:00
|
|
|
suitvalue := 0
|
2020-10-02 02:23:01 +00:00
|
|
|
var flushCards joker.Cards
|
2020-01-14 12:45:33 +00:00
|
|
|
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) {
|
2020-01-15 16:07:55 +00:00
|
|
|
sort.Sort(flushCards)
|
|
|
|
results = append(results, ScoreResult{Type: ScoreFlush, Cards: flushCards, Points: suitvalue})
|
2020-01-14 12:45:33 +00:00
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Score nobs
|
|
|
|
if scoringType != Peg {
|
2020-10-02 02:23:01 +00:00
|
|
|
rightJack := joker.Card{joker.FaceJack, starter.Suit}
|
2020-01-14 12:45:33 +00:00
|
|
|
if c.Contains(rightJack) {
|
2020-10-02 02:23:01 +00:00
|
|
|
results = append(results, ScoreResult{Type: ScoreNobs, Cards: joker.Cards{rightJack}, Points: 1})
|
2020-01-14 12:45:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Score 31
|
|
|
|
if scoringType == Peg && Sum(scoreCards) == 31 {
|
2020-01-15 16:07:55 +00:00
|
|
|
sort.Sort(scoreCards)
|
|
|
|
results = append(results, ScoreResult{Type: Score31, Cards: scoreCards, Points: 2})
|
2020-01-14 12:45:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, r := range results {
|
|
|
|
points += r.Points
|
|
|
|
}
|
|
|
|
sort.Sort(results)
|
|
|
|
return points, results
|
|
|
|
}
|