6
$\begingroup$

I would like to make a simple "game": Display a dialog with a random letter, if the same letter is pressed on the keyboard, the display changes into a string "Correct", and after a while a new random letter is generated.

However, in the below code, I am unable to get the "Correct" displayed on the screen. The Dynamic does not update timely.

rand := RandomChoice[FromCharacterCode /@ Join[Range[65, 90]]];
display = rand;

CreateDialog[
 Dynamic[Style[display, 600], TrackedSymbols :> {display}], 
 NotebookEventActions -> {"KeyDown" :> 
    Module[{k = Capitalize@CurrentValue@"EventKey", x = display},
     If[x === k,
      display = "Correct";
      Pause[1];
      display = rand]]}]

How to make it work?

$\endgroup$
7
$\begingroup$

What is going on

By default the Dynamic, EventHandlers and various controllers like Button and Slider use a PreemptiveLink to communicate with the kernel.

Synchronous versus Asynchronous Dynamic Evaluations about the PreemptiveLink:

[...] There is no queue; instead, the front end sends one evaluation at a time and waits for the result before continuing with its other work, [...] the front end is completely locked up, and no typing or other actions are possible. [...] (* so Dynamics' updates aswell *)

In your case display = "Correct"; Pause[1]; display = rand is that "one evaluation" during which the front end is locked up. Therefore there is no reaction for display's change.

Solutions

  • The shortest one would be to use a MainLink (Asynchronous/Queued).

    Unfortunately for Notebook/Cell/FrontEndEventActions we do not have Method->"Queued" like in Button or ActionMenu.

    You can check that this indeed works:

     Dynamic[x]
         (*check with and without that method*)
     Button["test", x = 1; Pause[1]; x = 2, Method -> "Queued" ]
    
  • FinishDynamic[]

    We can use FinishDynamic[] to tell the front end that it should (blindly) update all out of date Dynamics.

    This works well:

    display = "Correct"; FinishDynamic[]; Pause[1];  display = rand
    

    The Kernel waits till the front end confirms FinishDynamic[] end, and then proceeds with Pause. Quite often it is convenient, however, one should keep in mind that this function is going to prompt all dynamics checks.

    related: How to catch the “Preemptive” evaluations from within the main loop?

  • ScheduledTasks

    Scheduling a procedure is done immediately so it doesn't block the link anymore.

    Pause[1]; expr; can be translated to RunScheduledTasks[expr, {1}]

    Nesting and synchronizing multiple tasks may be tricky but here it works as intended.

    This automatically gives us:

    display = "Correct"; RunScheduledTask[display = rand, {1}]
    

Further readings

Evaluation order of Button[]'s action

Working with ScheduledTasks inside DynamicModules

Button with progress bar

$\endgroup$
  • 3
    $\begingroup$ The reason for not displaying 'correct' is of course that the notebook event actions are executed over the preemptive link. So it should work as intended when we can force the frontend to use the main link for executing the event actions. In many situtations we have an option Method->Queued for doing so, but I cannot find a similar option for notebooks. Do you know how this can be done? $\endgroup$ – Fred Simons Jul 13 '16 at 7:51
  • $\begingroup$ @FredSimons I've rephrased and polished my answer, do you think it lacks of something more? $\endgroup$ – Kuba Jul 17 '16 at 8:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.