[Microbit-Python] Game API objects I want

Nicholas H.Tollervey ntoll at ntoll.org
Wed Oct 7 10:51:24 CEST 2015


My 2c...

I've quickly skimmed... (I'm currently at work).

Basically, I support Larry's outlook that we should put common /
complicated things into a module so kids get to work on fun stuff rather
than boilerplate.

I'll re-read the details and respond more fully this evening or tomorrow
morning depending on how busy I get (bear in mind I've just returned
from PyCon India and my in-box is overflowing).

I think the process we went through with the music namespace was very
useful. Could we do the same again for a microbit.game module (for
instance)? Think of this as being like a mini-PEP :-P ... Also, Mark's
earlier emails (also on my to-reply-to list) about display/image related
things are also in the same sort of problem space (there's this
complicated / memory constrained thing, can we do it simpler?)

Thoughts..?

N.

On 07/10/15 09:26, Larry Hastings wrote:
> 
> 
> Having written two games on the micro:bit--one of which actually
> works!--and having some previous experience writing video games, I'd
> like to get up on my soap box and hold forth.
> 
> There are a couple of technologies that /nearly //every//game/ will
> use.  (One that /literally every game/ will use!)  And given that
> they're mildly complicated to do properly, and can consume a good chunk
> of the micro:bit's precious RAM to implement, I strongly suggest they be
> written in C++ and provided with MicroPython on the micro:bit.  I don't
> know if I'm up to writing (and debugging!) them on the micro:bit
> itself.  Maybe I could make a first pass in normal C++ on Linux, and
> someone else could port them to the micro:bit?
> 
> I can name three such technologies, as follows.
> 
> 
> *1: A debounced accelerometer scaling object*
> 
> My slalom game simply reads the current accelerometer value, restricts
> it to a specific range, then scales it down to the range I want to use
> and uses the resulting value, like so:
> 
>     [-1024                    1023]  <- accelerometer
>     [  0  |  1  |  2  |  3  |  4  ]  <- slalom column
> 
> Values lower than -1024 are ceilinged to -1024, values greater than 1023
> are floored to 1023.  I then add 1024 to the value, multiply by 5,
> divide by 2048, and call int().  I get a number from 0 to 4
> (inclusive).  Simple, done.
> 
> The problem is that it kind of sucks.  If you hold the micro:bit /just
> so/, on the border between two values (say 2 and 3) it quickly and
> seemingly-randomly bounces back and forth between the two.  This can
> make playing slalom kind of tricky.
> 
> Most of the code in the scrolly image demo and whack-a-mole game went
> into writing a debounced accelerometer position reader class.  The idea
> is simple enough: instead of simply taking the range of the
> accelerometer you're interested in, and scaling it to the range you
> want, you also establish "dead zones" between regions like so:
> 
>     [-512                      511]  <- accelerometer
>     [  0 |a| 1 |b| 2 |c| 3 |d| 4  ]  <- range with dead zones
> 
> Here I've labeled the dead zones with lowercase letters (a, b, c, d). 
> Dead zones magnified for illustrative purposes; in practice they're much
> smaller.
> 
> The idea is, the value reported when you're in a "dead zone" depends on
> the previous value.  The dead zones establish dynamic low-water and
> high-water marks for how far you'd have to go to cross over into another
> zone.
> 
> For example, if on frame N you were clearly in zone 1:
> 
>     [  0 |a| 1 |b| 2 |c| 3 |d| 4  ]
>                  *
> 
> And on frame N+1 you were in zone b:
> 
>     [  0 |a| 1 |b| 2 |c| 3 |d| 4  ]
>                 *
> 
> the debounced reader would then report that you're still in zone 1.  You
> have to force it all the way past the dead zone for it to report you as
> being in zone 2.  If, on a subsequent frame P, you were definitely in
> zone 2:
> 
>     [  0 |a| 1 |b| 2 |c| 3 |d| 4  ]
>                          *
> 
> And on frame P+1 you were in zone b:
> 
>     [  0 |a| 1 |b| 2 |c| 3 |d| 4  ]
>                 *
> 
> the debounced reader would then report that you're (still) in zone 2.
> 
> 
> One way to think of it is that the dead zones prefer the zone nearest to
> the previous accelerometer reading.  So if the accelerometer is
> currently in the spot marked with an asterisk, we can pretend that the
> zones map out as follows:
> 
>        *
>     [  0   | 1   | 2   | 3   | 4  ]
> 
>              *
>     [  0 |   1   | 2   | 3   | 4  ]
> 
>                    *
>     [  0 |   1 |   2   | 3   | 4  ]
> 
>                          *
>     [  0 |   1 |   2 |   3   | 4  ]
> 
>                                *
>     [  0 |   1 |   2 |   3 |   4  ]
> 
> 
> Long story short, the reason the scrolly image demo is so pleasant to
> use is because of this debounced accelerometer class.  If I didn't have
> that, it'd have the same bouncing-between-two-values problem that the
> slalom game has.  I suggest that most games on the micro:bit will use
> the accelerometer, and if they simply scale it they'll have the bouncing
> problem.
> 
> Of course, my debounced accelerometer class appears to consume most of
> the RAM of the micro:bit.  And that's /after/ I hard-coded it to some of
> the behavior I wanted.  A general-purpose debounced accelerometer class
> might not even fit!
> 
> I could write a simple class that does this in C++, and let somebody
> else who understands MicroPython could port it to the runtime and get it
> working on the micro:bit.
> 
> I think the API would look something like this:
> 
>     class AccelerometerScalingDebouncer:
>         def __init__(self, fn, input_min, input_max, output_min,
>     output_max, dead_zone=0.08):
>             ...
>         def read(self):
>             ...
> 
> fn would be the function to call to do a reading (like
> microbit.accelerometer.get_x).  input_min and input_max would establish
> the range we'd clip the input to.  output_min and output_max would
> establish the range of values returned (inclusive).  dead_zone would
> establish how big the dead zone around each value would be, and we could
> hide this from the students (or handwave it away, "don't worry about it,
> it does the right thing for you").
> 
> read() would read the current value, clip it, debounce it, scale it, and
> return the resulting value.
> 
> Again, you can see a working example of a class like this in my "scrolly
> image" demo.  I've attached it here so you don't have to go hunting for
> it.  It runs on the micro:bit.
> 
> 
> *2: A debounced reader for all the buttons*
> 
> A moment's thought suggests: we want a debounced button class, too. 
> Both my games used the buttons, and I had to write debouncing code for both.
> 
> The basic idea: if the user presses the button, you often want to
> process the button press exactly once.  So code like this doesn't work:
> 
>     next_frame = 0
>     while True:
>         delta = next_frame - microbit.time()
>         if delta > 0:
>             microbit.sleep(delta)
>             continue
>         next_frame = microbit.time() + 50
>         if microbit.button_a.is_pressed():
>            do_thing()
> 
> The problem: if the user holds down the button, you'll call do_thing()
> every 50ms.
> 
> I've seen people mention that there's actually an event queue in the
> DAL.  Maybe just exposing this somehow is sufficient?
> 
> I think what I want is an object like this:
> 
>     class ButtonDebouncer:
>         def __init__(self, a=True, b=True, both=False, delay=100):
>             ...
>         def poll(self):
>             ...
>         def a_pressed(self):
>             ...
>         def b_pressed(self):
>             ...
>         def both_pressed(self):
>             ...
> 
> You pass in to the constructor booleans indicating which buttons you
> care about: a, b, or both.  If both=True and either a=True or b=True,
> "delay" indicates how long to wait after one button is pressed before
> giving up and assuming we're not getting both buttons pressed.
> 
> If you press and hold button A, after the next time you call poll(),
> a_pressed() would return True /once/.  After that first time it would
> return False until you released A, called poll(), then pressed A and
> called poll().//
> 
> The object would internally use microbit.time() for time.  __init__
> would call poll().
> 
> I tried to write an example of this in Python for the micro:bit. 
> However my first try hit a MemoryError.  (Argh!  Double-argh!)  So I've
> attached my attempt, which you can consider a non-working mockup.
> 
> 
> *3: A simple scheduler*
> 
> /Every/ realtime game would want this.  And I doubt people will write
> non-realtime games on the micro:bit.
> 
> The basic idea: we want to do something at future time x.  And we also
> might want to do something every y milliseconds.
> 
> Python actually ships with a module to do this, "sched".  The
> sched.scheduler class is powerful and general-purpose.  But it's way
> more general than we need, and also it provides an inconvenient
> callback-based API.  ("I don't like callback-based APIs, I always get
> them wrong." --GvR)
> 
> How about this:
> 
>     class Scheduler:
>         def __init__(self):
>             ...
>         def schedule(self, event, delta, repeat=False):
>             ...
>         def clear(self):
>             ...
>         def run(self):
>             ...
> 
> 
> schedule() would add an event to the scheduler.  "event" is any object,
> "delta" is how many ms in the future we want the event to happen, and if
> repeat is True the event will recur every "delta" ms thereafter.
> 
> clear() would clear the current events from the schedule.
> 
> run() would sleep until the next scheduled event.  It would return the
> "event" object associated with the event.  If you run() and there are no
> currently scheduled events, it throws an exception.
> 
> I mocked this up in normal Python, attached.  I also made it easy to
> port to the micro:bit by simulating the microbit module.  To run on the
> microbit, just return the first couple paragraphs of stuff (it's marked)
> and add "import microbit" to the top.  Works fine.
> 
> 
> What do you think?  Anyone interested enough to work on the micro:bit
> implementations of these?
> 
> 
> //arry/
> 
> 
> _______________________________________________
> Microbit mailing list
> Microbit at python.org
> https://mail.python.org/mailman/listinfo/microbit
> 


-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 473 bytes
Desc: OpenPGP digital signature
URL: <https://mail.python.org/mailman/private/microbit/attachments/20151007/7d8e993e/attachment.sig>


More information about the Microbit mailing list