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