Search                        Top                                  Index
/*
TEACH RIVERWORLD
Aaron Sloman
Based on a program originally by Max Clowes,
University of Sussex, around 1978
Updated: 11 Dec 2011

Available as part of Poplog and here:
    http://www.cs.bham.ac.uk/research/projects/poplog/teach/riverworld

A Youtube video tutorial based on this is available here:

    http://youtu.be/JKCZyBIzKMk

This is a compilable file. The explanatory text is all inside pop11 comment
brackets, so compiling this file (using ENTER l1) ignores the text. After the
file is compiled, the sample commands, included in the comments below, can be
run.


CONTENTS - (Use <ENTER> g to access required sections)

 -- Introduction: Setting up a microworld for teaching
 -- Procedure setup_world used to initialise everything.
 -- Procedure display_world() is used to show the current state in pictorial form
 -- -- Test setup_world
 -- An error-reporting procedure: river_mishap
 -- -- Test river_mishap
 -- Action procedures which check preconditions then change the world
 -- -- putin(item)
 -- -- Procedure takeout(item)
 -- -- Test takeout(item)
 -- -- Procedure getin()
 -- -- Test getin
 -- -- Procedure getout()
 -- -- Test getout
 -- -- Procedure checkeat() (if edible things not protected, they get eaten)
 -- -- Test checkeat();
 -- -- Procedure crossriver()
 -- -- Test crossriver
 -- Tests for use in checking conditions after performing actions
 -- -- Procedure check_goals checks whether goals are already achieved
 -- -- Test check_goals
 -- -- Possibly useful utility procedure opposite(side)
 -- -- Test opposite
 -- -- Possibly useful procedure sameplace(item1, item2)
 -- -- Test sameplace
 -- -- Utility procedure: eat(item1, item2);
 -- Further Reading

-- Introduction: Setting up a microworld for teaching -------------

This tutorial (with video on Youtube) explains how to use the Pop11 language
including its pattern matcher and simple database mechanism to create a
micro-world for teaching some aspect of general programming, or AI (artificial
intelligence) programming.

An earlier tutorial TEACH RIVER
    http://www.cs.bham.ac.uk/research/projects/poplog/teach/river
with Youtube video showing how it works:
    http://youtu.be/xfGv9xfqDxU

made use of a microworld of the sort described here. The original program
providing the microworld (available as LIB RIVER in Pop11) is several decades
old. The version presented below makes less use of general programming
constructs (e.g. using global variables) and more use of a database in which
knowledge about the world, including both its general features and its current
state, can be stored.

It is useful to distinguish:
o Internal semantics of the program.
    Which entities and operations of the computer, or the virtual machine
    running on the computer are referred to by structures and commands in the
    program code.
    E.g. the program can refer to lists, strings, number representations,
    variables, their values, procedures, and operations on those items.

o External semantics of the program.
    Which entities, states, events and processes in the world being modelled
    (e.g. a boat, a man, a river, and processes of getting in and out of the
    boat, or moving the boat from one side of the river to the other) are
    referred to by structures and commands in the program code. The external
    semantics can also include causal relationships (what causes what, and what
    constraints what) in the world represented.

The external semantics may or may not include some portion of the actual world.
In this tutorial it is a mythical rather simple world, which is an abstraction
from things that can exist in our actual world, containing real rivers, boats,
animals, eatings, river crossings, and so on.

The external causal relationships will be represented by constraints on what the
program allows, or what side effects running portions of the program can have.

In this tutorial, intended for teachers learning to set up a microworld for
students to use, we employ the pop11 database and associated mechanisms, in
addition to conventional programming constructs, e.g. procedures,
variables, conditionals, loops, etc.

The pop11 database is one of a number of related libraries of varying
sophistication available in pop11 based in the Pop11 pattern matcher, introduced
in

    TEACH MATCHES
and summarised in
    HELP MATCHES

with an enhanced version described in
    HELP DOESMATCH

The database is explained in these files

    TEACH DATABASE
    HELP DATABASE
    TEACH FOREACH
    HELP FOREVERY
    HELP WHICH

and other files referenced in those.
    TEACH

Previous video tutorials in this collection introduced the use of the matcher.
This one shows how to use the matcher with Pop11's most elementary database
mechanisms to design a microworld.

The microworld is the puzzle world of man, fox, chicken, grain, boat and river,
illustrated in TEACH RIVER.

*/

;;; We use the pop11 database package, described in TEACH DATABASE
uses database;

