Task: Mastermind




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 
	scores []string 
}

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)
	
}

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
}








Ada: with Ada.Text_IO;
with Ada.Numerics.Discrete_Random;
with Ada.Strings.Fixed;
with Ada.Containers.Ordered_Sets;

use Ada.Strings.Fixed;

procedure MasterMind
is
   subtype Color_Number is Positive range 2 .. 20;
   subtype Code_Size is Positive range 4 .. 10;
   subtype Guesses_Number is Positive range 7 .. 20;
   subtype Color is Character range 'A' .. 'T';

   function Hint(correct, guess : in String) return String
   is
      Xs : Natural := 0;
      Os : Natural := 0;
      to_display : String(1 .. correct'Length) := (others => '-');
   begin
      for I in guess'Range loop
         if guess(I) = correct(I) then
            Xs := Xs + 1;
            to_display(I) := 'X';
         end if;
      end loop;
      for I in guess'Range loop
         if to_display(I) = '-' then
            for J in correct'Range loop
               if J /= I and to_display(J) /= 'X' and correct(J) = guess(I) then
                  Os := Os + 1;
                  exit;
               end if;
            end loop;
         end if;
      end loop;
      return Xs * 'X' & Os * 'O' & (guess'Length - Xs - Os) * '-';
   end Hint;

   generic
      type Data is (<>);
   function Input(message : in String) return Data;
   
   

   function Input(message : in String) return Data is
   begin
      loop
         Ada.Text_IO.Put(message);
         declare
            S : constant String := Ada.Text_IO.Get_Line;
         begin
            return Data'Value(S);
         exception
            when Constraint_Error =>
               Ada.Text_IO.New_Line;
               Ada.Text_IO.Put_Line("Invalid input!");
               Ada.Text_IO.Put_Line
                 ("Expected values in range:"
                  & Data'First'Img & " .." & Data'Last'Img);
               Ada.Text_IO.New_Line;
         end;
      end loop;
   end;

   function Input_Color_Number is new Input(Color_Number);
   function Input_Code_Size is new Input(Code_Size);
   function Input_Guesses_Number is new Input(Guesses_Number);
   function Input_Boolean is new Input(Boolean);

   CN : constant Color_Number := Input_Color_Number("How many colors? ");
   GN : constant Guesses_Number := Input_Guesses_Number("How many guesses? ");
   CS : constant Code_Size := Input_Code_Size("Size of the code? ");
   repeats : Boolean := Input_Boolean("With repeats? ");
   

   subtype Actual_Colors is Color range Color'First .. Color'Val(Color'Pos(Color'First) + CN - 1);
   package Actual_Colors_Sets is new Ada.Containers.Ordered_Sets(Element_Type => Actual_Colors);

   package Color_Random is new Ada.Numerics.Discrete_Random(Result_Subtype => Actual_Colors);
   generator : Color_Random.Generator;

   function Random return String
   is
      C : String(1 .. CS);
      seen : Actual_Colors_Sets.Set;
   begin
      for I in C'Range loop
         C(I) := Color_Random.Random(generator);
         while (not repeats) and seen.Contains(C(I)) loop
            C(I) := Color_Random.Random(generator);
         end loop;
         seen.Include(C(I));
      end loop;
      return C;
   end Random;

   function Get_Code return String is
   begin
      loop
         Ada.Text_IO.Put("> ");
         declare
            input : constant String := Ada.Text_IO.Get_Line;
         begin
            if input'Length /= CS then
               raise Constraint_Error;
            end if;
            for C of input loop
               if C not in Actual_Colors then
                  raise Constraint_Error;
               end if;
            end loop;
            return input;
         exception
            when Constraint_Error =>
               Ada.Text_IO.New_Line;
               Ada.Text_IO.Put_Line("Invalid input!");
               Ada.Text_IO.New_Line;
         end;
      end loop;
   end Get_Code;

   found : Boolean := False;
begin
   if (not repeats) and (CN < CS) then
      Ada.Text_IO.Put_Line("Not enough colors! Using repeats anyway.");
      repeats := True;
   end if;

   Color_Random.Reset(generator);

   declare
      answer : constant String := Random;
      previous : array(1 .. GN) of String(1 .. CS*2);
   begin
      for I in 1 .. GN loop
         declare
            guess : constant String := Get_Code;
         begin
            if guess = answer then
               Ada.Text_IO.Put_Line("You won, congratulations!");
               found := True;
            else
               previous(I) := guess & Hint(answer, guess);
               Ada.Text_IO.Put_Line(44 * '-');
               for J in 1 .. I loop
                  Ada.Text_IO.Put_Line
                    (previous(J)(1 .. CS)
                     & " => " & previous(J)(CS+1 .. previous(J)'Last));
               end loop;
               Ada.Text_IO.Put_Line(44 * '-');
               Ada.Text_IO.New_Line;
            end if;
         end;
         exit when found;
      end loop;
      if not found then
         Ada.Text_IO.Put_Line("You lost, sorry! The answer was: " & answer);
      end if;
   end;
end MasterMind;