# Mastermind Solver by Niels Hecker

• Script Name Mastermind Solver by Niels Hecker
• Description Niels created this script to verify a particular combination of digits as a solution the weekly PL/SQL Challenge logic quiz (modeled after the classic Mastermind game).
• Category PL/SQL General
• Contributor Steven Feuerstein (Oracle)
• Created Thursday May 11, 2017
• Statement 1
``````DECLARE
/*
Automatic Solution Generator for
PL/SQL Challenge's version of Mastermind

Author: Niels Hecker (player name: NielsHecker)

Requires Oracle Database 11g Release 1 or higher.

Permission granted by author to make it available to others to
use and modify to their own purposes.
*/
TEST_MODE  CONSTANT BOOLEAN := False;

-- maximum value for each digit in the solution (1..MAX_GUESS)
MAX_GUESS  CONSTANT BINARY_INTEGER := 7;

SUBTYPE TGuess         IS VARCHAR2(9);
TYPE    TtblGuesses    IS TABLE OF TGuess;
TYPE    TtblSolutions  IS TABLE OF VARCHAR2(4);

TYPE TrecSplitGuess    IS RECORD (Digit1  CHAR(1),
Digit2  CHAR(1),
Digit3  CHAR(1),
Digit4  CHAR(1),
Hits    VARCHAR2(4));
TYPE TtblSplitGuesses  IS TABLE OF TrecSplitGuess;

/* Replace the string in the line below with the guesses and clues in this format
(taken from the July 15 2011 quiz):
GUESSES  CONSTANT TtblGuesses := TtblGuesses( '2345:PN','7643:NN','3165:N');
*/
GUESSES  CONSTANT TtblGuesses := TtblGuesses( '4321:NN', '6437:NN');

iIndex           SIMPLE_INTEGER   := 0;
tblSplitGuesses  TtblSplitGuesses := TtblSplitGuesses();
tblSolutions     TtblSolutions    := TtblSolutions();

--===== shortened/simplyfied excerpt from central 'Core\$' schema ===========
TYPE tp  IS TABLE OF VARCHAR2(32767);

pParams  IN tp DEFAULT NULL)
RETURN VARCHAR2
IS
pix     BINARY_INTEGER;
Result  VARCHAR2(32767);
BEGIN
Result := Replace( Replace( pMask, '<#>', Chr(9)), '<^>', Chr(13)||Chr(10));
IF (pParams IS NOT NULL) THEN
pix := pParams.First();
WHILE (pix IS NOT NULL) LOOP
Result := Replace( Result, ('<' || (pix - 1) || '>'), pParams(pix));
pix := pParams.Next( pix);
END LOOP;
END IF;
RETURN (Result);
EXCEPTION
WHEN OTHERS THEN   RETURN (Result);
END XFormat;

-- originally in package "do" (DBMS_Output)
PROCEDURE dopl (pMsg  IN VARCHAR2 DEFAULT NULL)
IS BEGIN DBMS_Output.Put_Line( pMsg); END;

PROCEDURE dopf (pMsg     IN VARCHAR2 DEFAULT NULL,
pParams  IN tp DEFAULT tp())
IS BEGIN DBMS_Output.Put( XFormat( pMsg, pParams)); END;

PROCEDURE doplf (pMsg     IN VARCHAR2 DEFAULT NULL,
pParams  IN tp DEFAULT tp())
IS BEGIN DBMS_Output.Put_Line( XFormat( pMsg, pParams)); END;

FUNCTION dobool (pVal  IN BOOLEAN) RETURN VARCHAR2
IS BEGIN RETURN (CASE WHEN (pVal IS NULL) THEN '(null)'
WHEN pVal THEN 'True' ELSE 'False' END); END;

--==========================================================================

