From 7a882ea31779f99105b243d108aa0718ee36dec5 Mon Sep 17 00:00:00 2001 From: Troy Campbell Date: Wed, 19 Oct 2022 12:21:29 +1000 Subject: [PATCH] Added go version of Batnum --- 00_Alternate_Languages/08_Batnum/go/main.go | 248 ++++++++++++++++++++ 1 file changed, 248 insertions(+) create mode 100644 00_Alternate_Languages/08_Batnum/go/main.go diff --git a/00_Alternate_Languages/08_Batnum/go/main.go b/00_Alternate_Languages/08_Batnum/go/main.go new file mode 100644 index 00000000..10334799 --- /dev/null +++ b/00_Alternate_Languages/08_Batnum/go/main.go @@ -0,0 +1,248 @@ +package main + +import ( + "bufio" + "fmt" + "os" + "strconv" + "strings" +) + +type StartOption int8 + +const ( + StartUndefined StartOption = iota + ComputerFirst + PlayerFirst +) + +type WinOption int8 + +const ( + WinUndefined WinOption = iota + TakeLast + AvoidLast +) + +type GameOptions struct { + pileSize int + winOption WinOption + startOption StartOption + minSelect int + maxSelect int +} + +func NewOptions() *GameOptions { + g := GameOptions{} + + g.pileSize = getPileSize() + if g.pileSize < 0 { + return &g + } + + g.winOption = getWinOption() + g.minSelect, g.maxSelect = getMinMax() + g.startOption = getStartOption() + + return &g +} + +func getPileSize() int { + ps := 0 + var err error + scanner := bufio.NewScanner(os.Stdin) + + for { + fmt.Println("Enter Pile Size ") + scanner.Scan() + ps, err = strconv.Atoi(scanner.Text()) + if err == nil { + break + } + } + return ps +} + +func getWinOption() WinOption { + scanner := bufio.NewScanner(os.Stdin) + + for { + fmt.Println("ENTER WIN OPTION - 1 TO TAKE LAST, 2 TO AVOID LAST:") + scanner.Scan() + w, err := strconv.Atoi(scanner.Text()) + if err == nil && (w == 1 || w == 2) { + return WinOption(w) + } + } +} + +func getStartOption() StartOption { + scanner := bufio.NewScanner(os.Stdin) + + for { + fmt.Println("ENTER START OPTION - 1 COMPUTER FIRST, 2 YOU FIRST ") + scanner.Scan() + s, err := strconv.Atoi(scanner.Text()) + if err == nil && (s == 1 || s == 2) { + return StartOption(s) + } + } +} + +func getMinMax() (int, int) { + minSelect := 0 + maxSelect := 0 + var minErr error + var maxErr error + scanner := bufio.NewScanner(os.Stdin) + + for { + fmt.Println("ENTER MIN AND MAX ") + scanner.Scan() + enteredValues := scanner.Text() + vals := strings.Split(enteredValues, " ") + minSelect, minErr = strconv.Atoi(vals[0]) + maxSelect, maxErr = strconv.Atoi(vals[1]) + if (minErr == nil) && (maxErr == nil) && (minSelect > 0) && (maxSelect > 0) && (maxSelect > minSelect) { + return minSelect, maxSelect + } + } +} + +// This handles the player's turn - asking the player how many objects +// to take and doing some basic validation around that input. Then it +// checks for any win conditions. +// Returns a boolean indicating whether the game is over and the new pile_size. +func playerMove(pile, min, max int, win WinOption) (bool, int) { + scanner := bufio.NewScanner(os.Stdin) + done := false + for !done { + fmt.Println("YOUR MOVE") + scanner.Scan() + m, err := strconv.Atoi(scanner.Text()) + if err != nil { + continue + } + + if m == 0 { + fmt.Println("I TOLD YOU NOT TO USE ZERO! COMPUTER WINS BY FORFEIT.") + return true, pile + } + + if m > max || m < min { + fmt.Println("ILLEGAL MOVE, REENTER IT") + continue + } + + pile -= m + done = true + + if pile <= 0 { + if win == AvoidLast { + fmt.Println("TOUGH LUCK, YOU LOSE.") + } else { + fmt.Println("CONGRATULATIONS, YOU WIN.") + } + return true, pile + } + } + return false, pile +} + +// This handles the logic to determine how many objects the computer +// will select on its turn. +func computerPick(pile, min, max int, win WinOption) int { + var q int + if win == AvoidLast { + q = pile - 1 + } else { + q = pile + } + c := min + max + + pick := q - (c * int(q/c)) + + if pick < min { + pick = min + } else if pick > max { + pick = max + } + + return pick +} + +// This handles the computer's turn - first checking for the various +// win/lose conditions and then calculating how many objects +// the computer will take. +// Returns a boolean indicating whether the game is over and the new pile_size. +func computerMove(pile, min, max int, win WinOption) (bool, int) { + // first check for end-game conditions + if win == TakeLast && pile <= max { + fmt.Printf("COMPUTER TAKES %d AND WINS\n", pile) + return true, pile + } + + if win == AvoidLast && pile <= min { + fmt.Printf("COMPUTER TAKES %d AND LOSES\n", pile) + return true, pile + } + + // otherwise determine the computer's selection + selection := computerPick(pile, min, max, win) + pile -= selection + fmt.Printf("COMPUTER TAKES %d AND LEAVES %d\n", selection, pile) + return false, pile +} + +// This is the main game loop - repeating each turn until one +// of the win/lose conditions is met. +func play(pile, min, max int, start StartOption, win WinOption) { + gameOver := false + playersTurn := (start == PlayerFirst) + + for !gameOver { + if playersTurn { + gameOver, pile = playerMove(pile, min, max, win) + playersTurn = false + if gameOver { + return + } + } + + if !playersTurn { + gameOver, pile = computerMove(pile, min, max, win) + playersTurn = true + } + } +} + +// Print out the introduction and rules of the game +func printIntro() { + fmt.Printf("%33s%s\n", " ", "BATNUM") + fmt.Printf("%15s%s\n", " ", "CREATIVE COMPUTING MORRISSTOWN, NEW JERSEY") + fmt.Printf("\n\n\n") + fmt.Println("THIS PROGRAM IS A 'BATTLE OF NUMBERS' GAME, WHERE THE") + fmt.Println("COMPUTER IS YOUR OPPONENT.") + fmt.Println() + fmt.Println("THE GAME STARTS WITH AN ASSUMED PILE OF OBJECTS. YOU") + fmt.Println("AND YOUR OPPONENT ALTERNATELY REMOVE OBJECTS FROM THE PILE.") + fmt.Println("WINNING IS DEFINED IN ADVANCE AS TAKING THE LAST OBJECT OR") + fmt.Println("NOT. YOU CAN ALSO SPECIFY SOME OTHER BEGINNING CONDITIONS.") + fmt.Println("DON'T USE ZERO, HOWEVER, IN PLAYING THE GAME.") + fmt.Println("ENTER A NEGATIVE NUMBER FOR NEW PILE SIZE TO STOP PLAYING.") + fmt.Println() +} + +func main() { + for { + printIntro() + + g := NewOptions() + + if g.pileSize < 0 { + return + } + + play(g.pileSize, g.minSelect, g.maxSelect, g.startOption, g.winOption) + } +}