A O E M U A R Z O P N T I N S D H H I N A E A L R E R M O L I C E N I A O N T S E G E S
Thames Seine Orinoco Nile Euphrates Darling Amazon
Creating such a puzzle is quite easy. The crux is randomizing the location of letters from the names. This is easy if we forget about the letters in a word and just represent each word by repeating an id number for each letter. We may then permute the numbers without worrying about spelling order. When we have a sufficiently random order we can just replace each number front to back by the next letter from the word with that id. Here's how it goes:
First we define the words giving each an id number. At the same time we define and empty text.
(deffacts rivers
(river 1 T h a m e s)
(river 2 S e i n e)
(river 3 O r i n o c o)
(river 4 N i l e)
(river 5 E u p h r a t e s)
(river 6 D a r l i n g)
(river 7 A m a z o n)
(text #)
)
Now to face the randomization problem. We can overlook the word's letter order if we let the word be represented by a copy of its id for each original letter:
(defrule addRiver
(BUILD)
?f<-(text # $?T)
(river ?i $?R)
(test (not (member$ ?i $?T)))
=>
(retract ?f)
(loop-for-count (length$ $?R)
(bind $?T (create$ $?T ?i)))
(assert (text # $?T)))
When this rule is repeated with each individual river, we get a 'text' factthat looks like this:
f-16 (text # 7 7 7 7 7 7 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 4 4 4 4 3 3 3 3 3 3 3 2 2 2 2 2 1 1 1 1 1 1)
That is, it shows where the letters of word n are to be placed. Now we randomly permute those to new locations:
(defrule randomize
(RANDOMIZE)
?f<-(text # $?A ?x $?B ?y $?C)
=>
(retract ?f)
(assert (text # $?A ?y $?B ?x $?C)))
This rule swaps to digits from the text fact. If we use depth or breadth strategies nothing will change! We can test this by asserting RANDOMIZE and firing the rule 10 times by giving the command "(run 10)" The result under DEPTH is below. (Try breadth too if you like):
CLIPS> (assert (RANDOMIZE))
<Fact-17>
CLIPS> (run 10)
CLIPS> (facts)
f-0 (initial-fact)
f-1 (river 1 T h a m e s)
f-2 (river 2 S e i n e)
f-3 (river 3 O r i n o c o)
f-4 (river 4 N i l e)
f-5 (river 5 E u p h r a t e s)
f-6 (river 6 D a r l i n g)
f-7 (river 7 A m a z o n)
f-9 (BUILD)
f-17 (RANDOMIZE)
f-27 (text # 7 7 7 7 7 7 6 6 6 6 6 6 6 5 5 5 5 5 5 5 5 5 4 4 4 4 3 3 3 3 3 3 3 2 2 2 2 2 1 1 1 1 1 1)
The solution is to insist on a random strategy. Instead of continually selecting the same instantiation, we select a random instantiation. This works immediately.
CLIPS> (set-strategy random)
depth
CLIPS> (run 10)
CLIPS> (facts)
f-0 (initial-fact)
f-1 (river 1 T h a m e s)
f-2 (river 2 S e i n e)
f-3 (river 3 O r i n o c o)
f-4 (river 4 N i l e)
f-5 (river 5 E u p h r a t e s)
f-6 (river 6 D a r l i n g)
f-7 (river 7 A m a z o n)
f-9 (BUILD)
f-17 (RANDOMIZE)
f-37 (text # 7 7 7 2 7 2 5 4 6 6 5 6 6 6 6 2 5 5 2 5 5 5 4 7 4 4 3 3 3 3 3 3 5 6 5 2 7 3 1 1 1 1 1 1)
For a total of 11 facts.
CLIPS>
In the final procedure we will fire the randomize rule 100 times. That will give us a quite jumbled version of the digits in text. Now we just need to restore the letters of the river names. This is where the previously unmentioned "#" symbol comes into play. We we use it to separate the prefix of the text which has be 'decoded' from the tail which has not. Perhaps the rule will make this clear. (No I didn't think of it at first either!)
(defrule addRiverLetter
(DECODE)
?f<-(text $?PRFX # ?n $?SFFX)
?g<-(river ?n ?c $?Rest)
=>
(retract ?f ?g)
(assert (text $?PRFX ?c # $?SFFX)
(river ?n $?Rest)))
This rule uses the river for the next id to find the next decoded letter. The text is changed to a new longer prefix and the currently first of the remaining river letters is dropped. The next number to be decoded is always just after the #. Such a rule will run as long as there is a digit for ?n and a matching river with a letter for ?c. But we made one digit for each letter and just messed then about.
One last point has been overlooked. Each of the rules has an extra fact that is not important to the actions of the rule. They are there to stage the use of the rules. That is, we want to make full digitized 'text, then scramble the digits, then decode the result. These staging fact allows to control when a rule may operate by asserting or retracting the corresponding facts.
Recall that we also need to be sure we use random strategy during randomization. The entire package is complex enough to deserve a procedure (in CLIPS a function) of its own:
(deffunction makePuzzle ()
(reset)
(bind ?f (assert (BUILD)))
(run)
(retract ?f)
(bind ?f (assert (RANDOMIZE)))
(bind ?s (set-strategy random))
(run 100)
(retract ?f)
(set-strategy ?s)
(assert (DECODE))
(run)
)
Here the process. Start by restoring the original river and text data. Now assert the build fact remembering as ?f which fact it is. Run until the text is created. The system will stop at that point. Be neat. Retract the build fact before asserting the randomize fact. Set the strategy to random while remembering the old strategy as ?s. Randomize by letting the randomize rule run 100 times. Remove the randomize fact to prevent any further changes. Be courteous, Restore the original strategy. Now assert decode and finish. We get get a random result like the one below:
CLIPS> (makePuzzle)
CLIPS> (facts)
f-0 (initial-fact)
f-118 (DECODE)
f-174 (river 5)
f-192 (river 6)
f-194 (river 2)
f-196 (river 1)
f-202 (river 3)
f-204 (river 7)
f-205 (text O A E r u T m N i h p D i h l a a r a S t r a m l i e s n z n e i o e n g e s o c o n e #)
f-206 (river 4)
For a total of 10 facts.
CLIPS>
Of course, we could make our function more useful. We could add parameters so that any choice of words could be used. We would have to add code to expand the words to letters and to assert the 'river' (word?) facts. But like most programming, that would be tedious detail.
Variation: find a minimal sequence of letters that preserves the words. TBA
No comments:
Post a Comment