Building a 10-Counter (ONGOING)

A living article to document development for an educational tool.

Building a 10-Counter (ONGOING)

A Teaching Tool

My son is in kindergarten where they are learning to not only count, but count from the middle of a sequence. It's kind of impressive, but our teacher did inform us in our first conference that kindergarten is like first grade when she was a kid. After further looking at my wife and me a little more she added "...well, maybe second grade for you guys." Rude! (Just kidding, Mrs. K is a great teacher and our son loves her.)

Nevertheless, she works with "10-counters" to demonstrate a unit of 10. So if you were counting the number 14, you would have 2 10-counters (one with 10 round chips in it, the other with 4). The idea is that kids are learning to count starting at 10 and they can then move on to just counting the chips in the other 10-counter. I suppose that counting this way could also develop understanding of what the 2 in the number 25 actually means or how to better count by groups.

I saw Mrs. K working with a 10-counter on an overhead projector and thought that an online practice tool could be created to replicate this experience for kids at home or help teachers do this on the fly. This article is intended to show how I build this from beginning to end. It is also not intended to be a static article, nor a wiki. I'm calling this a living article, and I'll track changes and status below.

Design & Status

Here are features I think this tool should have:

  • Counting mode: count from a starting number to an ending number at an adjustable rate, demonstrating the number along with 10-counters. (added 1-17-25)
    Status: Close to done, including Auto count or manual count. 4/25 - Working on visual stuff (how to implement resizing counters dynamically)
  • Random (Flash card) mode: take a random number from a specified range and display the 10-counters with a "show answer" button and a "next" (added 1-17-25)
    Status: Development
  • Redistributable: I will host and list it on a service page on my site, but for schools that may want to run it under restricted environments, (and as a learning exercise) I should try to make it available on docker. (added 1-17-25)

    Status: Planning/Incomplete

Initial Planning

This simple application should need no database. I'm planning on writing this application as a .Net blazor application. I like being able to use HTML/JS libraries but also being able to wrap that in blazor's component driven design approach (much like react). This will also allow me to use some of Visual Studio's docker tooling which seems like a nice place to be.

There are a few layers here:
1. User Interface - controls forms for how to interact with the 10-counters or user preferences
2. A mid component will take user preferences and create the needed 10 counters and then interact with them appropriately.
3. The basic 10-counter component. Given a number between 1 and 10, it should display the appropriate number of circles.

The Basic 10-Counter

Here is the initial 10-counter design using D3/SVG interaction. Note, there are 2 modes for filling the 10-counter: left and natural (top bar first). I would also like to add another mode for vertical display or horizontal.

For the purposes of this demonstration, the size of the 10-counter is static. It lives side-by-side with the SVG element, the form controls (simple checkbox), and even the control logic of how it should "count" up and reset. To break this out into the intended design layers and get to the true nature of a 10-counter component is more complex. Think about how you would now count up to 20.

Where does the logic of counting live? The middle layer. This layer will get speed and how high to count from the user layer. The middle layer must be able to tell the 10 counter component to add the nth circle to its counter, display that, and then a second or so later, tell the counter component to add another circle (now n+1). The middle layer must also determine if it's time to call a new 10 counter and where that counter should be displayed.

If the 10-counter component simply returns an SVG component with a self-maintained state, this helps alleviate some complexity from the middle layer that can now be concentrated on layout and logic and not managing direct display commands. Since many of the commands to add a circle are really just javascript, these would likely live in a library file and the 10-component object may just end up looking like a shell that in turn calls these commands to populate or work on an SVG element.

Sanity Check/POC

So one of the things that I needed to learn up on is how a parent component could setup referencing for dynamically created components. While I'm writing the program, I don't know if the user will need 1 single 10-counter, or 5 of them. It's simple math to decide how many we need when the user gives us the value. Something like this would give us the number of counters we'd need:

//assuming a parameter of name "CountTo" 
int sizeCounters = (int)Math.Ceiling(CountTo/10.0); 

Ceiling because if we were given a CountTo of 24, we would need 3 10-counters to handle all them dots.

