We will now present a prototype example which shows how ProTcXl can be used to control a Prolog program. We will construct a constraint program which solves cryptarithmetic puzzles like the well known "SEND + MORE = MONEY". The graphical interface will make it possible to start and stop the solver, change the labeling routine and to enter new problems. Additionally, it will also be possible to step through the labeling routine to see its progress. The GUI consists of two windows:
The Prolog file money.pl contains the solver itself (written with the finite domain constraint solver) and a couple of predicates to handle Tk events. The program starts as follows:
:- lib(tk). :- lib(fd). :- tcl_source. :- make_local_array(mode). top :- tk([]), tcl_source('money.tcl'), tcl_source('new.tcl'), tcl 'top_window "" .new', handle_event("label", ["smallest"], _), message("Start"), repeat, block(do_money, Tag, Tag \= repeat_money).
We first load the necessary libraries, define a global mode variable which decides whether we stop after each labeling step or whether we run without stopping. When the program starts, it initialises Tk, sources the Tcl script files, initialises the two windows (the first one has prefix "" and it thus refers to the Tk main window). Then it pretends to handle the event "label smallest" which initialises the labeling procedure to select variables with smallest domain first. Finally, it starts a repeat-fail loop which solves the current problem. The displayed window is something like
We have used block/3 to start the execution, so that when a new problem is entered, we can easily abort the current problem and restart the new one from beginning. The program is started in mode step, so that it stops before the first labeling step. When we click on the Step button, one variable is selected, instantiated and the program stops at the next selected variable, skipping over variables which are already ground. The Run button causes the program to run until the next solution is found, but it still checks Tk events so that when we click on Step, it will stop again. When all solutions have been found, the program reaches the initial repeat-fail loop and starts again.
The most interesting part for us is the event handling routine:
The predicate handle_events/0 is the actuall event polling routine. It is called from inside the labeling predicate, but it can be called from any part of the program. If the current mode is run, it checks if there are any pending Prolog graphics events (and at the same time it also handles all pending Tk events) and if so, it calls handle_event/3 to serve the event and does this until there are no further events, or the mode changes.% % Labeling. Before each labeling step, check for Tk events. % If there are none and we are not stepping, just continue, % else process them and/or block. % label_vars([]). label_vars(List) :- select_var(List, Var, NewList), (var(Var) -> handle_events, select_value(Var) ; % do not stop when the variable is already bound true ), label_vars(NewList). % % Event handling % handle_events :- (getval(mode, run) -> % we only check for pending events, we do not block (tk_get_event([E|L]) -> handle_event(E, L, _), handle_events ; true ) ; tk_next_event([E|L]), % wait for the next event handle_event(E, L, Cont), (Cont = continue -> true ; handle_events ) ). handle_event("run", [], continue) :- !, message("Running"), setval(mode, run). handle_event("step", [], continue) :- !, message("Stopped"), setval(mode, step). handle_event("reset", [], _) :- !, message("Start"), exit_block(repeat_money). handle_event("exit", [], _) :- !, (tcl exit -> true; true), % may be already exited exit_block(end_money). handle_event("label", [How], no) :- label_type(How, Pred), !, Goal =..[Pred, L, V, N], compile_term(select_var(L, V, N) :- Goal).
If the mode is step, the program blocks and waits for the next Prolog graphics event, and serves it with handle_event/3. The third argument of handle_event/3 tells us whether we have to block again or we can continue. In our case, only the two buttons Run and Step let the program continue.
The New button opens a new window where the user can type a new problem definition. It checks if the problem does not contain too many characters, and it also converts all letters to upper case. It is not necessary to fill in all three operands, two are also accepted:
All events which may be handled without interaction with Prolog are handled in Tk, even if it sometimes needs some clumsy code (e.g. checking data consistency in the 'new' window. Unlike Prolog, Tcl has no command to remove duplicates from a list and so we had to count the letters explicitly. Calling Prolog to do this might have required less lines of code but it would have added an unnecessary complexity to the interface).
The Tcl scripts for this problem show a range of useful features and give hints how to program some frequently required tasks, like e.g. binding the 'Return' or 'Delete' keys in an entry, packing in several frames or packing different widgets to the same size.