-- split the specified guesses from constant 'GUESSES'
PROCEDURE PrepareGuesses\$
IS
org   TGuess;
splt  TrecSplitGuess;
BEGIN
<<"Over all Guesses">>
FOR g# IN 1..GUESSES.Count() LOOP
org := GUESSES(g#);

-- guess is specified ?
-- ==>  split it into single digits and order the hits (first the
--      P's and then the N's
IF (org IS NOT NULL) THEN
splt        := NULL;
splt.Digit1 := SubStr( org, 1, 1);
splt.Digit2 := SubStr( org, 2, 1);
splt.Digit3 := SubStr( org, 3, 1);
splt.Digit4 := SubStr( org, 4, 1);

splt.Hits := SubStr( org, 6);
splt.Hits := RPad( 'P', RegExp_Count( splt.Hits, '[Pp]'), 'P')
|| RPad( 'N', RegExp_Count( splt.Hits, '[Nn]'), 'N');

tblSplitGuesses.Extend();
tblSplitGuesses(tblSplitGuesses.Last()) := splt;

IF TEST_MODE THEN
doplf( '#<0>:  <1> <2> <3> <4>  -  <5>',
tp( tblSplitGuesses.Last(), splt.Digit1, splt.Digit2,
splt.Digit3, splt.Digit4, splt.Hits));
END IF;
END IF; -- (org IS NOT ...
END LOOP "Over all Guesses";
END PrepareGuesses\$; -- local to anonymous block

-- check the passed digits against all guesses
FUNCTION CheckGuesses\$ (p1  IN CHAR, p2  IN CHAR, p3  IN CHAR, p4  IN CHAR)
RETURN BOOLEAN
IS
splt     TrecSplitGuess;
iPosOK   BINARY_INTEGER;
iPosErr  BINARY_INTEGER;
vcHits   VARCHAR2(4);
bAllOK   BOOLEAN;

PROCEDURE IncPosOK\$ (pCond  IN BOOLEAN) IS
BEGIN IF pCond THEN iPosOK := iPosOK + 1; END IF; END;
PROCEDURE IncPosErr\$ (pCond  IN BOOLEAN) IS
BEGIN IF pCond THEN iPosErr := iPosErr + 1; END IF; END;
BEGIN
bAllOK := True;

IF TEST_MODE THEN
doplf( 'Check:  <0> <1> <2> <3>', tp( p1, p2, p3, p4));
END IF;

<<"Over all Guesses">>
FOR s# IN 1..tblSplitGuesses.Count() LOOP
splt    := tblSplitGuesses(s#);
iPosOK  := 0;
iPosErr := 0;

IF TEST_MODE THEN
doplf( '    Guess #<0>  -  <1> <2> <3> <4>',
tp( s#, splt.Digit1, splt.Digit2, splt.Digit3, splt.Digit4));
END IF;

-- check the digits for correct position
-- and "in result" but at wrong position
IncPosOK\$( splt.Digit1 = p1);
IncPosErr\$( splt.Digit1 IN (p2, p3, p4));

IncPosOK\$( splt.Digit2 = p2);
IncPosErr\$( splt.Digit2 IN (p1, p3, p4));

IncPosOK\$( splt.Digit3 = p3);
IncPosErr\$( splt.Digit3 IN (p1, p2, p4));

IncPosOK\$( splt.Digit4 = p4);
IncPosErr\$( splt.Digit4 IN (p1, p2, p3));

-- create the result for this combination

IF TEST_MODE THEN
dopf( '        iPosOK: <0>, iPosErr: <1>'
|| '  -  splt.Hits: <2>, vcHits: <3>  -  bAllOK: <4>',
tp( iPosOK, iPosErr, splt.Hits, vcHits, dobool( bAllOK)));
END IF;

-- if the test for this guess do not match exit the loop
bAllOK := bAllOK AND (splt.Hits = vcHits);

IF TEST_MODE THEN
dopl( ' -> ' || dobool( bAllOK));
END IF;

EXIT WHEN NOT bAllOK;
END LOOP "Over all Guesses";

RETURN (bAllOK);
END CheckGuesses\$; -- local to anonymous block

BEGIN -- of anonymous block
PrepareGuesses\$();

-- all digits can be in the first position
FOR i#1 IN 1..MAX_GUESS LOOP

-- in the second position the first digit is forbidden
FOR i#2 IN 1..MAX_GUESS LOOP
CONTINUE WHEN (i#2 = i#1);

-- in the third position the first and second digits are forbidden
FOR i#3 IN 1..MAX_GUESS LOOP
CONTINUE WHEN (i#3 = i#1) OR (i#3 = i#2);

-- in the fourth position the first, second and third digits are forbidden
FOR i#4 IN 1..MAX_GUESS LOOP
CONTINUE WHEN (i#4 = i#1) OR (i#4 = i#2) OR (i#4 = i#3);

IF CheckGuesses\$( i#1, i#2, i#3, i#4) THEN
iIndex := iIndex + 1;

tblSolutions.Extend();
tblSolutions(iIndex) := (i#1 || i#2 || i#3 || i#4);

dopl( LPad( iIndex, 3) || ':  ' || i#1 || i#2 || i#3 || i#4);
END IF;
END LOOP;
END LOOP;
END LOOP;
END LOOP;

dopl( 'Found ' || tblSolutions.Count() || ' solutions');
END; -- of anonymous block ``````
1: 1276
2: 1546
3: 1563
4: 1564
5: 1573
6: 1574
7: 1645
8: 1653
9: 1654
10: 1672
11: 1745
12: 1753
13: 1754
14: 1762
15: 2176
16: 2546
17: 2563
18: 2564
19: 2573
20: 2574
21: 2645
22: 2653
23: 2654
24: 2716
25: 2745
26: 2753
27: 2754
28: 3156
29: 3165
30: 3175
31: 3256
32: 3265
33: 3275
34: 3516
35: 3562
36: 3572
37: 3615
38: 3652
39: 3715
40: 3752
41: 5146
42: 5163
43: 5164
44: 5173
45: 5174
46: 5246
47: 5263
48: 5264
49: 5273
50: 5274
51: 5613
52: 5614
53: 5642
54: 5713
55: 5714
56: 5742
57: 7145
58: 7153
59: 7154
60: 7162
61: 7216
62: 7245
63: 7253
64: 7254
65: 7513
66: 7514
67: 7542
68: 7612
Found 68 solutions