/*
-- Procedure setup_world used to initialise everything.

*/

define setup_world();
    ;;; A procedure to initialize the world
    ;;; empty the database
    [] -> database;
    ;;; add the initial facts
    add( [boat isat left] );
    add( [chicken isat left] );
    add( [fox isat left] );
    add( [grain isat left] );
    add( [man isat left] );
    add( [transportable fox] );
    add( [transportable chicken] );
    add( [transportable grain] );
    add( [can_eat fox chicken] );
    add( [can_eat chicken grain] );
    add( [opposite right left] );
    add( [opposite left right] );
enddefine;

/*

-- Procedure display_world() is used to show the current state in pictorial form

We want it to show the contents of the database in one-dimensional diagrams like
these:

    ** [man grain chicken fox ---\ \_ _/ _________________ /---]

    ** [man grain chicken fox ---\ _________________ \_ _/ /---]

    ** [man grain chicken fox ---\ \_ _/ _________________ /---]

    ** [man grain chicken ---\ \_ fox _/ _________________ /---]

    ** [grain chicken ---\ \_ man fox _/ _________________ /---]

    ** [grain chicken ---\ _________________ \_ man fox _/ /---]

    ** [grain chicken ---\ _________________ \_ fox _/ /--- man]

    ** [grain chicken ---\ _________________ \_ _/ /--- fox man]

    ** [chicken ---\ _________________ \_ _/ /--- fox man]


NB: the laws of our microworld should not allow some of these states to exist!

*/

define display_world();
    ;;; Procedure used to print "picture" of current scene, as
    ;;; illustrated above.

    lvars item;
    [%
        ;;; If man on left bank, show man first
        if present([man isat left]) then
            "man"
        endif;

        ;;; show transportable items that are on left side
        ;;; 'forevery' finds all possible ways of satisfying the list of
        ;;; conditions represented by a list of patterns. See HELP FOREVERY

        forevery ![[transportable ?item] [?item isat left]] do
            item
        endforevery,

        ;;; show left bank
        "---\",

        ;;; If boat is at the left bank, show left side of boat, and the boat's
        ;;; contents, and the right side of boat

        if present([boat isat left]) then
            "\_",
            forevery ![[?item isin boat]] do
                item
            endforevery,
            "_/"
        endif,

        ;;; show empty stretch of 'water'
        "_________________",


        ;;; If boat is at the right bank, show left side of boat, and the boat's
        ;;; contents, and the right side of boat

        if present([boat isat right]) then
            "\_",
            forevery ![[?item isin boat]] do
                item
            endforevery,
            "_/"
        endif,

        ;;; show right bank
        "/---",


        ;;; show transportable items that are on right side, using forevery
        ;;; as before to find them all.

        forevery ![[transportable ?item] [?item isat right]] do
            item
        endforevery,

        ;;; if the man is on the right bank, show him last
        if present([man isat right]) then
            "man"
        endif;
    %] =>

    ;;; the print arrow '=>' above, prints out the list in the form of a
    ;;; picture, as shown above

    ;;; end with a blank line.
    pr(newline);

enddefine;

/*

-- -- Test setup_world

setup_world();

database ==>

;;; That should print out

    ** [[opposite left right]
        [opposite right left]
        [can_eat chicken grain]
        [can_eat fox chicken]
        [transportable grain]
        [transportable chicken]
        [transportable fox]
        [man isat left]
        [grain isat left]
        [fox isat left]
        [chicken isat left]
        [boat isat left]]


display_world();

;;; That should print out:

    ** [man grain chicken fox ---\ \_ _/ _________________ /---]

remove([boat isat left]);
add([boat isat right]);

;;; the database has now changed -- boat now at right:
database ==>

    ** [[boat isat right]
        [opposite left right]
        [opposite right left]
        [can_eat chicken grain]
        [can_eat fox chicken]
        [transportable grain]
        [transportable chicken]
        [transportable fox]
        [man isat left]
        [grain isat left]
        [fox isat left]
        [chicken isat left]]

display_world();

    ;;; the boat is now shown at the right (moved by magic):

    ** [man grain chicken fox ---\ _________________ \_ _/ /---]

;;; More examples of tests are included in later tests.
*/

/*


-- An error-reporting procedure: river_mishap --------------------------

*/

define river_mishap(list);
    [MISHAP:]=>
    list ==>
    [The World:]=>
    display_world();
enddefine;


/*

-- -- Test river_mishap

setup_world();

river_mishap([elephant missing]);


*/


