25 August 2021

When the going gets 'gui'...

¯\_(ツ)_/¯

So, in the last post I laid out my plan the manage the different GUI screens for the tic-tac-toe app, promising that, with all of the big decisions already made, today would be chock-full of implementation and smooth-sailing. For the most part it actually was! It went slower than I thought, but I made steady progress. The only unforseen issue surfaced when I had to transition to a screen with the same exact layout and widgets as the previous. Here's the first of the two screens in play:

Configure Player X

And the second:

Configure Player O

The only difference between these two screens is the "X" vs. "O". I observed that sometimes the second screen seemed to be skipped altogether (???). Then I realized that this had to do with the combination of fast frame rates and slow mouse clicks. It's very probable that at 30 frames per second, a simple mouse click will often straddle multiple frames. When the mouse is clicked on the first screen the update function does the following:

  1. detects the mouse click using (quil/mouse-pressed?)
  2. persists (on the state data structure) the option corresponding with the position of the mouse cursor
  3. persists (on the state data structure) that the subsequent frames should transition to the next screen

This all happens VERY fast. Meanwhile, the slow human finger is still pressing the mouse button. So, when the next frame comes around and we draw and update the second screen the above steps are executed again without the user even seeing the second screen or getting a chance to move the mouse to a different option. :(

So, my current solution involves saving a bit more information on the state data structure to keep track of whether we can trust the value returned from (quil/mouse-pressed?). Here's a truth table that conveys what I wanted to achieve:

:ready-to-click? (in) (q/mouse-pressed?) (in) clicked? (out) :ready-to-click? (out)
true true true true
false true false false
false false false true
true false false true

And here's the code that implements the above logic:

(defn process-click [state]
  (let [ready?   (:ready-for-click? state)
        pressed? (q/mouse-pressed?)]
    (assoc state :clicked? (and ready? pressed?)
                 :ready-for-click? (not pressed?))))

Maybe there's some quil feature or another approach that is better or more appropriate for this kind of problem, but this works. In any case, I should have a fully-functional game by the end of tomorrow...