I'm a newbie working through HTDP2 (Felleisen et al.) on my own but have gotten stuck on question #380 of chapter IV -Intertwined Data. The problem is within the context of creating a DSL but I am first reacquainted with a general FSM Simulator and provided with the following code:
; An FSM is a [List-of 1Transition]
; A 1Transition is a list of two items:
; (cons FSM-State (cons FSM-State '()))
; An FSM-State is a String that specifies a color
; data examples
(define fsm-traffic
'(("red" "green") ("green" "yellow") ("yellow" "red")))
; FSM FSM-State -> FSM-State
; matches the keys pressed by a player with the given FSM
(define (simulate state0 transitions)
(big-bang state0 ; FSM-State
[to-draw
(lambda (current)
(overlay (text current 12 "black")
(square 100 "solid" current)))]
[on-key
(lambda (current key-event)
(find transitions current))]))
; [X Y] [List-of [List X Y]] X -> Y
; finds the matching Y for the given X in alist
(define (find alist x)
(local ((define fm (assoc x alist)))
(if (cons? fm) (second fm) (error "not found"))))
The problem is then stated as follows:
Reformulate the data definition for 1Transition so that it is possible to restrict transitions to certain keystrokes. Try to formulate the change so that find
continues to work without change. What else do you need to change to get the complete program to work? Which part of the design recipe provides the answer(s)? See exercise 229 for the original exercise statement.
Exercise 229 introduces a structure type definition to keep track of the states and the transitions but since the problem statement asks me to stay within the provided find
function I am hard pressed to come up with a similar structure type of definition. Instead I came up with the following Data Definition:
; An FSM is a [List-of Transition]
; A Transition is a two-item list of the following form:
; (cons FSM-State (cons (cons KeyEvent (cons FSM-State '()))'()))
; data example
(define fsm-traffic (list (list "red" (list "r" "green"))
(list "green" (list "g" "yellow"))
(list "yellow" (list "y" "red"))))
Hence calling (find fsm-traffic "green")
I get (list "g" "yellow")
I have thus modified the on-key
clause as follows:
(lambda (current key-event)
(if (string=? key-event (first (first (rest (first transitions)))))
(second (find transitions current))
(error "invalid input")))
Now if I start the program with (simulate "red" fsm-traffic)
State0 is rendered and if I press "r" it goes to "green" FSM-State but then it won't accept "g" to go to the following state and so on.
If I begin the world-program with (simulate "yellow" fsm-traffic)
then FSM-State "yellow" is rendered but it will not transition to any other state (the error
clause is activated); similarly with "green" as the starting state.
My hunch is that since I defined fsm-traffic
with the "red" state first it accepts its input to transition to "green" but since the same is not happening with the other states big-bang
is not "juggling" the transitions
parameter right. But I just don't know how to fix that. I also don't know if I've gone wrong since the start with my data definition.
Thank you in advance for helping me out.
P.D. please let me know if I have followed the rules on posting on stackoverflow (this is my first post :).
You identified the source of the problem is correctly. You defined fsm-traffic
with the "red"
state and the "r"
transition first, and the on-key
handler only looks at the first
. It does this in the if
question:
(string=? key-event (first (first (rest (first transitions)))))
; ^ ^
; | this makes it only look at `"red"`
; this makes it only look at `"r"` within that
This if
question seems complicated. Just like we would do if it was a function with a signature and purpose, we can design the inputs, output, and purpose of this expression.
Which keys are valid depends on the transitions table, but it also depends on the current state. This is the reason why only "r"
is valid on the "red"
state, but "g"
is valid on the "green"
state. So its inputs are:
current : FSM-State
key-event : KeyEvent
transitions : FSM
Your existing expression ignores current
.
It is an if
question, so it should output a Boolean
. The purpose of this boolean is to determine whether the key is valid on the current state.
It should be true when there is any transition in the table for both the current
state and the key-event
.
This purpose statement with "any" and "both" sounds complicated enough that it deserves to be its own function. Using the inputs, output, and purpose we just made:
;; FSM-State KeyEvent FSM -> Boolean
;; Determines whether there is any transition in the FSM table for both the current
;; state and the key event.
(define (transition-for-state-and-key? state key table)
....)
Your current expression for the body this doesn't include the state
,
(string=? key (first (first (rest (first table)))))
But the actual body should depend on both state
and key
.
Just like this depends on both, the find
function should also depend on both state
and key
.
;; FSM-State KeyEvent FSM -> FSM-State
;; Finds the matching transition-state from the given state with the given key in the table.
(define (find-transition-state state key table)
....)
And note the relationship between transition-for-state-and-key?
and find-transition-state
. The transition-for-state-and-key?
function should return true exactly when find-transition-state
won't error.
This should be enough for you to continue.