// Package bowling is for counting bowling scores.
package bowling
import (
"fmt"
)
const maxRolls = 21
type Game struct {
currRoll int // current Roll
rolls []int
}
// NewGame creates a new bowling game.
func NewGame() *Game {
return &Game{rolls: make([]int, maxRolls), currRoll: 0}
}
// Score calculates the score.
func (g *Game) Score() (int, error) {
if g.currRoll < maxRolls-1 {
return 0, fmt.Errorf("not enough rolls to score")
}
if g.currRoll == maxRolls-1 && (g.rolls[maxRolls-2] == 10 ||
g.rolls[maxRolls-2]+g.rolls[maxRolls-3] == 10) {
return 0, fmt.Errorf("last one is a strike, waiting for fill ball to score")
}
var ret int
for i, v := range g.rolls {
ret += v
// strike handling
if i%2 == 0 && v == 10 {
// this is a strike
if i < 18 {
ret += g.rolls[i+2]
if g.rolls[i+2] == 10 {
// strike after strike
ret += g.rolls[i+4]
} else {
ret += g.rolls[i+3]
}
} else if i == 18 {
// ret += g.rolls[i+1] + g.rolls[i+2]
}
}
// spare handling
if i%2 != 0 && v+g.rolls[i-1] == 10 && g.rolls[i-1] != 10 {
// spare counts twice
if i == 19 {
// unless this is the last frame
continue
}
ret += g.rolls[i+1]
}
}
return ret, nil
}
// Roll handles a roll in the game.
func (g *Game) Roll(pins int) error {
if pins < 0 || pins > 10 {
return fmt.Errorf("pins out of range")
}
if g.currRoll == maxRolls {
return fmt.Errorf("to much rolls")
}
if g.currRoll == maxRolls-1 && g.rolls[maxRolls-3]+g.rolls[maxRolls-2] < 10 {
return fmt.Errorf("21st roll is only valid when previous is strike/spare")
}
g.rolls[g.currRoll] = pins
if g.currRoll%2 != 0 && g.currRoll != maxRolls-2 {
if g.rolls[g.currRoll]+g.rolls[g.currRoll-1] > 10 {
return fmt.Errorf("two rolls sum in frame > 10")
}
}
// 20 is fill ball
if g.currRoll == maxRolls-1 {
if g.rolls[18]+g.rolls[19] != 10 && g.rolls[18]+g.rolls[19] != 20 {
if g.rolls[g.currRoll]+g.rolls[g.currRoll-1] > 10 {
return fmt.Errorf("fill ball problem")
}
}
}
// strike and !last frame closes the frame
if pins == 10 && g.currRoll < 18 {
g.currRoll++
}
g.currRoll++
return nil
}