[F/OS] Game: SchulteTable

SchulteTable is a fast-paced cognitive training application built on MIT App Inventor.

Demo

All Blocks


syncRecyclerGrid (Dynamic Recycler Allocation):

Generates structural layout instances on-demand, recycling existing nodes and updating layout dimensions smoothly without rebuilding views.

[Request Grid Size: N×N]
│
▼
┌───────────────────────────────────────┐
│ STEP A: Dynamic Pool Validation       │
│ Is Total Cells > Global Pool Size?    │
└──────────────────┬────────────────────┘
├─► YES: Instantly inflate missing nodes using schemas.
└─► NO : Skip inflation (Saves CPU cycles).
│
▼
┌───────────────────────────────────────┐
│ STEP B: Structural Hydration & Sizing │
│ Loop through Global Pool Size         │
└──────────────────┬────────────────────┘
├─► Index <= Total Cells ──► Set Visible = True, Compute Flex Width/Height
└─► Index > Total Cells  ──► Set Visible = False (Soft-delete to pool)

GenerateAndShuffleList:

Handles data generation and randomizes elements in memory without creating duplicate lists.

========================================================================
                 PHASE 1: LINEAR SEQUENCE GENERATION
========================================================================

  [ Create Empty List ]  ───►  Loop from 1 to maxLimit (Step +1)
                                      │
                                      ▼
                      Resulting 'shuffledNumbers' List:
                      ┌───┬───┬───┬───┬───┬───────┬───┐
        Index (1-based)│ 1 │ 2 │ 3 │ 4 │ 5 │  ...  │ N │
                      ├───┼───┼───┼───┼───┼───────┼───┤
        Stored Value   │ 1 │ 2 │ 3 │ 4 │ 5 │  ...  │ N │
                      └───┴───┴───┴───┴───┴───────┴───┘
                      (Allocated once, perfectly ordered)


========================================================================
               PHASE 2: IN-PLACE FISHER-YATES SHUFFLE
========================================================================

  The loop sets 'swapFromIndex' at the end of the list (N) and steps
  backward down to 2 (Step -1).

  EXAMPLE SNAPSHOT: Loop is currently at Index 5 (swapFromIndex = 5)

   Step A: Roll a random integer from 1 to 5.
           Let's say 'swapToInIndex' rolls a 2.

   Step B: Extract values into temporary local memory registers.
           ┌──────────────────────────────────────────────┐
           │ • swapFromValue = List[5] (Value: 5)          │
           │ • swapToValue   = List[2] (Value: 2)          │
           └──────────────────────────────────────────────┘

   Step C: Perform the atomic index swap.

                 ┌───────────────────────────────┐
                 │  [2] is written to Index 5   │
                 ▼                               │
         ┌───┬───────┬───┬───┬───────┐            │
   Index │ 1 │   2   │ 3 │ 4 │   5   │            │
         ├───┼───────┼───┼───┼───────┤            │
   Value │ 1 │   2   │ 3 │ 4 │   5   │            │
         └───┬───────┴───┰───┴───────┘            │
             │           ┃                        │
             │           ┗━━━━━━━━━━━━━━━━━━━━━━━━┛
             └───────────────────────────────────┐
                                                 ▼
                                  [5] is written to Index 2


   State After Single Swap:
   ┌───┬───────┬───┬───┬───────┐
   │ 1 │   5   │ 3 │ 4 │   2   │  <─── Index 5 is now locked!
   └───┴───────┴───┴───┴───────┘      The loop decrements to Index 4.

Asynchronous Guarded Lifecycle Loop

StartNewMatch & when any Button.Click

Runs isolated game timer threads and introduces non-blocking debounced interaction filters (GuardGate) to eradicate race conditions and double-click registration bugs.

MATCH INITIALIZATION

================================================================================
                        MATCH INITIALIZATION (StartNewMatch)
================================================================================

       [ Trigger StartNewMatch ]
                   │
                   ▼
       Initialize Tracking Pointer (expectedNumber = 1)
       Map Shuffled Numerical Values onto UI Grid Buttons
                   │
                   ▼
        Is Game Mode set to 'Race Mode'?
                   │
         ┌─────────┴─────────┐
         ▼ YES               ▼ NO (Survival Mode)
  ┌─────────────────┐ ┌───────────────────────────┐
  │ Reset to "00:00"│ │ Reset to "45"             │
  │ Async Timer:    │ │ Async Timer:              │
  │ Ticks @ 100ms   │ │ Ticks @ 1000ms (Max 45)   │
  └────────┬────────┘ └─────────────┬─────────────┘
           │                        │
           ▼                        ▼
  [Compute & Render]  [Decrement Display Counter]
  mm:ss on Main Thread   Loop complete -> Run Game Over
                                    

:rocket: Architectural Optimization: AsyncDelay vs. Native Clock

Used AsyncDelay extension instead of heavy native Clock components, ensuring smooth performance and preventing UI thread blocking during rapid grid updates.

:video_game: Dual-Mode Interval Routing

By moving away from standard monolithic millisecond counters, the app isolates two distinct interval engines utilizing the same background execution thread wrapper (id: "GameTimer").

1. :chequered_flag: Race Mode (Time Attack)