We can figure this out in the parent component's OnParametersSet event, and then we can initialize a private field array of 10-counters to the appropriate size:

//assuming an uninitialized 10-counter array called counters
counters = new Counter[sizeCounters];
for (int i=0; i < sizeCounters; i++)
{
  counters[i] = new Counter();
}

In the rendering section of our parent controller, we can dynamically render and setup references to those counters like so:

<div style="border:solid 1px black;">
@foreach (Counter counter in counters)
{
  <Counter @ref="counters[Array.IndexOf(counters, counter)]" />
}
</div>

We can reference those components and their methods now by index. In the below example, I use a button to add 4 to a series of components until we hit the "CountTo" limit of 24 using this function:

//assuming an int field for tracking the current value
private void addToCounter(int a)
{
  if(CurrentCount < CountTo)
  {
    int addAmount = CountTo >= (CurrentCount+a)? a : (CountTo - CurrentCount);
    CurrentCount += addAmount;

    if (counters != null) {
      for (int i=0; i<counters.Length; i++)
      {
        addAmount = counters[i].AddReturnRemainder(addAmount);
        if (addAmount == 0) break;
      }
    }
  }
}

Each counter component has a function for adding a specific number to its tracking value. If that tracking value is going to hit its limit of 10, it will return the difference it can't handle. Imagine trying to put 12 balls into a bucket designed for 10. In this case you will have 2 balls left over you need to put into a new basket. That's the driving gist of this approach. If you have no balls left over, there's no need to move on to the next bucket.

Demo of POC

Now suppose we have a middle component that instead of adding 4, is passed how fast it should continually add the number 1 up to a certain point. This would be an "Auto" counter. This is the "Start Counting" logic called by a button click for such a component:

public async Task StartCount()
{
  while(CurrentCount < CountTo)
  {
    await Task.Delay((int)(1000 * AutoSpeed));
    int addAmount = 1;
    CurrentCount += addAmount;

    if (counters != null)
    {
      for (int i=0; i<counters.Length; i++)
      {
        addAmount = counters[i].AddReturnRemainder(addAmount);
        if (addAmount == 0) break;
      }
    }
  }
}

In the above code, suppose we are counting to 11 (CountTo=11) and we are currently to the number 10 (CurrentCount=10). When we start to loop through our counters, we go to the first one which should have an internally tracked number of 9. We add 1 to it, and because it can handle 1 more, it will change its internal number to 10 and return 0 to this looping function. The last inner for loop will then break out because it has successfully added "1" somewhere. Now the loop will start over because 10 is less than 11 so it will need to add another one. This time our CurrentCount will be 11 (because we are going to be adding the eleventh "dot"). The last inner for loop will try to add 1 to the first counter, it will politely decline by returning 1 (which is the amount still needing to be added somewhere). This will then be handed to the next counter that has been patiently waiting and it will update it's internal tracking number to 1 and return 0.

The advantage to shaping logic this way is it would be easy to change this auto counter to count by 2 or any other number. If we set the "addAmount" variable within the loop to 3, it would try add 3 dots with every loop. We would have to adjust logic for how many times the main while loop performs and how to bring the addAmount in as a parameter but our groundwork is level enough for add-ons later if we chose to do this.

Here's kind of what this general POC for auto counting would look like.

Auto Counter Demo

I'm curious on the jitter. The numbers indicate a few variables I'm checking, but the 20 is what we are counting to, the .5 indicates the speed. These numbers should increment every .5 seconds (medium is 1 second). Sometimes the numbers seem a little fast or slow. Curious.

I think actually showing dots instead of numbers is the next step.

Article Log

  • 1/17/25 - Article of intent posted
  • 1/19/25 - Initial Planning, starting development
  • 1/24/25 - Rewrote initial planning, and added basic 10-counter demo and discussion
  • 3/25/25 - POC for basic component layout provided.
  • 4/11/25 - POC for auto counter added, cleaned up article some, removed files
  • 4/25/25 - Have working manual/auto counter, but the presentation from beginning to end is not great. Working on visual fixes. Very few updates to article.