Create a simple version of the board game: Mastermind.

Mastermind is a draft programming task. It is not yet considered ready to be promoted as a complete task, for reasons that should be found in its talk page.

It must be possible to:

  • choose the number of colors will be used in the game(2 - 20)
  • choose the color code length(4 - 10)
  • choose the maximum number of guesses the player has (7 - 20)
  • choose whether or not will be repeated colors in the code


The game should display all the player guesses and the results of that guess.

Display(just an idea.):

Feature Graphic Version Text Version
Player guess Colored circles Alphabet letters
Correct color & position Black circle X
Correct color White circle O
None Gray circle -

A text version example: 1: ADEF - XXO-
Translates to:
first guess;
the four colors(ADEF);
result: two correct colors and spot, one correct color/wrong spot one color is not in the code.

Happy coding!


Related tasks



C++

<lang cpp>#include <iostream>

  1. include <algorithm>
  2. include <ctime>
  3. include <string>
  4. include <vector>

typedef std::vector<char> vecChar;

class master { public:

   master( size_t code_len, size_t clr_count, size_t guess_count, bool rpt ) {
       std::string color = "ABCDEFGHIJKLMNOPQRST";
       if( code_len < 4 ) code_len = 4; else if( code_len > 10 ) code_len = 10;
       if( !rpt && clr_count < code_len ) clr_count = code_len; 
       if( clr_count < 2 ) clr_count = 2; else if( clr_count > 20 ) clr_count = 20;
       if( guess_count < 7 ) guess_count = 7; else if( guess_count > 20 ) guess_count = 20;
       
       codeLen = code_len; colorsCnt = clr_count; guessCnt = guess_count; repeatClr = rpt;
       for( size_t s = 0; s < colorsCnt; s++ ) {
           colors.append( 1, color.at( s ) );
       }
   }
   void play() {
       bool win = false;
       combo = getCombo();
       while( guessCnt ) {
           showBoard();
           if( checkInput( getInput() ) ) {
               win = true;
               break;
           }
           guessCnt--;
       }
       if( win ) {
           std::cout << "\n\n--------------------------------\n" <<
               "Very well done!\nYou found the code: " << combo <<
               "\n--------------------------------\n\n";
       } else {
           std::cout << "\n\n--------------------------------\n" <<
               "I am sorry, you couldn't make it!\nThe code was: " << combo <<
               "\n--------------------------------\n\n";
       }
   }

private:

   void showBoard() {
       vecChar::iterator y;
       for( int x = 0; x < guesses.size(); x++ ) {
           std::cout << "\n--------------------------------\n";
           std::cout << x + 1 << ": ";
           for( y = guesses[x].begin(); y != guesses[x].end(); y++ ) {
               std::cout << *y << " ";
           }
           std::cout << " :  ";
           for( y = results[x].begin(); y != results[x].end(); y++ ) {
               std::cout << *y << " ";
           }
           int z = codeLen - results[x].size();
           if( z > 0 ) {
               for( int x = 0; x < z; x++ ) std::cout << "- ";
           }
       }
       std::cout << "\n\n";
   }
   std::string getInput() {
       std::string a;
       while( true ) {
           std::cout << "Enter your guess (" << colors << "): ";
           a = ""; std::cin >> a;
           std::transform( a.begin(), a.end(), a.begin(), ::toupper );
           if( a.length() > codeLen ) a.erase( codeLen );
           bool r = true;
           for( std::string::iterator x = a.begin(); x != a.end(); x++ ) {
               if( colors.find( *x ) == std::string::npos ) {
                   r = false;
                   break;
               }
           }
           if( r ) break;
       }
       return a;
   }
   bool checkInput( std::string a ) {
       vecChar g;
       for( std::string::iterator x = a.begin(); x != a.end(); x++ ) {
           g.push_back( *x );
       }
       guesses.push_back( g );
       
       int black = 0, white = 0;
       std::vector<bool> gmatch( codeLen, false );
       std::vector<bool> cmatch( codeLen, false );

       for( int i = 0; i < codeLen; i++ ) {
           if( a.at( i ) == combo.at( i ) ) {
               gmatch[i] = true;
               cmatch[i] = true;
               black++;
           }
       }

       for( int i = 0; i < codeLen; i++ ) {
           if (gmatch[i]) continue;
           for( int j = 0; j < codeLen; j++ ) {
               if (i == j || cmatch[j]) continue;
               if( a.at( i ) == combo.at( j ) ) {
                   cmatch[j] = true;
                   white++;
                   break;
               }
           }
       }
      
       vecChar r;
       for( int b = 0; b < black; b++ ) r.push_back( 'X' );
       for( int w = 0; w < white; w++ ) r.push_back( 'O' );
       results.push_back( r );
       return ( black == codeLen );
   }
   std::string getCombo() {
       std::string c, clr = colors;
       int l, z;
       for( size_t s = 0; s < codeLen; s++ ) {
           z = rand() % ( int )clr.length();
           c.append( 1, clr[z] );
           if( !repeatClr ) clr.erase( z, 1 );
       }
       return c;
   }
   size_t codeLen, colorsCnt, guessCnt;
   bool repeatClr;
   std::vector<vecChar> guesses, results;
   std::string colors, combo;

};

int main( int argc, char* argv[] ) {

   srand( unsigned( time( 0 ) ) );
   master m( 4, 8, 12, false );
   m.play();
   return 0;

} </lang>

Output:
Enter your guess (ABCDEFGH): gbda

--------------------------------
1: A B C D  :  X O O -
--------------------------------
2: A A A E  :  O - - -
--------------------------------
3: E E E E  :  - - - -
--------------------------------
4: B B B C  :  X - - -
--------------------------------
5: D B A F  :  X O O -
--------------------------------
6: G B D A  :  X X X -

Enter your guess (ABCDEFGH): hbda


--------------------------------
Very well done!
You found the code: HBDA
--------------------------------

Easyprog.online

Run it

<lang>col[] = [ 922 990 171 229 950 808 ] len code[] 4 len guess[] 4

subr init_vars

 row = 7

. func draw_rate r black white . .

 for j range 2
   for c range 2
     move c * 3 + 67.5 r * 11 + 12.6 + j * 3
     if black > 0
       color 000
       circle 1.2
       black -= 1
     elif white > 0
       color 999
       circle 1.2
       white -= 1
     else
       color 420
       circle 0.7
     .
   .
 .

. func show_code . .

 color 642
 move 27 0
 rect 46 8
 for i range 4
   move i * 8 + 33 3
   color col[code[i]]
   circle 2
 .

. func next_row . .

 for i range 4
   guess[i] = -1
 .
 for c range 4
   move c * 9 + 32 row * 11 + 14
   color 900
   circle 2
   color 420
   circle 1.7
 .

. func rate . .

 test[] = code[]
 for i range 4
   if guess[i] = test[i]
     black += 1
     test[i] = -2
     guess[i] = -1
   .
 .
 for i range 4
   for j range 4
     if guess[i] = test[j]
       white += 1
       test[j] = -2
       guess[i] = -1
     .
   .
 .
 move 64 row * 11 + 11
 color 642
 rect 9 6
 # 
 call draw_rate row black white
 row -= 1
 if black = 4
   row = -1
 .
 if row = -1
   call show_code
 else
   call next_row
 .

