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);
FUNCTION XFormat (pMask IN VARCHAR2,
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
vcHits := RPad( 'P', iPosOK, 'P') || RPad( 'N', iPosErr, 'N');
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