/*
-- Action procedures which check preconditions then change the world

-- -- putin(item)
*/


define putin(item);
    lvars place, thing;

    if item = "man" then

        river_mishap([man cannot put man in boat -- use 'getin()']);

    elseif not(present([transportable ^item]))
    then

        river_mishap([CANNOT Put 'non-transportable' ^item in boat]);

    elseif present([man isin boat]) then

        river_mishap([man in boat cannot put in ^item]);

    elseif present(![?thing isat boat]) then

        river_mishap([^thing already in boat -- cannot put in ^item]);

    elseif allpresent( ![[man isat ?place] [^item isat ?place]] )
    then

        if present([boat isat ^place]) then

            remove([^item isat ^place]);
            add([^item isin boat]);
            [^item loaded] =>
            display_world();

        else
            lvars newplace;
            lookup(![boat isat ?newplace]);
            river_mishap([CANNOT PUTIN: man at ^place but boat at ^newplace]) =>
        endif

    else
        river_mishap([Unexpected unknown failure])
    endif;

enddefine;

/*
;;; test putin

setup_world();

display_world();

putin("elephant");

putin("man");

putin("fox");

database ==>

*/

/*
-- -- Procedure takeout(item)

*/

define takeout(item);
    lvars item, place, otherplace;

    if item = "man" then

        river_mishap([man cannot take man out of boat -- use 'getout()']);

    elseif present([man isin boat]) then

        river_mishap([man in boat cannot take out ^item]);

    elseif not (allpresent(![[man isat ?place] [boat isat ?place]])) then

        ;;; This situation should never occur. Can you prove it cannot?

        lookup(! [man isat ?place]);
        lookup(! [boat isat ?otherplace]);

        river_mishap([cannot takeout: man at ^place, boat at ^otherplace]);

    elseif not(present([^item isin boat])) then

        river_mishap([^item not in boat -- cannot take it out]);

    else
        ;;; Where is the man? set the variable place, to be used below
        lookup(! [man isat ?place]);

        remove([^item isin boat]);
        add([^item isat ^place]);

        ;;; report the action, and show the world state
        [^item unloaded] =>
        display_world();
    endif
enddefine;

/*

-- -- Test takeout(item)
;;; some of these should report errors

setup_world();

display_world();

takeout("chicken");
takeout("boat");
takeout("fox");
takeout("man");

putin("fox");
takeout("fox");

*/
/*

-- -- Procedure getin()

*/

vars procedure checkeat;    ;;; defined below, could be invoked in getin()

define getin();
    ;;; put the man in the boat, then check whether anything gets
    ;;; eaten
    lvars place, otherplace;

    ;;; Make sure the presuppositions of the action are satisfied
    if  present([man isin boat]) then

        river_mishap('Man already in boat');

    else

        ;;; find where the man is, and check boat is there

        lookup( ! [man isat ?place] );

        if present([boat isat ^place]) then

            ;;; conditions satisfied, so move man from bank to boat

            remove([man isat ^place]);
            add([man isin boat]);

            display_world();

            ;;; now check if something can be eaten
            ;;; Try inserting a call of checkeat, defined below
            ;;; checkeat()

        else

            ;;; A running program should never get here, if  bug free!

            lookup( ![boat isat ?otherplace] );
            river_mishap([Cannot getin -- man at ^place, boat at ^otherplace]);

        endif

    endif;

enddefine;

/*
-- -- Test getin

setup_world();

display_world();

getin();

display_world();


*/

/*

-- -- Procedure getout()

*/

define getout();
    lvars place;

    ;;; Check conditions first, then perform actions

    if  not(present([man isin boat])) then
        river_mishap('man already out of boat')
    else

        ;;; ensure first effect -- man not in boat
        remove([man isin boat]);

        ;;; find which side the boat is at, and put the man there
        lookup(![boat isat ?place]);
        add([man isat ^place]);

        display_world();
    endif;
enddefine;

/*
-- -- Test getout

getin();

getout();

*/


/*

-- -- Procedure checkeat() (if edible things not protected, they get eaten)

The procedure getin() above could be altered to invoke this, immediately after
the man's location has been transferred to inside the boat.

Try that change.

*/