. func new . .

 call init_vars
 for i range 4
   code[i] = random 6
 .
 color 642
 move 15 0
 rect 70 100
 color 420
 move 27 0
 rect 46 8
 move 35 2
 color 864
 text "Mastermind"
 color 420
 move 25 10
 line 25 94
 move 75 10
 line 75 94
 for r range 8
   for c range 4
     move c * 9 + 32 r * 11 + 14
     circle 2
   .
   call draw_rate r 0 0
 .
 call next_row

.

func do_move . .

 c = trunc ((mouse_x - 27.5) / 9)
 move c * 9 + 32 row * 11 + 14
 guess[c] = (guess[c] + 1) mod 6
 color col[guess[c]]
 circle 3.2
 # 
 if guess[0] > -1 and guess[1] > -1 and guess[2] > -1 and guess[3] > -1
   move 64 row * 11 + 11
   color 900
   rect 8.6 6
   move 64.2 row * 11 + 11.2
   color 642
   rect 8.2 5.6
   color 420
   move 64.6 row * 11 + 11.6
   text "OK"
 .

. on mouse_down

 if row = -1
   call new
 elif mouse_y > row * 11 + 9 and mouse_y < row * 11 + 19
   if mouse_x > 28 and mouse_x < 63.5
     call do_move
   elif mouse_x > 65 and mouse_x < 77
     if guess[0] > -1 and guess[1] > -1 and guess[2] > -1 and guess[3] > -1
       call rate
     .
   .
 .

. textsize 5 linewidth 0.8 call new</lang>

Go

<lang 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 }</lang>

