next up previous contents index
Next: Index Up: Appendices Previous: Listing of qsort.m

   
Listing of the buggy mastermind program

%-------------------------------------------------------------------------%
%
% Author : Erwan Jahier
% File   : mastermind.m
%
%-------------------------------------------------------------------------%

:- module mastermind.

:- interface.
:- import_module io, list.

:- type color
        --->     blue
        ;        white
        ;        black
        ;        yellow
        ;        gray
        ;        red
        ;        brown
        ;        orange
        ;        hole.


:- type mastermind
        ---> mastermind(color, color, color, color, color).

:- type store ---> store(mastermind, int, int).

:- type database == list(store).

:- pred main(io__state::di, io__state::uo) is det.

%-------------------------------------------------------------------------%

:- implementation.
:- import_module mastermind_generator, mastermind_checker, display, list, int, 
        require, std_util.


main -->
        ask_user_for_a_mastermind(Mastermind),
        solve_mastermind(Mastermind).
        

%-------------------------------------------------------------------------%
:- pred solve_mastermind(mastermind, io__state, io__state).
:- mode solve_mastermind(in, di, uo) is det.
solve_mastermind(Code) -->
        { generate_all_possible_guess(ListofGuesses) },
        { solve_mastermind_step(Code, 0, NumberOfGuesses, [], Database,
                ListofGuesses, _) },
        display_mastermind(Code, Database),

%       Display the results when tcl/tk display is broken.
        write_string("The solution was found with "),
        write(NumberOfGuesses),
        write_string(" guesses.\n"),
        write_string("The successive guesses were :\n"),
        print_database(Database),
        nl.

%-------------------------------------------------------------------------%
:- pred solve_mastermind_step(
        mastermind::in,         % Code to break
        int::in,                % Guess counter
        int::out,               % New guess counter
        database::in,           % List of previous attemps
        database::out,          % New list of attemps
        list(mastermind)::in,   % List of untried attemps
        list(mastermind)::out   % New list of untried attemps
                ) is det.