define checkeat() -> items_eaten;
    ;;; check if last move was safe
    ;;; If unsafe, make eating happen

    ;;; return list of things eaten, empty if nothing eaten
    [] -> items_eaten;

    lvars eater, eaten, place;

    forevery ![[can_eat ?eater ?eaten][?eater isat ?place][?eaten isat ?place]] do

        if not(present([man isat ^place])) then
            remove([^eaten isat =]);

            river_mishap([DISASTER -- ^eater has consumed ^eaten]);

            [^^items_eaten ^eaten] -> items_eaten;
        endif;

    endforevery;

enddefine;

/*

-- -- Test checkeat();

setup_world();
display_world();
checkeat() =>

getin();
display_world();
checkeat() =>

add([chicken isat right]);
add([grain isat right]);
display_world();
checkeat() =>

*/


/*

-- -- Procedure crossriver()

*/

define crossriver();
    lvars place, newplace;

    ;;; check man is in boat, find location of boat, and find
    ;;; opposite location
    if  allpresent(
        ![
            [man isin boat]
            [boat isat ?place]
            [opposite ?place ?newplace]
        ])
    then

        ;;; move boat to opposite location

        remove([boat isat ^place]);
        add([boat isat ^newplace]);

        display_world()
    else
        ;;; if test failed, report mishap

        river_mishap('BOAT NOT SELF PROPELLING: MAN NOT IN BOAT')
    endif
enddefine;


/*
-- -- Test crossriver

setup_world();
display_world();
crossriver();
getin();
display_world();
crossriver();
getout();
checkeat();

*/



/*

-- Tests for use in checking conditions after performing actions

*/

/*
-- -- Procedure check_goals(goals) checks whether goals are already achieved

This is not used in the demos in the online video tutorial, but show how an
agent trying to achieve some goals could check whether its goals have been
achieved, after various actions.

*/

define check_goals(goals) -> result;
    ;;; goals is a list of conditions, to be checked
    ;;; Test the assumption that the goals are already achieved:

    ;;; assume goals achieved,
    true -> result;

    ;;; Check goals one by one, and if one is not true make result false

    lvars goal;

    for goal in goals do

        if not(present(goal)) then

            false -> result;
            [^goal not achieved] =>

            ;;; could use quitloop() to exit here

        endif
    endfor;

enddefine;


/*

-- -- Test check_goals

setup_world();

database ==>

display_world();

check_goals([[man isat left][fox isat left]])=>
check_goals([[man isat right][fox isat right]])=>
check_goals([[man isat left][foc isat left]])=>


EXERCISE: alter check_goals to make it return either true or a list of
fulfilled goals.

*/


/*
-- -- Possibly useful utility procedure opposite(side)

*/

define opposite(side) -> other;
    ;;; given side, a word, find its opposite, another word

    if present( ![opposite ^side ?other] ) then

        ;;; do nothing. other will be the result

    elseif present( ![opposite ?other ^side] ) then

        ;;; do nothing. other will be the result

    else
        ;;; this should never happen!
        river_mishap([^side has no opposite]);
        false -> other
    endif;

enddefine;

/*

-- -- Test opposite
setup_world();

opposite("left") =>
opposite("right") =>
opposite("boat") =>

*/



/*
-- -- Possibly useful procedure sameplace(item1, item2)
*/

define sameplace(item1, item2) -> boolean;
    ;;; item1 and item2 are words, e.g. "man" "fox" "boat"
    lvars place;
    allpresent(![[^item1 isat ?place][^item2 isat ?place]]) -> boolean
enddefine;

/*
-- -- Test sameplace

setup_world();
display_world();

sameplace("grain", "fox") =>
sameplace("man", "fox") =>
sameplace("man", "dog") =>
putin("chicken");

sameplace("man", "chicken") =>
sameplace("fox", "chicken") =>
sameplace("chicken", "chicken") =>
takeout("chicken");
sameplace("man", "chicken") =>

*/


/*
-- -- Utility procedure: eat(item1, item2);
;;; not yet used, but might be

*/

define eat(item1, item2);
;;; dispose of edible, unattended item
    lvars place;
    remove(![^item2 isat ?place]);
    river_mishap([DISASTER: ^item1 has eaten ^item2 TOO BAD])
enddefine;



/*

-- Further Reading ----------------------------------------------------

Pop11 Primer: TEACH PRIMER, especially the section on internal semantics and
external semantics for a programming language.

TEACH RIVER
TEACH RIVERCHAT

Available after

    uses newkit

    TEACH PRBRIVER
    Shows how a program using poprulebase can search for a plan to cross
    the river, using either depth first or breadth first searching.

--- $usepop/pop/teach/riverworld
--- Copyright University of Birmingham 2011. All rights reserved.

*/