Hi,
I have the code below I am using to learn coding in Go.
It is a bowling game, it will calculate the final score and show visual representation of the game.
I am looking for advice and feedback about this code, so I can check if I am learning correct and if possible learn new things from your comment. Any feedback will be much appreciated.
The code is run-able on playground also: https://play.golang.org/p/QHf5Cqmx8M
package main
import (
"fmt"
"strconv"
)
const (
maxPin int = 10
maxFrames int = 10
)
type frameValue interface {
Value() int
FirstBallValue() int
SecondBallValue() int
GetNext() frameValue
SetNext(frameValue)
}
type frame struct {
FirstBall int
SecondBall int
next frameValue
}
type extraBallFrame struct {
*frame
ExtraBall int
}
// Game : Bownling game
type Game struct {
head frameValue
tail frameValue
}
// ### frame
func (f *frame) IsStrike() bool {
return f.FirstBall == maxPin
}
func (f *frame) IsSpare() bool {
if f.IsStrike() {
return false
}
return (f.FirstBall + f.SecondBall) == maxPin
}
func (f *frame) String() string {
if f.IsStrike() {
return "X"
}
if f.IsSpare() {
return fmt.Sprintf("%v/", f.FirstBall)
}
return fmt.Sprintf("%v%v", f.FirstBall, f.SecondBall)
}
func (f *frame) Value() int {
sum := f.FirstBall + f.SecondBall
if f.next != nil {
if f.IsSpare() {
sum += f.next.FirstBallValue()
}
if f.IsStrike() {
sum += f.next.FirstBallValue() + f.next.SecondBallValue()
}
}
return sum
}
func (f *frame) FirstBallValue() int {
return f.FirstBall
}
func (f *frame) SecondBallValue() int {
if f.IsStrike() && f.next != nil {
return f.next.FirstBallValue()
}
return f.SecondBall
}
func (f *frame) SetNext(v frameValue) {
f.next = v
}
func (f *frame) GetNext() frameValue {
return f.next
}
// ### extraBallFrame
func (e *extraBallFrame) Value() int {
return e.frame.Value()
}
func (e *extraBallFrame) FirstBallValue() int {
return e.frame.FirstBallValue()
}
func (e *extraBallFrame) SecondBallValue() int {
return e.SecondBall + e.ExtraBall
}
func (e *extraBallFrame) String() string {
s := ""
if e.FirstBall == maxPin {
s += "X"
} else {
s += strconv.Itoa(e.FirstBall)
}
if e.SecondBall == maxPin {
s += "X"
} else if (e.FirstBall + e.SecondBall) == maxPin {
s += "/"
} else {
s += strconv.Itoa(e.SecondBall)
}
if e.ExtraBall == maxPin {
s += "X"
} else {
s += strconv.Itoa(e.ExtraBall)
}
return s
}
// #### Game
func (g *Game) add(frame frameValue) {
if g.head == nil {
g.head = frame
g.tail = g.head
} else {
g.tail.SetNext(frame)
g.tail = frame
}
}
func (g *Game) addFrameInternal(ball1, ball2 int) {
g.add(&frame{ball1, ball2, nil})
}
func (g *Game) addFrameExtraInternal(ball1, ball2, extraBall int) {
g.add(&extraBallFrame{&frame{ball1, ball2, nil}, extraBall})
}
// AddFrame : Add frame with value from two ball's
func (g *Game) AddFrame(ball1, ball2 int) *Game {
g.addFrameInternal(ball1, ball2)
return g
}
// AddFrameExtra : Add frame with value from three ball's. That should be the latest frame
func (g *Game) AddFrameExtra(ball1, ball2, extraBall int) *Game {
g.addFrameExtraInternal(ball1, ball2, extraBall)
return g
}
// AddStrike : Add frame as strike
func (g *Game) AddStrike() *Game {
g.addFrameInternal(10, 0)
return g
}
// AddSpare : Add rame as spare, second ball value is calculated
func (g *Game) AddSpare(ball1 int) *Game {
g.addFrameInternal(ball1, (maxPin - ball1))
return g
}
// AddMiss : Add frame as missing both ball's
func (g *Game) AddMiss() *Game {
g.addFrameInternal(0, 0)
return g
}
// CalculateScore :
func (g *Game) CalculateScore() int {
var score int
for e := g.head; e != nil; e = e.GetNext() {
score += e.Value()
}
return score
}
// Show :
func (g *Game) Show() string {
var result string
for e := g.head; e != nil; e = e.GetNext() {
result += fmt.Sprintf("%v,", e)
}
return result[:len(result)-1]
}
func testGame(game *Game, shouldBeValue int, shouldBeString string) {
asIsValue := game.CalculateScore()
if shouldBeValue != asIsValue {
fmt.Println("ERROR - Expected:", shouldBeValue, "Result:", asIsValue)
}
asIsString := game.Show()
if shouldBeString != asIsString {
fmt.Println("ERROR - Expected:", shouldBeString, "Result:", asIsString)
}
}
func main() {
var shouldBeValue int
var shouldBeString string
var game *Game
shouldBeValue = 270
shouldBeString = "X,X,X,X,X,X,X,X,X,9/1"
game = &Game{}
game.AddStrike().AddStrike().AddStrike().AddStrike().AddStrike().AddStrike().AddStrike().AddStrike().AddStrike().AddFrameExtra(9, 1, 1)
testGame(game, shouldBeValue, shouldBeString)
shouldBeValue = 271
shouldBeString = "X,X,X,X,X,X,X,X,X,1/X"
game = &Game{}
game.AddStrike().AddStrike().AddStrike().AddStrike().AddStrike().AddStrike().AddStrike().AddStrike().AddStrike().AddFrameExtra(1, 9, 10)
testGame(game, shouldBeValue, shouldBeString)
shouldBeValue = 300
shouldBeString = "X,X,X,X,X,X,X,X,X,XXX"
game = &Game{}
game.AddStrike().AddStrike().AddStrike().AddStrike().AddStrike().AddStrike().AddStrike().AddStrike().AddStrike().AddFrameExtra(10, 10, 10)
testGame(game, shouldBeValue, shouldBeString)
shouldBeValue = 20
shouldBeString = "11,11,11,11,11,11,11,11,11,11"
game = &Game{}
game.AddFrame(1, 1).AddFrame(1, 1).AddFrame(1, 1).AddFrame(1, 1).AddFrame(1, 1).AddFrame(1, 1).AddFrame(1, 1).AddFrame(1, 1).AddFrame(1, 1).AddFrame(1, 1)
testGame(game, shouldBeValue, shouldBeString)
fmt.Println("Finished")
}