solve_mastermind_step(Code, GuessNumberIn, GuessNumberOut, DatabaseIn,
        DatabaseOut, ListofGuessesIn, ListofGuessesOut) :-
        ( 
                propose_a_guess(DatabaseIn, ListofGuessesIn, ListofGuesses2,
                        Guess),
                check_mastermind(Guess, Code, Bulls, Cows),
                ( Bulls = 5 ->
                        DatabaseOut = [ store(Guess, Bulls, Cows) | DatabaseIn],
                        GuessNumberOut is GuessNumberIn + 1,
                        ListofGuessesOut = ListofGuesses2
                ;
                        solve_mastermind_step(Code, 
                                GuessNumberIn + 1, GuessNumberOut,
                                [ store(Guess, Bulls, Cows) | DatabaseIn], 
                                DatabaseOut,
                                ListofGuesses2,
                                ListofGuessesOut )
                )
        ;
                % Should never occur
                 error(" in solve_mastermind_step/6:
                I have tried all the possible solutions and I was not able 
                to break that code !\n")
        ).


%-------------------------------------------------------------------------%
:- pred is_guess_sensible(mastermind, database).
:- mode is_guess_sensible(in, in) is semidet.
is_guess_sensible(_, []).
is_guess_sensible(Guess, [ store(OldGuess, Bulls, Cows) | DatabaseTail]) :-
        % The guess is sensible if it produces the same number of cows and 
        % bulls with the previous attemps.
        check_mastermind(OldGuess, Guess, Bulls, Cows),
        is_guess_sensible(Guess, DatabaseTail).


:- pred propose_a_guess(database, list(mastermind), list(mastermind), 
        mastermind).
:- mode propose_a_guess(in, in, out, out) is semidet.
propose_a_guess(Database, ListofGuessesIn, ListofGuessesOut, ProposedGuess) :-
        ListofGuessesIn = [ Guess | TailGuesses ],
        ( is_guess_sensible(Guess, Database) ->
                ListofGuessesOut = TailGuesses,
                ProposedGuess = Guess
        ;
                propose_a_guess(Database, TailGuesses, ListofGuessesOut, 
                        ProposedGuess)
        ).


:- pred generate_all_possible_guess(list(mastermind)).
:- mode generate_all_possible_guess(out) is det.
generate_all_possible_guess(ListOfGuesses) :-
        solutions(generate_a_guess, ListOfGuesses).


:- pred generate_a_guess(mastermind).
:- mode generate_a_guess(out) is multi.
generate_a_guess(Guess) :-
        Guess = mastermind(C1, C2, C3, C4, C5),
        select_color(C1),
        select_color(C2),
        select_color(C3),
        select_color(C4),
        select_color(C5).


:- pred select_color(color).
:- mode select_color(out) is multi.
select_color(blue).
select_color(white).
select_color(black). 
select_color(yellow).
select_color(gray).
select_color(red).
select_color(brown).
select_color(orange).


%-------------------------------------------------------------------------%
:- pred print_database(database, io__state, io__state).
:- mode print_database(in, di, uo) is det.
print_database(Database) -->
        { reverse(Database, DatabaseReversed) },
        print_database2(DatabaseReversed, 1),
        nl.


:- pred print_database2(database, int, io__state, io__state).
:- mode print_database2(in, in, di, uo) is det.
print_database2([], _) -->
        nl.

print_database2([store(mastermind(C1, C2, C3, C4, C5), Bulls, Cows)|Tail], N) -->
        write_string("     "),
        write(N),
        write_string(": "),
        write(C1),
        write_string(" "),
        write(C2),
        write_string(" "),
        write(C3),
        write_string(" "),
        write(C4),
        write_string(" "),
        write(C5),
        write_string(" ("),        
        write(Bulls),
        write_string(" Bulls and "),
        write(Cows),
        write_string(" Cows).\n"),
        print_database2(Tail, N + 1).

%-------------------------------------------------------------------------%
%
% Author : Erwan Jahier
% File   : mastermind_checker.m
%
% 
% This module implements the check_mastermind/4 predicate. 
% This predicate takes in input a Code and a Guess and outputs the number of
% Bulls (colors that appears in identical position in the Guess and in the Code)
% and Cows (colors that appears in the Guess and in the code but at wrong
% positions).

:- module mastermind_checker.

:- interface.
:- import_module mastermind, int.

:- pred check_mastermind(mastermind, mastermind, int, int).
:- mode check_mastermind(in, in, out, out) is det.

:- implementation.
:- import_module int, list, mastermind.


%-------------------------------------------------------------------------%
check_mastermind(Guess, Code, Bulls, Cows) :-
         count_bulls(Guess, Code, Bulls, Guess2, Code2),
        % Code2 and  Guess2 is the same as Code and Guess except that 
        % we have removed the well placed whatsits.
         count_cows(Guess2, Code2, Cows).        


:- pred count_bulls(mastermind, mastermind, int, mastermind, mastermind).
:- mode count_bulls(in, in, out, out, out) is det.
count_bulls(Guess, Code, Bulls, Guess2, Code2) :-
        Guess = mastermind(G1, G2, G3, G4, G5),
        Code = mastermind(Color1, Color2, Color3, Color4, Color5),
        Guess2 = mastermind(
                        remove_bulls(G1, Color1), 
                        remove_bulls(G2, Color2), 
                        remove_bulls(G3, Color3), 
                        remove_bulls(G4, Color4), 
                        remove_bulls(G5, Color5) 
                                ),
        Code2 = mastermind(
                        remove_bulls(Color1, G1), 
                        remove_bulls(Color2, G2), 
                        remove_bulls(Color3, G3), 
                        remove_bulls(Color4, G4), 
                        remove_bulls(Color5, G5) 
                                ),
        count_hole(Guess2, Bulls).


:- func remove_bulls(color, color) = color.
:- mode remove_bulls(in, in) = out is det.
remove_bulls(G, Color) = Out :-
        % We remove the well placed whatsits
        ( (G = Color) ->
                Out = hole
        ;
                Out = G
        ).


:- pred count_hole(mastermind, int).
:- mode count_hole(in, out) is det.
count_hole(mastermind(C1, C2, C3, C4, C5), N) :-
        Counter0 = 0,
        count_hole2(C1, Counter0, Counter1),
        count_hole2(C2, Counter1, Counter2),
        count_hole2(C3, Counter2, Counter3),
        count_hole2(C4, Counter3, Counter4),
        count_hole2(C5, Counter4, N).

                
:- pred count_hole2(color, int, int).
:- mode count_hole2(in, in, out) is det.
count_hole2(Color, Cin, Cout) :-
        ( (Color = hole) ->
                Cout is Cin + 1
        ;
                Cout = Cin
        ).

        
:- pred count_cows(mastermind, mastermind, int).
:- mode count_cows(in, in, out) is det.
count_cows(Guess, Code, Cows) :-
        % Outputs the number of cows in the Guess.
        Guess = mastermind(G1, G2, G3, G4, G5),
        Code = mastermind(Color1, Color2, Color3, Color4, Color5),
        List1 = [G1, G2, G3, G4, G5],
        remove_cows_from_list(Color1, List1, List2),
        remove_cows_from_list(Color2, List2, List3),
        remove_cows_from_list(Color3, List3, List4),
        remove_cows_from_list(Color4, List4, List5),
        remove_cows_from_list(Color5, List5, List6),
        list__length(List6, M),
        Cows is 5 - M.


:- pred remove_cows_from_list(color, list(color), list(color)).
:- mode remove_cows_from_list(in, in, out) is det.
remove_cows_from_list(_, [], []).
remove_cows_from_list(C, [X | Xs], ListOut) :-
        ( 
                C \= hole, 
                C = X
         ->
                ListOut = Xs
        ;
                remove_cows_from_list(C, Xs, List),
                ListOut = [C | List]    %  Bug !
                % ListOut = [X | List]  % Correct code.
        ).

%-------------------------------------------------------------------------%


%-------------------------------------------------------------------------%
%
% Author : Erwan Jahier
% File   : mastermind_generator.m
%
% This module provides  mastermind generators: 
%  - ask_user_for_a_mastermind/1
%  - generate_random_mastermind/3. 
%
% The first one will prompt the user to enter a mastermind problem. The second 
% one will pseudo-randomly generate mastermind problems.

:- module mastermind_generator.

:- interface.
:- import_module mastermind, io, random.

:- pred ask_user_for_a_mastermind(
        mastermind::out, 
        io__state::di, 
        io__state::uo)is det.

:- pred generate_random_mastermind(
        mastermind::out, 
        random__supply::mdi, 
        random__supply::muo) is det.

:- pred convert_string_into_color(string, color).
:- mode convert_string_into_color(in, out) is semidet.
:- mode convert_string_into_color(out, in) is semidet.

:- implementation.
:- import_module mastermind, random, term, term_io, io, list, int.


%-------------------------------------------------------------------------%
ask_user_for_a_mastermind(Mastermind) -->
        write_string("Enter a mastermind problem :\n"),
        term_io__read_term(ReadTerm),
        (
                { convert_readterm_into_mastermind(ReadTerm, Mastermind1) }
        ->
                { Mastermind = Mastermind1 }
        ;
                write_string("\nYou should write something like:\n"),
                write_string("mastermind(blue, white, black, yellow, gray)."),
                write_string("\nPossible colors are: "),
                write_string("blue, white, black, yellow, gray, red, brown, "),
                write_string("orange.\n"),
                ask_user_for_a_mastermind(Mastermind)
        ).


:- pred convert_readterm_into_mastermind(read_term, mastermind).
:- mode convert_readterm_into_mastermind(in, out) is semidet.
convert_readterm_into_mastermind(ReadTerm, Mastermind) :- 
        ReadTerm = term(_Varset, Term),
        Term = functor(
                atom("mastermind"),
                [
                        functor(atom(ColorStr1), _, _), 
                        functor(atom(ColorStr2), _, _), 
                        functor(atom(ColorStr3), _, _), 
                        functor(atom(ColorStr4), _, _), 
                        functor(atom(ColorStr5), _, _)
                ], 
                context("<standard input>", _)
                        ), 
        convert_string_into_color(ColorStr1, Color1),
        convert_string_into_color(ColorStr2, Color2),
        convert_string_into_color(ColorStr3, Color3),
        convert_string_into_color(ColorStr4, Color4),
        convert_string_into_color(ColorStr5, Color5),
        Mastermind = mastermind(Color1, Color2, Color3, Color4, Color5).


% :- pred convert_string_into_color(string, color).
% :- mode convert_string_into_color(in, out) is semidet.
% :- mode convert_string_into_color(out, in) is semidet.
convert_string_into_color("red", red).
convert_string_into_color("blue", blue).
convert_string_into_color("white", white).
convert_string_into_color("yellow", yellow).
convert_string_into_color("gray", gray).
convert_string_into_color("brown", brown).
convert_string_into_color("orange", orange).
convert_string_into_color("black", black).


%-------------------------------------------------------------------------%
        % random__init(0, RS) 
        % generate_random_mastermind(Mastermind, RS, RSout)
        % ...
generate_random_mastermind(mastermind(C1, C2, C3, C4, C5), RSin, RSout) :-
        generate_one_color(C1, RSin, RS1),
        generate_one_color(C2, RS1, RS2),
        generate_one_color(C3, RS2, RS3),
        generate_one_color(C4, RS3, RS4),
        generate_one_color(C5, RS4, RSout).


:- pred generate_one_color(color, random__supply, random__supply).
:- mode generate_one_color(out, mdi, muo) is det.
generate_one_color(Color, RSin, RSout) :-
        random__random(Num,  RSin, RS1),
        random__randmax(RandMax, RS1, RSout),
        NNum is Num * 8,
        (
                NNum < RandMax
        ->
                Color = blue
        ;
                NNum < 2*RandMax
        ->
                Color = white
        ;
                NNum < 3*RandMax
        ->
                Color = black
        ;
                NNum < 4*RandMax
        ->
                Color = yellow
        ;
                NNum < 5*RandMax
        ->
                Color = gray
        ;
                NNum < 6*RandMax
        ->
                Color = red
        ;
                Color = brown
        ).

%-------------------------------------------------------------------------%
%
% Author : Erwan Jahier
% File   : display.m
%
% Display a mastermind board via a tcl/tk script

:- module display.

:- interface.
:- import_module io, mastermind.


:- pred display_mastermind(mastermind, database, io__state, io__state).
:- mode display_mastermind(in, in, di, uo) is det.

%-------------------------------------------------------------------------%

:- implementation.
:- import_module mastermind, mastermind_generator, mastermind_checker, list, int, 
        require, std_util, string.


display_mastermind(Code, Database) -->
        { generate_tcltk_command(Code, Database, TclTkCmd) },
        io__call_system(TclTkCmd, _Res).

        
:- pred generate_tcltk_command(mastermind, database, string).
:- mode generate_tcltk_command(in, in, out) is det.
generate_tcltk_command(Code, Database, TclTkCmd) :-
        Code = mastermind(C1, C2, C3, C4, C5),
        CodeList = [C1, C2, C3, C4, C5],
        list__length(Database, Card),
        int_to_string(Card, CardStr),
        transform_list_into_command(CodeList, CodeCmd2),
        wrap_with(CodeCmd2, " \" ", " \" ", CodeCmd),
        transform_database_into_command(Database, DatabaseCmd),
        append("display_mastermind ", CodeCmd, A1),
        append(A1, DatabaseCmd, A2),
        append(A2, CardStr, TclTkCmd).


:- pred transform_database_into_command(database, string).
:- mode transform_database_into_command(in, out) is det.
transform_database_into_command(Database, DatabaseCmd) :-
        transform_database_into_command2(Database, SubmitionCmd, ResponseCmd),
        wrap_with(SubmitionCmd, " \" ", " \" ", SubmitionCmdNew),
        wrap_with(ResponseCmd, " \" ", " \" ", ResponseCmdNew),
        append(SubmitionCmdNew, ResponseCmdNew, DatabaseCmd).


:- pred transform_database_into_command2(database, string, string).
:- mode transform_database_into_command2(in, out, out) is det.
transform_database_into_command2(Database, SubmitionList, ResponseList) :-
        (
                Database = [],
                SubmitionList = " ",
                ResponseList = " "
        ;
                Database = [Store | Tail],
                Store = store(mastermind(C1, C2, C3, C4, C5), Bulls, Cows),
                List = [C1, C2, C3, C4, C5],
                transform_list_into_command(List, Submition),
                wrap_with(Submition, " { ", " } ", Submition2),
                generate_response_command(Bulls, Cows, Response),
                transform_database_into_command2(Tail, SubmitionTail, ResponseTail),
                append(Submition2,  SubmitionTail, SubmitionList),
                append(Response, ResponseTail, ResponseList)
        ).


:- pred generate_response_command(int, int, string).
:- mode generate_response_command(in, in, out) is det.
generate_response_command(Bulls, Cows, Response) :-
        generate_response(Bulls, " black ", BullsCmd),
        generate_response(Cows, " white ", CowsCmd),
        append(BullsCmd, CowsCmd, BullsCowsCmd),
        wrap_with(BullsCowsCmd, " { ",  " } ", Response).


:- pred generate_response(int, string, string).
:- mode generate_response(in, in, out) is det.
generate_response(N, Color, Cmd) :-
        (
                N = 0
        ->
                Cmd = " "
        ;
                % N > 0,
                NN is N - 1,
                generate_response(NN, Color, Cmd2),
                append(Color, Cmd2, Cmd)
        ).
                

:- pred transform_list_into_command(list(color), string).
:- mode transform_list_into_command(in, out) is det.
transform_list_into_command(List , Cmd) :-
        (
                List = [],
                Cmd = ""
        ;
                List = [C | Tail],
                transform_list_into_command(Tail, TailCmd),
                ( convert_string_into_color(CStr, C) ->
                        append(CStr, " ", A1),
                        append(A1, TailCmd, Cmd)
                ;
                        Cmd = TailCmd
                )
        ).


:- pred wrap_with(string, string, string, string).
:- mode wrap_with(in, in, in, out) is det.
wrap_with(StringIn, Before, After, StringOut) :-
        append(Before, StringIn, A1),
        append(A1, After, StringOut).



jahier@irisa.fr