Output (using Knuth's five-guess algorithm):
A set of 4 letters (from 'A' to 'F') has been selected as the code.
You have 12 guesses.
[... output of first guesses omitted ...]
Enter guess #4: FEDE

------------------
 1:  AABB : ----
------------------
 2:  FFED : XOOO
------------------
 3:  DFDF : XO--
------------------
 4:  FEDE : XOOO

Enter guess #5: EFFE

You found the code in 5 guesses.

Kotlin

Translation of: C++

<lang scala>// version 1.2.51

import java.util.Random

val rand = Random()

class Mastermind {

   private val codeLen: Int
   private val colorsCnt: Int
   private var guessCnt = 0
   private val repeatClr: Boolean
   private val colors: String
   private var combo = ""
   private val guesses = mutableListOf<CharArray>()
   private val results = mutableListOf<CharArray>()
   constructor(codeLen: Int, colorsCnt: Int, guessCnt: Int, repeatClr: Boolean) {
       val color = "ABCDEFGHIJKLMNOPQRST"
       this.codeLen = codeLen.coerceIn(4, 10)
       var cl = colorsCnt
       if (!repeatClr && cl < this.codeLen) cl = this.codeLen
       this.colorsCnt = cl.coerceIn(2, 20)       
       this.guessCnt = guessCnt.coerceIn(7, 20)    
       this.repeatClr = repeatClr
       this.colors = color.take(this.colorsCnt)
   }
   fun play() {
       var win = false
       combo = getCombo()
       while (guessCnt != 0) {
           showBoard()
           if (checkInput(getInput())) {
               win = true
               break
           }
           guessCnt--
       }
       println("\n\n--------------------------------")
       if (win) {
           println("Very well done!\nYou found the code: $combo")
       }
       else {
           println("I am sorry, you couldn't make it!\nThe code was: $combo")
       }
       println("--------------------------------\n")
   }
   private fun showBoard() {
       for (x in 0 until guesses.size) {
           println("\n--------------------------------")
           print("${x + 1}: ")
           for (y in guesses[x]) print("$y ")
           print(" :  ")
           for (y in results[x]) print("$y ")
           val z = codeLen - results[x].size
           if (z > 0) print("- ".repeat(z))
       }
       println("\n")
   }
   private fun getInput(): String {
       while (true) {
           print("Enter your guess ($colors): ")
           val a = readLine()!!.toUpperCase().take(codeLen)          
           if (a.all { it in colors } ) return a
       }
   }
   private fun checkInput(a: String): Boolean {
       guesses.add(a.toCharArray())
       var black = 0
       var white = 0
       val gmatch = BooleanArray(codeLen)
       val cmatch = BooleanArray(codeLen)
       for (i in 0 until codeLen) {
           if (a[i] == combo[i]) {
               gmatch[i] = true
               cmatch[i] = true
               black++
           }
       }
       for (i in 0 until codeLen) {
           if (gmatch[i]) continue
           for (j in 0 until codeLen) {
               if (i == j || cmatch[j]) continue
               if (a[i] == combo[j]) {
                   cmatch[j] = true
                   white++
                   break
               }
           }
       }   
       val r = mutableListOf<Char>()
       r.addAll("X".repeat(black).toList())
       r.addAll("O".repeat(white).toList())    
       results.add(r.toCharArray())
       return black == codeLen
   }
   private fun getCombo(): String {
       val c =  StringBuilder()
       val clr = StringBuilder(colors)
       for (s in 0 until codeLen) {
           val z = rand.nextInt(clr.length)
           c.append(clr[z])
           if (!repeatClr) clr.deleteCharAt(z)
       }
       return c.toString()
   }

}

fun main(args: Array<String>) {

   val m = Mastermind(4, 8, 12, false)
   m.play()

}</lang>

Sample input/output (showing last 2 guesses only):

Enter your guess (ABCDEFGH): hcgb

--------------------------------
1: A B C D  :  O - - - 
--------------------------------
2: E F G H  :  X O O - 
--------------------------------
3: E G F A  :  O O - - 
--------------------------------
4: G F E A  :  O O - - 
--------------------------------
5: F H G B  :  X X O - 
--------------------------------
6: F B G C  :  X O - - 
--------------------------------
7: F G A B  :  X O - - 
--------------------------------
8: A H G C  :  X O - - 
--------------------------------
9: G H D B  :  X O O - 
--------------------------------
10: H A G B  :  X X X - 
--------------------------------
11: H C G B  :  X X X - 

Enter your guess (ABCDEFGH): hegb


--------------------------------
Very well done!
You found the code: HEGB
--------------------------------

Lua

Based on C++ <lang lua> math.randomseed( os.time() ) local black, white, none, code = "X", "O", "-" local colors, codeLen, maxGuess, rept, alpha, opt = 6, 4, 10, false, "ABCDEFGHIJKLMNOPQRST", "" local guesses, results function createCode()

   code = ""
   local dic, a = ""
   for i = 1, colors do
       dic = dic .. alpha:sub( i, i )
   end
   for i = 1, codeLen do
       a = math.floor( math.random( 1, #dic ) )
       code = code .. dic:sub( a, a )
       if not rept then
           dic = dic:sub(1, a - 1 ) .. dic:sub( a + 1, #dic )
       end
   end

end function checkInput( inp )

   table.insert( guesses, inp )
   local b, w, fnd, str = 0, 0, {}, ""
   for bl = 1, codeLen do
       if inp:sub( bl, bl ) == code:sub( bl, bl ) then
           b = b + 1; fnd[bl] = true
       else
           for wh = 1, codeLen do
               if nil == fnd[bl] and wh ~= bl and inp:sub( wh, wh ) == code:sub( bl, bl ) then
                   w = w + 1; fnd[bl] = true
               end
           end
       end
   end
   for i = 1, b do str = str .. string.format( "%s ", black ) end
   for i = 1, w do str = str .. string.format( "%s ", white ) end
   for i = 1, 2 * codeLen - #str, 2 do str = str .. string.format( "%s ", none ) end
   table.insert( results, str )
   return b == codeLen

end function play()

   local err, win, r = true, false;
   for j = 1, colors do opt = opt .. alpha:sub( j, j ) end
   while( true ) do
       createCode(); guesses, results = {}, {}
       for i = 1, maxGuess do
           err = true;
           while( err ) do
               io.write( string.format( "\n-------------------------------\nYour guess (%s)?", opt ) )
               inp = io.read():upper(); 
               if #inp == codeLen then
                   err = false;
                   for k = 1, #inp do
                       if( nil == opt:find( inp:sub( k, k ) ) ) then 
                           err = true;
                           break;
                       end
                   end
               end
           end
           if( checkInput( inp ) ) then win = true; break
           else
               for l = 1, #guesses do
                   print( string.format( "%.2d: %s : %s", l, guesses[l], results[l] ) )
               end
           end
       end
       if win then print( "\nWell done!" )
       else print( string.format( "\nSorry, you did not crack the code --> %s!", code ) )
       end
       io.write( "Play again( Y/N )? " ); r = io.read()
       if r ~= "Y" and r ~= "y" then break end
   end

end --entry point --- if arg[1] ~= nil and tonumber( arg[1] ) > 1 and tonumber( arg[1] ) < 21 then colors = tonumber( arg[1] ) end if arg[2] ~= nil and tonumber( arg[2] ) > 3 and tonumber( arg[2] ) < 11 then codeLen = tonumber( arg[2] ) end if arg[3] ~= nil and tonumber( arg[3] ) > 6 and tonumber( arg[3] ) < 21 then maxGuess = tonumber( arg[3] ) end if arg[4] ~= nil and arg[4] == "true" or arg[4] == "false" then rept = ( arg[4] == "true" ) end play() </lang>

Output:
-------------------------------
Your guess (ABCDEF)?bcde
01: BCDE : X X - -

-------------------------------
Your guess (ABCDEF)?bcaf
01: BCDE : X X - -
02: BCAF : X O O -

-------------------------------
Your guess (ABCDEF)?badf
01: BCDE : X X - -
02: BCAF : X O O -
03: BADF : X X O -

-------------------------------
Your guess (ABCDEF)?bafe

Well done!
Play again( Y/N )?

Perl

Translation of: Perl 6

<lang perl>use List::Util qw(any);

print 'Enter pool size, puzzle size, attempts allowed: '; ($pool,$length,$tries) = split /\s+/, <>; $length = 4 if $length eq or $length < 3 or $length > 11; $pool = 6 if $pool eq or $pool < 2 or $pool > 21; $tries = 10 if $tries eq or $tries < 7 or $tries > 21;

@valid = sort { -1 + 2*int(rand 2) } ('A' .. 'T')[0..$pool-1]; @puzzle = @valid[0..$length-1];

$black = '●'; $white = '○';

while () {

   header();
   print "$_\n" for @guesses;
   lose() if  @guesses == $tries;
   @guess = get_guess();
   next unless is_valid(@guess);
   $score = score(\@puzzle, \@guess);
   win() if $score eq join ' ', ($black) x $length;
   push @guesses, join(' ', @guess) . ' :: ' . $score;

}

sub score {

   local *puzzle = shift;
   local *guess  = shift;
   my @score;
   for $i (0..$length-1) {
       if    (     $puzzle[$i] eq $guess[$i]) { push @score, $black }
       elsif (any {$puzzle[$i] eq $_} @guess) { push @score, $white }
       else                                   { push @score, '-'    }
   }
   join ' ', reverse sort @score;

}

sub header {

   $num = $tries - @guesses;
   print  "Valid letter, but wrong position: ○ - Correct letter and position: ●\n";
   print  "Guess the $length element sequence containing the letters " . join(', ', sort @valid) . "\n";
   printf "Repeats are not allowed. You have $num guess%s remaining\n", $num > 1 ? 'es' : ;

}

sub get_guess { print 'Your guess?: '; $g = <>; return split /\s*/, uc $g }

sub is_valid { $length == @_ }

sub win { print 'You win! The correct answer is: ' . join(' ',@puzzle) . "\n"; exit }

sub lose { print 'Too bad, you ran out of guesses. The solution was: ' . join(' ',@puzzle) . "\n"; exit } </lang> Sample output, omitting redundant instructions.

Output:
Valid letter, but wrong position: ○ - Correct letter and position: ●
Guess the 4 element sequence containing the letters A, B, C, D, E, F
Repeats are not allowed. You have 10 guesses remaining
Your guess?: a b c e
A B C E :: ○ ○ ○ -
Your guess?: b a d c
Repeats are not allowed. You have 8 guesses remaining
A B C E :: ○ ○ ○ -
B A D C :: ● ● ○ ○
Your guess?: a b c d
Repeats are not allowed. You have 7 guesses remaining
A B C E :: ○ ○ ○ -
B A D C :: ● ● ○ ○
A B C D :: ○ ○ ○ ○
Your guess?: b d a c
You win! The correct answer is: B D A C

Perl 6

Works with: Rakudo version 2017.01

By default, plays classic Mastermind using letters in place of colors. ( 4 chosen from 6, no repeats, 10 guess limit. ) Pass in parameters to modify the game. Enter a string of --length (default 4) letters with or without spaces. Guesses accept lower or upper case. <lang perl6>sub MAIN (

   Int :$colors  where 1 < * < 21 = 6,  Int :$length  where 3 < * < 11 = 4,
   Int :$guesses where 7 < * < 21 = 10, Bool :$repeat = False
 ) {
   my @valid = ('A' .. 'T')[^$colors];
   my $puzzle = $repeat ?? @valid.roll($length) !! @valid.pick($length);
   my @guesses;
   my $black = '●';
   my $white = '○';
   loop {
       clearscr();
       say header();
       printf " %{$length * 2}s :: %s\n", @guesses[$_][0], @guesses[$_][1] for ^@guesses;
       say ;
       lose() if @guesses == $guesses;
       my $guess = get-guess();
       next unless $guess.&is-valid;
       my $score = score($puzzle, $guess);
       win() if $score eq ($black xx $length).join: ' ';
       @guesses.push: [$guess, $score];
   }
   sub header {
       my $num = $guesses - @guesses;
       qq:to/END/;
       Valid letter, but wrong position: ○ - Correct letter and position: ●
       Guess the {$length} element sequence containing the letters {@valid}
       Repeats are {$repeat ??  !! 'not '}allowed. You have $num guess{ $num == 1 ??  !! 'es'} remaining.
       END
   }
   sub score ($puzzle, $guess) {
       my @score;
       for ^$length {
           if $puzzle[$_] eq $guess[$_] {
               @score.push: $black;
           }
           elsif $puzzle[$_] eq any(@$guess) {
               @score.push: $white;
           }
           else {
               @score.push('-');
           }
       }
       @score.sort.reverse.join: ' ';
   }
   sub clearscr { $*KERNEL ~~ /'win32'/ ?? run('cls') !! run('clear') }
   sub get-guess { (uc prompt 'Your guess?: ').comb(/@valid/) }
   sub is-valid (@guess) { so $length == @guess }
   sub win  { say 'You Win! The correct answer is: ', $puzzle; exit }
   sub lose { say 'Too bad, you ran out of guesses. The solution was: ', $puzzle; exit }

}</lang>

Sample output:
Valid letter, but wrong position: ○ - Correct letter and position: ●
Guess the 4 element sequence containing the letters A B C D E F
Repeats are not allowed. You have 5 guesses remaining.

  A B C D :: ○ ○ ○ -
  C A B E :: ● ○ ○ -
  D A E F :: ● ○ - -
  B A E C :: ● ○ ○ -
  D E B C :: ○ ○ ○ ○

Your guess?: cdeb
You Win! The correct answer is: (C D E B)

Phix

OTT GUI solution, fully configurable with solver. <lang Phix>-- demo/rosetta/mastermind.exw constant SET_LIMIT = 100_000 -- above this, it uses sampling.

constant help_text = """ The game of mastermind, with a Knuth solver.

Specify the number of colours (1..20), the code length (1..10), the number of guesses allowed (1-20), and whether colours can be repeated. The latter is automatically ticked when length>colours (and greyed-out inactive), since no codes could ever be generated without some repeats.

Note that at the highest settings there are 10,240,000,000,000 possible answers: the (order n squared) analysis of that is simply not practical, as indeed is simply building the initial list of all possible answers, and therefore a fixed limit of 100,000 has been applied, which also just about manages to keep the program responsive. I tried to replace excluded samples after each turn but at 90/99/99.9% failure rates that becomes pointless very quickly, and obviously if the actual answer is not in the 100,000 samples, it cannot possibly find it. You can always trim the search space back to something more reasonable at any time, and still play the game if that limit is breached, but with weaker hints.

Conversely the lowest settings do not make for an interesting game, but proved quite useful when ironing out some bugs, so were left in.

The Use button (disabled until something useful found) allows you to take the best found (so far), displayed at the top of the colours frame. Obviously "1/1 (100%)" means that it has deduced the correct answser. Below that the colours frame shows all available colours, which can be individually clicked in any order.

Press Delete or click on the last peg (in the left-hand game frame) to remove it, before the last one is placed, however once full your turn is immediately scored and cannot be undone.

New Game, Help, and Exit buttons are assumed to be self-explanatory. Changing the option settings implicitly triggers a new game, except for the number of permitted guesses (pre-game over). Reducing the number of guesses can also be used as a means of conceding.

When a correct guess is entered or all guesses have been used the hint and colours are replaced with "GAME OVER - YOU WIN/LOSE" under the actual answer. """

include pGUI.e

Ihandle dlg, colours, codelen, maxgoes, repeats, progress,

       usehint, game_canvas, colour_canvas

integer ncolours, ncodelen, nmaxgoes bool brepeats

sequence secret = {},

        hint = {},
        guesses = {{}},
        scores = {}

-- -- note: while the game is ongoing, length(guesses) should always be -- length(scores)+1; equal lengths is equivalent to game over. --

function get_score(sequence guess, goal) integer blacks = 0, -- (right colour & place)

       whites = 0  -- ("" but wrong place)
   for i=1 to length(guess) do
       if guess[i]=goal[i] then
           blacks += 1
           guess[i] = ' '
           goal[i] = ' '
       end if
   end for
   for i=1 to length(guess) do
       if guess[i]!=' ' then
           integer k = find(guess[i],goal)
           if k then
               whites += 1
               goal[k] = ' '
           end if
       end if
   end for
   return {blacks, whites}

end function

function random_set()

   sequence cset = tagset(ncolours),
            res = repeat(0,ncodelen)
   for i=1 to ncodelen do
       integer c = rand(length(cset))
       res[i] = cset[c]
       if not brepeats then
           cset[c..c] = {}
       end if
   end for
   return res

end function

sequence initial_set,

        excluded

atom is_len, -- logically length(initial_set), except when > SET_LIMIT.

    is_excluded

procedure create_initial_set()

   is_len = iff(brepeats?power(ncolours,ncodelen):k_perm(ncolours,ncodelen))
   if is_len<=SET_LIMIT then
       -- full set
       initial_set = repeat(0,is_len)
       excluded = repeat(false,is_len)
       is_excluded = 0
       sequence next = iff(brepeats?repeat(1,ncodelen):tagset(ncodelen))
       for i=1 to is_len do
           initial_set[i] = next
           for ndx=length(next) to 1 by -1 do
               integer n = next[ndx]
               while n<=ncolours do
                   n += 1
                   if brepeats 

-- or not find(n,next[1..ndx-1]) then

                   or not find(n,next) then --(see below)
                       exit
                   end if
               end while
               next[ndx] = n
               if n<=ncolours then
                   if not brepeats then
                       --
                       -- eg in the 4 colours and 4 holes case,
                       --   (start)       (above)       (this)
                       --  {1,2,3,4} --> {1,2,4,_} --> {1,2,4,3}
                       --  {1,2,4,3} --> {1,3,_,_} --> {1,3,2,4}
                       --  ...   (20 other cases omitted)
                       --  {4,3,1,2} --> {4,3,2,_} --> {4,3,2,1}
                       --
                       -- note this is probably sub-optimal, then
                       -- again insignificant vs. o(n^2) analysis
                       -- which is about to follow.
                       --
                       for j=ndx+1 to length(next) do
                           for k=1 to ncolours do

-- if not find(k,next[1..j-1]) then

                               if not find(k,next) then --(see below)
                                   next[j] = k
                                   exit
                               end if
                           end for
                       end for
                   end if
                   exit
               end if
               --
               -- technical note: if not brepeats, we are going to
               -- replace all next[ndx..$] later/above anyway, but
               -- replacing with 0 means we can avoid those slices.
               -- The next three all work: 1 is perfect for the
               -- brepeats=true case, but brepeats=false needs the 
               -- above slices, while the 2nd & 3rd are equivalent
               -- the latter is obviously somewhat faster, at the
               -- cost of a wtf?!, without a comment such as this.
               --

-- next[ndx] = 1 -- next[ndx] = iff(brepeats?1:0)

               next[ndx] = brepeats -- (equivalent)
           end for
       end for
   else
       --
       -- generate SET_LIMIT random codes
       -- note that if (as is quite likely) the actual answer is
       -- not present in initial_set, then obviously it cannot
       -- possibly find it!
       --
       initial_set = repeat(0,SET_LIMIT)
       excluded = repeat(false,SET_LIMIT)
       is_excluded = 0
       for i=1 to SET_LIMIT do
           initial_set[i] = random_set()
       end for
   end if

end procedure

atom done, is_done, best bool best_excluded

function idle_action()

   atom to_do = length(initial_set)-is_excluded,
        t1 = time()+1
   string samp = iff(is_len=length(initial_set)?"":sprintf(" samples of %,d",{is_len}))
   for i=1 to 100000 do    -- reasonable slice of work
       done += 1
       is_done += not excluded[done]
       sequence guest = initial_set[done],
                scores = {}, counts = {}
       if not find(guest,guesses) then
           for j=1 to length(initial_set) do
               if not excluded[j] then
                   sequence s = get_score(guest,initial_set[j])
                   integer k = find(s,scores)
                   if k=0 then
                       scores = append(scores,s)
                       counts = append(counts,1)
                   else
                       counts[k] += 1
                   end if
               end if
           end for
           if length(counts)=0 then
               IupSetStrAttribute(progress,"TITLE","[answer not in %,d%s]",{SET_LIMIT,samp})
               return IUP_IGNORE   -- (stop idle)
           end if
           integer k = largest(counts,return_index:=true),
                   ck = counts[k]
           if ck<best 
           or (ck=best and best_excluded>excluded[done]) then
               --          (^^ old was ^^^ new ain't ^^)
               --          aka old=true(1)>this=false(0)
               best = ck
               hint = guest
               best_excluded = excluded[done]
               IupSetInt(usehint,"ACTIVE",true)
               IupUpdate(colour_canvas)
           end if
       end if
   
       if done=length(initial_set) then
           IupSetStrAttribute(progress,"TITLE","%,d/%,d%s (100%%)",{is_done,to_do,samp})
           return IUP_IGNORE   -- (stop idle)
       end if
       if time()>t1 then exit end if
   end for
   IupSetStrAttribute(progress,"TITLE","%,d/%,d%s (%d%%)",{is_done,to_do,samp,100*(is_done/to_do)})
   return IUP_DEFAULT

end function constant idle_action_cb = Icallback("idle_action")

procedure start_idle()

   done = 0
   is_done = 0
   best = length(initial_set)+1
   IupSetGlobalFunction("IDLE_ACTION",idle_action_cb)

end procedure

procedure new_game()

   ncolours = IupGetInt(colours,"VALUE")
   ncodelen = IupGetInt(codelen,"VALUE")
   nmaxgoes = IupGetInt(maxgoes,"VALUE")
   brepeats = IupGetInt(repeats,"VALUE")
   secret = random_set()
   guesses = {{}}
   scores = {}
   hint = {}
   create_initial_set()
   start_idle()

end procedure

constant colour_table = {#e6194b, -- Red

                        #3cb44b,   -- Green
                        #ffe119,   -- Yellow
                        #4363d8,   -- Blue
                        #f58231,   -- Orange
                        #911eb4,   -- Purple
                        #42d4f4,   -- Cyan
                        #f032e6,   -- Magenta
                        #bfef45,   -- Lime
                        #fabebe,   -- Pink
                        #469990,   -- Teal
                        #e6beff,   -- Lavender
                        #9A6324,   -- Brown
                        #fffac8,   -- Beige
                        #800000,   -- Maroon
                        #aaffc3,   -- Mint
                        #808000,   -- Olive
                        #ffd8b1,   -- Apricot
                        #000075,   -- Navy
                        #a9a9a9}   -- Grey

-- saved in redraw_cb(), for click testing in button_cb(): sequence last_guess = {},

        colour_centres = {}

integer guess_r2 = 0,

       colour_r2 = 0

function redraw_cb(Ihandle ih, integer /*posx*/, integer /*posy*/)

   Ihandle frame = IupGetParent(ih)
   string title = IupGetAttribute(ih,"TITLE")
   if not find(title,{"Game","Colours"}) then ?9/0 end if
   integer {cw,ch} = IupGetIntInt(ih, "DRAWSIZE")
   
   cdCanvas cddbuffer = IupGetAttributePtr(ih,"DBUFFER")
   IupGLMakeCurrent(ih)
   cdCanvasActivate(cddbuffer)
   cdCanvasClear(cddbuffer)
   if title="Game" then
       integer mx = min(floor(cw/(ncodelen*1.5+0.5)),floor(ch/(nmaxgoes+1))),
               diameter = floor(mx/2),
               px = floor((cw-(ncodelen*1.5+0.5)*mx)/2), -- (set margin)
               cy = ch, cx, c, r
       last_guess = {}
       for g=1 to length(guesses) do
           cy -= mx
           cx = px+floor(mx/2)
           for i=1 to 2*ncodelen+1 do
               if i<=ncodelen then
                   if i<=length(guesses[g]) then
                       c = colour_table[guesses[g][i]]
                       if g=length(guesses) then
                           last_guess = Template:Cx,ch-cy
                       end if
                   else
                       c = CD_GREY
                   end if
                   r = diameter
               else
                   c = CD_GREY
                   integer k = i-ncodelen-1
                   if k>=1 and g<=length(scores) then
                       integer {b,w} = scores[g]
                       c = iff(k<=b ? CD_BLACK : iff(k<=b+w ? CD_WHITE : CD_GREY))
                   end if
                   r = floor(diameter*0.5)
               end if
               cdCanvasSetForeground(cddbuffer,c)
               if i!=ncodelen+1 then
                   cdCanvasSector(cddbuffer, cx, cy, r, r, 0, 360)
                   cdCanvasSetForeground(cddbuffer,CD_DARK_GREY)
                   cdCanvasCircle(cddbuffer, cx, cy, r)
               end if
               cx += iff(i<ncodelen?mx:floor(mx/2))
           end for
       end for
       guess_r2 = floor(diameter*diameter/4)
   elsif title="Colours" then
       integer mx = min(floor(cw/ncodelen),floor(ch/2)),
               r = floor(mx/2),
               px = floor((cw-ncodelen*mx)/2), -- (set margin)
               cy = ch-r, cx, c
       cx = px+floor(mx/2)
       bool active = length(hint)>0
       if length(scores)=nmaxgoes then
           hint = secret
           active = true
       end if
       for i=1 to ncodelen do
           c = iff(active?colour_table[hint[i]]:CD_GREY)
           cdCanvasSetForeground(cddbuffer,c)
           cdCanvasSector(cddbuffer, cx, cy, r, r, 0, 360)
           cdCanvasSetForeground(cddbuffer,CD_DARK_GREY)
           cdCanvasCircle(cddbuffer, cx, cy, r)
           cx += mx
       end for
       if length(scores)=nmaxgoes
       or guesses[$]=secret then
           ch -= floor(mx/2)
           {} = cdCanvasTextAlignment(cddbuffer, CD_CENTER)
           string wl = iff(guesses[$]=secret?"WIN":"LOSE"),
                  msg = sprintf("GAME OVER - YOU %s",{wl})
           cdCanvasText(cddbuffer, cw/2, ch/2, msg) 
       else
           integer ch0 = ch
           ch -= mx
           --
           -- calculate the best nw*nh way to fit all the colours in:
           -- (if nw ends up = ncodelen there is no clear separation
           --  between the hint and the colour table; the start with
           --  ncodelen+1 solves that and looks pretty good to me.)
           --
           integer nw = ncodelen+1,    -- (as above)
                   nh = 1
           while nw*nh<ncolours do
               if (cw/(nw+1))>(ch/(nh+1)) then
                   nw += 1
               else
                   nh += 1
               end if
           end while
           --
           -- now draw all the colours
           --
           mx = min(floor(cw/nw),floor(ch/nh))
           r = floor(mx/2)
           px = floor((cw-nw*mx)/2)
           cx = px+floor(mx/2)
           cy = ch-r
           integer this_row = 0
           colour_centres = repeat(0,ncolours)
           colour_r2 = floor(r*r/4)
           for i=1 to ncolours do
               colour_centres[i] = {cx,ch0-cy}
               c = colour_table[i]
               cdCanvasSetForeground(cddbuffer,c)
               cdCanvasSector(cddbuffer, cx, cy, r, r, 0, 360)
               cdCanvasSetForeground(cddbuffer,CD_DARK_GREY)
               cdCanvasCircle(cddbuffer, cx, cy, r)
               cx += mx
               this_row += 1
               if this_row>=nw then
                   this_row = 0
                   cx = px + floor(mx/2)
                   cy -= mx
               end if
           end for
       end if
   end if
   cdCanvasFlush(cddbuffer)
   return IUP_DEFAULT

end function

function map_cb(Ihandle ih)

   IupGLMakeCurrent(ih)
   atom res = IupGetDouble(NULL, "SCREENDPI")/25.4
   cdCanvas cddbuffer = cdCreateCanvas(CD_GL, "10x10 %g", {res})
   IupSetAttributePtr(ih,"DBUFFER",cddbuffer)
   cdCanvasSetBackground(cddbuffer, CD_PARCHMENT)
   return IUP_DEFAULT

end function

function canvas_resize_cb(Ihandle canvas)

   cdCanvas cddbuffer = IupGetAttributePtr(canvas,"DBUFFER")
   integer {canvas_width, canvas_height} = IupGetIntInt(canvas, "DRAWSIZE")
   atom res = IupGetDouble(NULL, "SCREENDPI")/25.4
   cdCanvasSetAttribute(cddbuffer, "SIZE", "%dx%d %g", {canvas_width, canvas_height, res})
   return IUP_DEFAULT

end function

procedure redraw_all()

   IupUpdate({game_canvas,colour_canvas})

end procedure

procedure undo_move() -- Called from button_cb and from K_DEL, but latter may be invalid.

   if length(guesses[$])!=0 then
       guesses[$] = guesses[$][1..$-1]
       redraw_all()
   end if

end procedure

procedure add_move(integer i)

   if i!=0 then
       guesses[$] &= i
   end if
   if length(guesses[$])=ncodelen then
       sequence guest = guesses[$],
                score = get_score(guest,secret)
       scores = append(scores,score)   
       if score!={ncodelen,0}  -- (not all black==game over)
       and length(guesses)<nmaxgoes then
           for i=1 to length(initial_set) do
               if not excluded[i] then
                   bool ei = get_score(guest,initial_set[i])!=score
                   if ei then
                       if length(initial_set)!=is_len then
                           --
                           -- Sadly, if we have been offering good hints,
                           -- that means any replacement is 90/99/99.9% 
                           -- likely to also be excluded, hence this
                           -- becomes quite pointless (or ludicrously
                           -- expensive if we loop) *very* quickly....
                           --
                           initial_set[i] = random_set()
                           ei = get_score(guest,initial_set[i])!=score
                       end if          
                       if ei then
                           excluded[i] = true
                           is_excluded += 1
                       end if
                   end if
               end if
           end for
           guesses = append(guesses,{})
           hint = {}
           IupSetInt(usehint,"ACTIVE",false)
           start_idle()
       end if
   end if
   redraw_all()

end procedure

function usehint_cb(Ihandle /*usehint*/)

   guesses[$] = hint
   add_move(0)
   return IUP_DEFAULT

end function

function button_cb(Ihandle canvas, integer button, pressed, x, y, atom /*pStatus*/)

   Ihandle frame = IupGetParent(canvas)
   string title = IupGetAttribute(frame,"TITLE")
   if not find(title,{"Game","Colours"}) then ?9/0 end if
   if button=IUP_BUTTON1 and not pressed then      -- (left button released)
       {sequence centres, integer r2} = iff(title="Game"?{last_guess,guess_r2}
                                                        :{colour_centres,colour_r2})
       for i=1 to length(centres) do
           integer {cx,cy} = sq_sub(centres[i],{x,y})
           if (cx*cx+cy*cy)<=r2 then
               if title="Game" then
                   undo_move()
               else
                   add_move(i)
               end if
               exit
           end if
       end for
   end if
   return IUP_CONTINUE

end function

function new_game_cb(Ihandle /*ih*/)

   new_game()
   redraw_all()
   return IUP_DEFAULT

end function

function exit_cb(Ihandle /*ih*/)

   return IUP_CLOSE

end function constant cb_exit = Icallback("exit_cb")

function help_cb(Ihandln /*ih*/)

   IupMessage("Mastermind",help_text)
   return IUP_DEFAULT

end function

function key_cb(Ihandle /*dlg*/, atom c)

   if c=K_ESC then return IUP_CLOSE end if
   if c=K_F1 then return help_cb(NULL) end if
   if find(c,{K_DEL,K_BS}) then undo_move() end if
   return IUP_CONTINUE

end function

function valuechanged_cb(Ihandle ih)

   ncolours = IupGetInt(colours,"VALUE")
   ncodelen = IupGetInt(codelen,"VALUE")
   nmaxgoes = IupGetInt(maxgoes,"VALUE")
   IupSetInt(repeats,"ACTIVE",ncodelen<=ncolours)
   if ncodelen>ncolours then
       IupSetInt(repeats,"VALUE",true)
   end if
   brepeats = IupGetInt(repeats,"VALUE")
   if ih!=maxgoes
   or length(scores)=length(guesses) then  -- (game over)
       new_game()
   elsif nmaxgoes<=length(scores) then
       -- (signal/force game over state)
       guesses = guesses[1..length(scores)]
   end if
   redraw_all()
   return IUP_DEFAULT

end function constant cb_valuechanged = Icallback("valuechanged_cb")

procedure main()

   IupOpen()

   colours = IupText("SPIN=Yes, SPINMIN=1, SPINMAX=20, VALUE=6, RASTERSIZE=34x")
   codelen = IupText("SPIN=Yes, SPINMIN=1, SPINMAX=10, VALUE=4, RASTERSIZE=34x")
   maxgoes = IupText("SPIN=Yes, SPINMIN=1, SPINMAX=20, VALUE=7, RASTERSIZE=34x")
   repeats = IupToggle("Repeatable?","VALUE=YES, RIGHTBUTTON=YES, PADDING=5x4")
   progress = IupLabel("-","EXPAND=HORIZONTAL, PADDING=5x4")
   usehint = IupButton("Use",Icallback("usehint_cb"),"PADDING=5x4, ACTIVE=NO")
   game_canvas = IupGLCanvas("RASTERSIZE=200x")
   colour_canvas = IupGLCanvas("RASTERSIZE=x200")
   Ihandle newgame = IupButton("New Game",Icallback("new_game_cb"),"PADDING=5x4"),
           help = IupButton("Help (F1)",Icallback("help_cb"),"PADDING=5x4"),
           quit = IupButton("E&xit",Icallback("exit_cb"),"PADDING=5x4"),
           vbox = IupVbox({IupHbox({IupLabel("Colours (1-20)","PADDING=5x4"),colours}),
                           IupHbox({IupLabel("Code Length (1-10)","PADDING=5x4"),codelen}),
                           IupHbox({IupLabel("Guesses (1-20)","PADDING=5x4"),maxgoes}),
                           IupHbox({repeats},"MARGIN=10x5"),
                           IupHbox({progress}),
                           IupHbox({usehint,newgame,help,quit})},"MARGIN=5x5"),
           game_frame = IupFrame(IupHbox({game_canvas},"MARGIN=3x3"),"TITLE=Game"),
           option_frame = IupFrame(vbox,"TITLE=Options"),
           colour_frame = IupFrame(colour_canvas,"TITLE=Colours"),
           full = IupHbox({game_frame,IupVbox({option_frame,colour_frame})})
   IupSetCallbacks({colours,codelen,maxgoes,repeats}, {"VALUECHANGED_CB", cb_valuechanged})
   IupSetCallbacks({game_canvas,colour_canvas}, {"ACTION", Icallback("redraw_cb"),
                                                 "MAP_CB", Icallback("map_cb"),
                                                 "RESIZE_CB", Icallback("canvas_resize_cb"),
                                                 "BUTTON_CB", Icallback("button_cb")})
   dlg = IupDialog(IupHbox({full},"MARGIN=3x3"),"TITLE=Mastermind")
   IupSetCallback(dlg, "K_ANY", Icallback("key_cb"))
   IupSetAttributeHandle(dlg,"DEFAULTENTER", usehint)
   new_game()
   IupShowXY(dlg,IUP_CENTER,IUP_CENTER)
   IupSetAttribute(dlg, "RASTERSIZE", NULL)
   IupSetStrAttribute(dlg, "MINSIZE", IupGetAttribute(dlg,"RASTERSIZE"))
   IupMainLoop()
   IupClose()

end procedure main()</lang>

REXX

More checks could have been added   (for illegal inputs and illegal options). <lang rexx>/*REXX pgm scores mastermind game with a human or CBLFs (Carbon Based Life Forms). */ parse arg let wid mxG oRep seed _ /*obtain optional arguments from the CL*/

     arg  .   .   .   rep .                     /*get uppercase 4th argument  "   "  " */

if let== | let=="," then let= 20 /*Not specified? Then use the default.*/ if wid== | wid=="," then wid= 4 /* " " " " " " */ if mxG== | mxG=="," then mxG= 20 /* " " " " " " */ if rep== | rep=="," then rep= 0 /* " " " " " " */ if datatype(seed,'W') then call random ,,seed /*use a seed for random repeatability. */ if abbrev( 'REPEATSALLOWED',rep,3) then rep=1 /*allow an abbreviated option for REP. */ if abbrev('NOREPEATSALLOWED',rep,3) then rep=0 /* " " " " " " */ call vet arg(), 'args' /*Vet the number of arguments entered. */ /*◄■■■■■■ optional vetting.*/ call vet let, 'letters', 2, 20 /* " " " " letters in the code*/ /*◄■■■■■■ optional vetting.*/ call vet wid, 'width', 4, 10 /* " " " " the width of code. */ /*◄■■■■■■ optional vetting.*/ call vet mxG, 'maxGuess', 7, 20 /* " " " " maximum guesses. */ /*◄■■■■■■ optional vetting.*/ call vet rep, 'REP', 0, 1e8 /* " " value if repeats are allowed*/ /*◄■■■■■■ optional vetting.*/ call gen; yourG= 'Your guess must be exactly '

                                                youve= "You've already tried that guess "
       do prompt=0  by 0  until xx==wid;   say  /*play until guessed or QUIT is entered*/
       say id 'Please enter a guess with '   wid  ' letters                   [or Quit]:'
       pull g;   g=space(g,0);  L=length(g);     if abbrev('QUIT',g,1)  then exit 0
       if L\==wid  then do;  say id '***error***'  yourG wid  " letters.";  iterate;  end
       call dups                                /*look through the history log for dups*/
       q=?;      XX=0;      OO=0;     try=try+1 /*initialize some REXX vars;  bump TRY.*/
            do j=1  for L;  if substr(g,j,1) \== substr(q,j,1)  then iterate    /*hit? */
            xx=xx+1;    q=overlay('▒', q, j)    /*bump the  XX  correct   count.       */
            end   /*j*/                         /* [↑]  XX  correct count; scrub guess.*/
            do k=1  for L;   _=substr(g, k, 1)  /*process the count for  "spots".      */
            if pos(_, q)==0  then iterate       /*is this  (spot)  letter in the code? */
            oo=oo+1;       q=translate(q, , _)  /*bump the  OO  spot count.            */
            end   /*k*/                         /* [↑]  OO  spot count;  & scrub guess.*/
       say
       @.try=id  right('guess'  try, 11)     '  ('mxG       "is the max):"    g   '──►' ,
                                     copies('X', xx)copies("O", oo)copies('-', wid-xx-oo)
       call hist
       if try==mxG  then do;  say;      say id   "you've used the maximum guesses:"   mxG
                              say;      say id   "The code was: "   ?;    say;     exit 1
                         end
       end   /*prompt*/

say; say " ┌─────────────────────────────────────────┐"

                              say "          │                                         │"
                              say "          │  Congratulations, you've guessed it !!  │"
                              say "          │                                         │"
                              say "          └─────────────────────────────────────────┘"

exit 0 /*stick a fork in it, we're all done. */ /*──────────────────────────────────────────────────────────────────────────────────────*/ dups: do h=1 for try; if g\=word(@.h, 8) then iterate /*any duplicated guesses? */

        say;  say id youve  " (guess number" h').'; iterate prompt; end  /*h*/;    return

/*──────────────────────────────────────────────────────────────────────────────────────*/ gen: if rep==0 then reps= 'no' /*create a literal for the prompt msg. */

                else reps=
     @abc= 'QWERTYUIOPASDFGHJKLZXCVBNM'         /*capital letters used for random code.*/
     id='────────';  try=0;  L@abc=length(@abc) /*identifier in front of msg from here.*/
     ?=
         do  until  length(?)==wid              /*gen random codes 'til there's enough.*/
         r=substr(@abc, random(1, L@abc), 1)    /*generate a random letter, 1 at a time*/
         if \rep & pos(r, ?)\==0  then iterate  /*maybe  don't  allow a repeated digit.*/
         ?=? || r; if ?=='QUIT'&let==4  then ?= /*append random letter; ··· except this*/
         end   /*until*/                        /* [↑]  builds a unique  N-letter code.*/
     say
     say id 'A random code of '   wid   "letters  (out of a possible "  let  ' letters) '
     say id 'has been generated   (with'    reps    "repeats)."
     return

/*──────────────────────────────────────────────────────────────────────────────────────*/ hist: do hist=1 for try; say @.hist; end; return /*show "guess" history.*/ s: if arg(1)==1 then return ; return "s" /*a simpler pluraizer. */ ser: say; say; say '***error***' arg(1); say; say; exit 13 /*──────────────────────────────────────────────────────────────────────────────────────*/ /*◄■■■■■■ optional vetting.*/ vet: parse arg val,?,mn,mx /*vet (validate) a specified argument. */ /*◄■■■■■■ optional vetting.*/

     if ?=="args" & (val>1 | _\=)  then call ser "Too many arguments specified. "  _     /*◄■■■■■■ optional vetting.*/
     if ?=="args"       then return                                                        /*◄■■■■■■ optional vetting.*/
     if \datatype(val, 'N')          then call ser ? "isn't numeric: "               val   /*◄■■■■■■ optional vetting.*/
     if \datatype(val, 'W')          then call ser ? "isn't an integer: "            val   /*◄■■■■■■ optional vetting.*/
     if val < mn                     then call ser ? "has a value less than "        mn    /*◄■■■■■■ optional vetting.*/
     if val > mx                     then call ser ? "has a value greater than "     mx    /*◄■■■■■■ optional vetting.*/
     if ?=='REP' & \datatype(val,W)  then call ser "Value for REPEATS isn't valid: " oRep  /*◄■■■■■■ optional vetting.*/
     return 1</lang>

output

──────── A random code of  4 letters  (out of a possible  20  letters)
──────── has been generated   (with no repeats).

──────── Please enter a guess with  4  letters                   [or Quit]:
abcd                  ◄■■■■■■ user input

────────     guess 1   (20 is the max): ABCD ──► ----

──────── Please enter a guess with  4  letters                   [or Quit]:
efgh                  ◄■■■■■■ user input

────────     guess 1   (20 is the max): ABCD ──► ----
────────     guess 2   (20 is the max): EFGH ──► ----

──────── Please enter a guess with  4  letters                   [or Quit]:
ijkl                  ◄■■■■■■ user input

────────     guess 1   (20 is the max): ABCD ──► ----
────────     guess 2   (20 is the max): EFGH ──► ----
────────     guess 3   (20 is the max): IJKL ──► O---

──────── Please enter a guess with  4  letters                   [or Quit]:
mnop                  ◄■■■■■■ user input

     ···············································
     ·    (Some of the output has been elided.)    ·
     ···············································

──────── Please enter a guess with  4  letters                   [or Quit]:
yinp                  ◄■■■■■■ user input

────────     guess 1   (20 is the max): ABCD ──► ----
────────     guess 2   (20 is the max): EFGH ──► ----
────────     guess 3   (20 is the max): IJKL ──► O---
────────     guess 4   (20 is the max): MNOP ──► XO--
────────     guess 5   (20 is the max): WXYZ ──► O---
────────     guess 6   (20 is the max): LKHI ──► O---
────────     guess 7   (20 is the max): PONM ──► XO--
────────     guess 8   (20 is the max): ZYXW ──► O---
────────     guess 9   (20 is the max): YZWX ──► X---
────────    guess 10   (20 is the max): MOPN ──► OO--
────────    guess 11   (20 is the max): OMPN ──► OO--
────────    guess 12   (20 is the max): LKJI ──► O---
────────    guess 13   (20 is the max): JILK ──► X---
────────    guess 14   (20 is the max): YINP ──► XXXX

          ┌─────────────────────────────────────────┐
          │                                         │
          │  Congratulations, you've guessed it !!  │
          │                                         │
          └─────────────────────────────────────────┘

Ring

<lang ring>

  1. Project : Mastermind

colors = ["A", "B", "C", "D"] places = list(2) mind = list(len(colors)) rands = list(len(colors)) master = list(len(colors)) test = list(len(colors)) guesses = 7 repeat = false nr = 0

if repeat

  for n = 1 to len(colors)
       while true
                 rnd = random(len(colors)-1) + 1
                 if rands[rnd] != 1
                    mind[n] = rnd
                    rands[rnd] = 1
                   exit
                 ok
       end     
  next

else

  for n = 1 to len(colors)
       rnd = random(len(colors)-1) + 1
       mind[n] = rnd
   next

ok

for n = 1 to len(colors)

     master[n] = char(64+mind[n])

next while true

        for p = 1 to len(places)
              places[p] = 0
        next
        nr = nr + 1
        see "Your guess (ABCD)? "
        give testbegin
        for d = 1 to len(test)
              test[d] = testbegin[d]
        next 
        flag = 1
        for n = 1 to len(test)
              if upper(test[n]) != master[n]
                 flag = 0
              ok
        next
        if flag = 1
           exit
        else
           for x = 1 to len(master)
                 if upper(test[x]) = master[x]
                     places[1] = places[1] + 1
                 ok                  
           next
           mastertemp = master
           for p = 1 to len(test)
                 pos = find(mastertemp, upper(test[p]))
                 if pos > 0 
                    del(mastertemp, pos)
                    places[2] = places[2] + 1
                 ok
           next 
        ok
        place1 = places[1] 
        place2 = places[2] - place1
        place3 = len(master) - (place1 + place2)
        showresult(test, place1, place2, place3)
        if nr = guesses
           exit
        ok

end see "Well done!" + nl see "End of game" + nl

func showresult(test, place1, place2, place3)

       see "" + nr + " : " 
       for r = 1 to len(test)
            see test[r]
       next
       see " : "
       for n1 = 1 to place1
             see "X" + " "
       next
       for n2 = 1 to place2
             see "O" + " "
       next
       for n3 = 1 to place3
             see "-" + " "
       next
       see nl

</lang> Output:

Your guess (ABCD)? BCDA
1 : BCDA : X X O - 
Your guess (ABCD)? BCDB
2 : BCDB : X X X - 
Your guess (ABCD)? BCBB
3 : BCBB : X X X - 
Your guess (ABCD)? BCAB
Well done!
End of game

zkl

Translation of: C++

<lang zkl>class MasterMind{

  fcn init(code_len,guess_count){
     var codeLen =code_len.max(4).min(10);
     var guessCnt=guess_count.max(7).min(20);
     var colors  ="ABCDEFGHIJKLMNOPQRST"[0,codeLen];
  }
  fcn play{
     guesses,win,blackWhite:=List(),False,Void;
     code:=codeLen.pump(String,'wrap(_){ colors[(0).random(codeLen)] });
     do(guessCnt){

str:=getInput(); win,blackWhite = checkInput(str,code); guesses.append(T(str,blackWhite)); showBoard(guesses); if(win) break;

     }
     if(win) println("--------------------------------\n",

"Very well done!\nYou found the code: ",code);

      else println("--------------------------------\n",

"I am sorry, you didn't discover the code!\nThe code was: ",code);

   }
   fcn [private] showBoard(guesses){
      foreach n,gbw in ([1..].zip(guesses)){
         guess,blackWhite := gbw;
         println("%2d: %s :% s %s".fmt(n,

guess.split("").concat(" "), blackWhite.split("").concat(" "), "- "*(codeLen - blackWhite.len())));

      }
   }
   fcn [private] getInput{
      while(True){

a:=ask("Enter your guess (" + colors + "): ").toUpper()[0,codeLen]; if(not (a-colors) and a.len()>=codeLen) return(a);

      }
   }
   fcn [private] checkInput(guess,code){

// black: guess is correct in both color and position

       // white: correct color, wrong position

matched,black := guess.split("").zipWith('==,code), matched.sum(0); // remove black from code, prepend null to make counting easy code = L("-").extend(matched.zipWith('wrap(m,peg){ m and "-" or peg },code)); white:=0; foreach m,p in (matched.zip(guess)){ if(not m and (z:=code.find(p))){ white+=1; code[z]="-"; } } return(black==codeLen,"X"*black + "O"*white)

   }

}(4,12).play();</lang>

Output:
Enter your guess (ABCD): abcd
 1: A B C D : X O O - 
Enter your guess (ABCD): abcc
 1: A B C D : X O O - 
 2: A B C C : O O - - 
Enter your guess (ABCD): aaad
 1: A B C D : X O O - 
 2: A B C C : O O - - 
 3: A A A D : X - - - 
Enter your guess (ABCD): bccd
 1: A B C D : X O O - 
 2: A B C C : O O - - 
 3: A A A D : X - - - 
 4: B C C D : X X O - 
Enter your guess (ABCD): dcbd
 1: A B C D : X O O - 
 2: A B C C : O O - - 
 3: A A A D : X - - - 
 4: B C C D : X X O - 
 5: D C B D : X X X X 
--------------------------------
Very well done!
You found the code: DCBD