Ticks every 1,000ms (1 second) to compute and display a classic Minutes:Seconds elapsed timer.

2. :hourglass_not_done: Survival Mode (45s Countdown)

Ticks every 100ms (tenths of a second) to maintain high-precision countdown tracking, rendering fractional seconds (Minutes:Seconds.Tenths) smoothly without dropping frames.

// Clean lifecycle termination when the final matching sequence is met:
CALL AsyncDelay.ManageTask(id: "GameTimer", action: "IntervalCancel")


BUTTON INTERCEPTION

================================================================================
                   BUTTON INTERCEPTION (when any Button.Click)
================================================================================

                 [ User Taps A Grid Cell Button ]
                                │
                                ▼
                     Is Component dynamic?
                                │
                                ▼ YES
               Extract ID String & Stripped Index
               (e.g., "cell_button_14" ──► index = 14)
                                │
                                ▼
               Does Cell Value == global expectedNumber?
                                │
                                ▼ YES
             ┌─────────────────────────────────────┐
             │    AsyncDelay1.GuardGate Enters     │
             │   Target ID: "temp-guard-cell-14"   │
             └──────────────────┬──────────────────┘
                                │
             ┌──────────────────┴──────────────────┐
             ▼                                     ▼
    [ FIRST INSTANT TAP ]                [ MULTIPLE RAPID TAPS ]
   • Gate: UNLOCKED                     • Gate: LOCKED
   • Turn Button Green                  • Event Intercepted & Dropped
   • expectedNumber = expectedNumber + 1   • Zero State Corruption
             │
             ▼
    Is expectedNumber > maxLimit?
             │
             ▼ YES (Win State Reached)
   ┌──────────────────────────────────────┐
   │ 1. CALL AsyncDelay: "IntervalCancel" │ ──► Instantly Freezes
   │ 2. Pop "You Win!" Dialog Notification│     'GameTimer' Thread
   └──────────────────────────────────────┘

:puzzle_piece: Core Extensions Used

  • Dynamic Components – Utilized for structural recycler pool rendering and view recycling.
  • Async Delay – A custom-tailored background optimization framework for non-blocking state loops.
  • Flexbox – Handles complex, responsive grid layout child arrangements seamlessly.

:open_file_folder: Open Source & Contributions

SchulteTable.aia (427.6 KB)

Feel free to ask questions about the game

Developer: Hridoy

Architecture Pattern: Dynamic Recycler Matrix Pool


5 Likes

cool

Could this have been done without any extensions ?

1 Like

Yes it can be done without any extension

1. Replace Async Delay with Clock component

if(global currentGameMode = "RACE"){

  SET lbl_G_SB_TimerValue.Text = "00:00";
  SET Clock1.TimerInterval = 100;

} else if(global currentGameMode = "SURVIVAL"){

  SET lbl_G_SB_TimerValue.Text = 45;
  SET Clock1.TimerInterval = 1000;

}

SET Clock1.TimerEnabled = true;

When Clock1.Timer(){

  if(global currentGameMode = "RACE"){
    // Do Format Date mm:ss
  } else if(global currentGameMode = "SURVIVAL"){
    SET lbl_G_SB_TimerValue.Text = lbl_G_SB_TimerValue.Text - 1

    if(lbl_G_SB_TimerValue.Text == 0){
      SET Clock1.TimerEnabled = false;
      // Show Notifier Game Over
    }
  }

}

On Button Click:

if (global expectedNumber > global maxNumber){
  SET Clock1.TimerEnabled = false;
  // Show Notifier Win
}

2. Replace Dynamic Grid

Option 1: Create grids manually and map them in lists in blocky

Option 2: Create Grids with Canvas

I don't have much experience with Canvas, but I suppose it would be quite similar to how you created the circles in Peg Solitaire:

1 Like

A background on Shulte Tables

If you play by the rules, and maintain focus on the centre box, it is a bit weird at first (like making you feel seasick)...:slight_smile:

2 Likes

Had a go at creating Shulte Tables on a canvas with no extensions. Seems to work OK, I shouldn't have started where I started from, leading to extra code required to handle number selecting and box colouring, but hey-ho...

Main components: canvas, clock, buttons and labels

Blocks

CanvasShulteTable.aia (16.5 KB)

Ich kann es nicht öffnen…

You need to open aia projects in Appinventor, then build (compile) them to create an apk you can install on your phone.

Thank you! ʘ⁠‿⁠ʘ

if(global currentGameMode = "RACE"){

SET lbl_G_SB_TimerValue.Text = "00:00";
SET Clock1.TimerInterval = 100;

} else if(global currentGameMode = "SURVIVAL"){

SET lbl_G_SB_TimerValue.Text = 45;
SET Clock1.TimerInterval = 1000;

}

SET Clock1.TimerEnabled = true;

hi. And where do these lines of code show up at MIT?

It's not from MIT App Inventor. It's just pseudocode written as plain text to illustrate the logic. You can follow the code and recreate the equivalent blocks in the Blockly editor.

Here is my version with CompCreator extension, 134 blocks used.
Only basic function implemented, no clock / time count functions.

SchulteTable_CompCreator.aia (40.0 KB)

3 Likes