Implementing an open/close function in Prolog I'm going through the Adventure in Prolog tutorial and I'm trying to implement an open/close rule for each of the doors I have.


  Add an open/closed status for each of the doors. Write open and close predicates that do the obvious


:- dynamic here/1, location/2, have/1, turned_on/1, turned_off/1.
room(kitchen).
room(office).
room(hall).
room('dining room').
room(cellar).

location(desk, office).
location(apple, kitchen).
location(flashlight, desk).
location('washing machine', cellar).
location( nani,'washing machine' ).
location(broccoli, kitchen).
location(crackers, kitchen).
location(computer, office).

door(office, hall).
door(kitchen, office).
door(kitchen, cellar).
door(hall, 'dining room').
door('dining room', kitchen).

edible(apple).
edible(crackers).

tastes_yucky(broccoli).

turned_off(flashlight).

here(kitchen).

connect(X, Y) :- door(X, Y).
connect(X, Y) :- door(Y, X).

list_things(Place) :-
  location(X, Place),
  tab(2),
  write(X), nl,
  fail.
list_things(AnyPlace).

list_connections(Conn) :-
  connect(Conn, X),
  tab(2),
  write(X),
  nl,
  fail.
list_connections(_).

look :-
  here(X),
  write('you are in the '), write(X), nl,
  write('you can see:'), nl,
  list_things(X),
  write('you can go to:'), nl,
  list_connections(X).

goto(Place) :-
  can_go(Place),
  move(Place),
  look.

can_go(Room) :-
  here(X),
  connect(X, Room).
can_go(Room) :-
  write('You can\'t go there from here'),nl,
  fail.
move(Place):-
  retract(here(X)),
  asserta(here(Place)).

take_object(X) :-
  retract(location(X, _)),
  asserta(have(X)).
  write('taken'), nl.

put(X) :-
  retract(have(X)),
  asserta(location(X,_)).
inventory:-
  have(X),
  write('you have the following items:'), nl,
  write(X).
turn_on(X):-
  turned_off(X),
  retract(turned_off(X)),
  asserta(turned_on(X)),
  write('flash light turned on').
turn_on(X):-
  write('you can\'t turn that on').

turn_off(X):-
  turned_on(X),
  retract(turned_on(X)),
  asserta(turned_off(X)),
  write('flash light is now off').
turn_off(X):-
  write('you can\'t turn that off').


Now to actually add the rules:

door(office, hall).
door(kitchen, office).
door(kitchen, cellar).
door(hall, 'dining room').
door('dining room', kitchen).

opened(office, hall).
closed(kitchen, office).
opened(kitchen, cellar).
closed(hall, 'dining room').
opened('dining room', kitchen).

open(X, Y) :-
  closed(X, Y),
  retract(closed(X, Y)),
  asserta(opened(X, Y)),
  writeln('door opened').
open(X, Y) :-
  closed(Y, X),
  retract(closed(Y, X)),
  asserta(opened(Y, X)),
  writeln('door opened').
close(X, Y) :-
  opened(X, Y),
  retract(opened(X, Y)),
  asserta(closed(X, Y)),
  writeln('door closed').
close(X, Y) :-
  opened(Y, X),
  retract(opened(Y, X)),
  asserta(closed(Y, X)),
  writeln('door closed').


I'm only concerned  that I'm using the open(X,Y) twice (also the close).  Is there a better way?
Always try hard to avoid assert/1 and retract/1: Using these predicates makes it impossible to reason about your other predicates in isolation, and therefore make debugging and extending your code much harder.

In this case, there is a clear declarative solution:

Represent the state of all doors as a Prolog term, and thread this state through all predicates that need it. For example, a simple representation could be:

[door_state(door1, open), door_state(door2, closed), door_state(door3, open)]

You can thread such a state through as follows:

state0_action_state(S0, Action, S) :-
    ...

This predicate relates an initital state to the next state, depending on Action. Given the example representation above, an action like close(door1) would result in the next state:

[door_state(door1, closed), door_state(door2, closed), door_state(door3, open)]

Thus, your predicates can all be tested in isolation, and do not depend on any implicit state.