RosettaCodeData/Task/Mastermind/Go/mastermind.go

189 lines
4.2 KiB
Go

package main
import (
"errors"
"flag"
"fmt"
"log"
"math/rand"
"strings"
"time"
)
func main() {
log.SetPrefix("mastermind: ")
log.SetFlags(0)
colours := flag.Int("colours", 6, "number of colours to use (2-20)")
flag.IntVar(colours, "colors", 6, "alias for colours")
holes := flag.Int("holes", 4, "number of holes (the code length, 4-10)")
guesses := flag.Int("guesses", 12, "number of guesses allowed (7-20)")
unique := flag.Bool("unique", false, "disallow duplicate colours in the code")
flag.Parse()
rand.Seed(time.Now().UnixNano())
m, err := NewMastermind(*colours, *holes, *guesses, *unique)
if err != nil {
log.Fatal(err)
}
err = m.Play()
if err != nil {
log.Fatal(err)
}
}
type mastermind struct {
colours int
holes int
guesses int
unique bool
code string
past []string // history of guesses
scores []string // history of scores
}
func NewMastermind(colours, holes, guesses int, unique bool) (*mastermind, error) {
if colours < 2 || colours > 20 {
return nil, errors.New("colours must be between 2 and 20 inclusive")
}
if holes < 4 || holes > 10 {
return nil, errors.New("holes must be between 4 and 10 inclusive")
}
if guesses < 7 || guesses > 20 {
return nil, errors.New("guesses must be between 7 and 20 inclusive")
}
if unique && holes > colours {
return nil, errors.New("holes must be > colours when using unique")
}
return &mastermind{
colours: colours,
holes: holes,
guesses: guesses,
unique: unique,
past: make([]string, 0, guesses),
scores: make([]string, 0, guesses),
}, nil
}
func (m *mastermind) Play() error {
m.generateCode()
fmt.Printf("A set of %s has been selected as the code.\n", m.describeCode(m.unique))
fmt.Printf("You have %d guesses.\n", m.guesses)
for len(m.past) < m.guesses {
guess, err := m.inputGuess()
if err != nil {
return err
}
fmt.Println()
m.past = append(m.past, guess)
str, won := m.scoreString(m.score(guess))
if won {
plural := "es"
if len(m.past) == 1 {
plural = ""
}
fmt.Printf("You found the code in %d guess%s.\n", len(m.past), plural)
return nil
}
m.scores = append(m.scores, str)
m.printHistory()
fmt.Println()
}
fmt.Printf("You are out of guesses. The code was %s.\n", m.code)
return nil
}
const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
const blacks = "XXXXXXXXXX"
const whites = "OOOOOOOOOO"
const nones = "----------"
func (m *mastermind) describeCode(unique bool) string {
ustr := ""
if unique {
ustr = " unique"
}
return fmt.Sprintf("%d%s letters (from 'A' to %q)",
m.holes, ustr, charset[m.colours-1],
)
}
func (m *mastermind) printHistory() {
for i, g := range m.past {
fmt.Printf("-----%s---%[1]s--\n", nones[:m.holes])
fmt.Printf("%2d: %s : %s\n", i+1, g, m.scores[i])
}
}
func (m *mastermind) generateCode() {
code := make([]byte, m.holes)
if m.unique {
p := rand.Perm(m.colours)
for i := range code {
code[i] = charset[p[i]]
}
} else {
for i := range code {
code[i] = charset[rand.Intn(m.colours)]
}
}
m.code = string(code)
//log.Printf("code is %q", m.code)
}
func (m *mastermind) inputGuess() (string, error) {
var input string
for {
fmt.Printf("Enter guess #%d: ", len(m.past)+1)
if _, err := fmt.Scanln(&input); err != nil {
return "", err
}
input = strings.ToUpper(strings.TrimSpace(input))
if m.validGuess(input) {
return input, nil
}
fmt.Printf("A guess must consist of %s.\n", m.describeCode(false))
}
}
func (m *mastermind) validGuess(input string) bool {
if len(input) != m.holes {
return false
}
for i := 0; i < len(input); i++ {
c := input[i]
if c < 'A' || c > charset[m.colours-1] {
return false
}
}
return true
}
func (m *mastermind) score(guess string) (black, white int) {
scored := make([]bool, m.holes)
for i := 0; i < len(guess); i++ {
if guess[i] == m.code[i] {
black++
scored[i] = true
}
}
for i := 0; i < len(guess); i++ {
if guess[i] == m.code[i] {
continue
}
for j := 0; j < len(m.code); j++ {
if i != j && !scored[j] && guess[i] == m.code[j] {
white++
scored[j] = true
}
}
}
return
}
func (m *mastermind) scoreString(black, white int) (string, bool) {
none := m.holes - black - white
return blacks[:black] + whites[:white] + nones[:none], black == m.holes
}