%-------------------------------------------------------------------------%
%
% 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).