189 lines
4.2 KiB
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
|
|
}
|