The Ravel kit #
For use with a simulator (more information here):
- Wokwi simulation;
- configuration: .
Run button to (re)launch the application.
Headless (without GUI) #
Reset #
Resetting the kit
import ucuq
lcd = ucuq.HD44780_I2C(16, 2, ucuq.SoftI2C(6, 7))
rgb = ucuq.WS2812(8, 20)
buzzer = ucuq.Buzzer(ucuq.PWM(5))
oled = ucuq.SSD1306_I2C(128, 64, ucuq.I2C(8, 9))
Buzzer #
Beep #
import ucuq
FREQ = 440
buzzer = ucuq.Buzzer(ucuq.PWM(5))
buzzer.on(FREQ)
ucuq.sleep(1)
buzzer.off()
Scales #
import ucuq
FREQ_LOW = 220
FREQ_HIGH = 880
DELAY_TIME = .1
NUM_STEPS = 24
buzzer = ucuq.Buzzer(ucuq.PWM(5))
coeff = (FREQ_HIGH/FREQ_LOW)**(1/NUM_STEPS)
for i in range(NUM_STEPS):
buzzer.on(int(FREQ_LOW * coeff ** i))
ucuq.sleep(DELAY_TIME)
for i in range(NUM_STEPS + 1):
buzzer.on(int(FREQ_HIGH / coeff ** i))
ucuq.sleep(DELAY_TIME)
buzzer.off()
LCD #
Simple display #
import ucuq
MESSAGE = "Greetings!"
lcd = ucuq.HD44780_I2C(16, 2, ucuq.SoftI2C(6, 7))
lcd.putString(MESSAGE)
lcd.backlightOn()
Scrollings #
import ucuq
MESSAGE = "Hello, how are you?"
DELAY = 0.15
lcd = ucuq.HD44780_I2C(16, 2, ucuq.SoftI2C(6, 7))
SIZE = 16
message = (" " * SIZE) + MESSAGE + (" " * SIZE)
lcd.backlightOn()
for x in range( len(message) - SIZE + 1 ):
lcd.moveTo(0,0)
lcd.putString(message[x:x+SIZE])
lcd.moveTo(0,1)
x = (len(message) - SIZE) - x
lcd.putString(message[x:x+SIZE])
ucuq.sleep(DELAY)
lcd.backlightOff()
RGB #
Plain #
import ucuq
R = 10
G = 8
B = 1
rgb = ucuq.WS2812(8, 20)
rgb.fill((R, G, B)).write()
Rainbow #
import ucuq, random
MIN = 1
MAX = 5
rgb = ucuq.WS2812(8, 20)
for _ in range(15):
for led in range(8):
rgb.setValue(led, tuple((lambda: random.randint(MIN, MAX))() for _ in range(3))).write()
ucuq.sleep(.03)
ucuq.sleep(2)
rgb.fill((0, 0, 0)).write()
OLED #
Drawing #
import ucuq
oled = ucuq.SSD1306_I2C(128, 64, ucuq.I2C(8, 9))
oled.draw("03c00c30181820044c32524a80018001824181814812442223c410080c3003c0", 16, mul=4, ox=32).show()
Multi-components #
Pink panther #
import ucuq, random, math
RGB_MAX = 10
RGB_MIN = 2
buzzer = ucuq.Buzzer(ucuq.PWM(5))
oled = ucuq.SSD1306_I2C(128, 64, ucuq.I2C(8, 9))
lcd = ucuq.HD44780_I2C(16, 2, ucuq.SoftI2C(6, 7))
rgb = ucuq.WS2812(8, 20)
lcd.backlightOff()
rand = lambda: int(math.exp(random.uniform(math.log(RGB_MIN),math.log(RGB_MAX))))
def callback(buzzer, events, duration):
global cumul, pantherPict, pantherDelay, led, ledValue
if duration:
ucuq.sleepStart()
for event in events:
if event[1] !=-1:
buzzer.off()
if event[1] != 0:
buzzer.play(int(event[1]))
rgb.setValue(led % 8, ledValue).write()
ledValue[led % 3] = rand()
led += 1
rgb.setValue(led % 8,(0,0,0)).write()
cumul += duration
if cumul > pantherDelay * pantherPict and duration >= .35:
oled.fill(0).show()
oled.draw(PANTHER[pantherPict % len(PANTHER)], 128).show()
pantherPict += 1
if duration:
ucuq.sleepWait(duration)
def main():
ucuq.polyphonicPlay(VOICES, 120, buzzer, callback)
TEXT = " " * 14 + "That's all folks!" + " " * 16
lcd.backlightOn()
for i in range(64):
rgb.setValue(((led + (i // 8)) % 8),(0,0,0)).write()
ucuq.sleepStart()
oled.scroll(0, 1).show()
lcd.moveTo(0,0).putString(TEXT[i//2:i//2+16])
ucuq.sleepWait(0.07)
oled.fill(0).show()
lcd.backlightOff()
rgb.fill((0, 0, 0)).write()
PANTHER = (
'fffffffffffffffffffffffffffffffefffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01fffffffffffffffffffffffffffffcfc7ffffffffffffffffffffffffffff9ff3ffffffffffffffffffffffffffffbff3ffffffffffffffffffffffffffff387bfffffffffffffffffffffffe03ff7333fffffffffffffffffffffffe00ff7733ffffffffffffffffffffffff007f6773ffffffffffffffffffffc7fff87f6663ffffffffffffffffffff87ffc07f6cc3fffffffffffffffffe070fff188269c7fffffffffffffffffcf01ffe7c00638ffffffffffffffffff9f803fefe7fef0ffffffffffffffffff3f800c0fffffe1ffffffffffffffffff3883e0dfffffe7ffffffffffffffffff7647e7dffffff3ffffffffffffffffff7327f7dff9fffbffffffffffffffffff79a7f79f01fff9ffffffffffffffffff3c87f3fc07fffcffffffffffffffffff9e07d3f879fffcffffffffffffffffffc7b782f9e3fffcffffffffffffffffffe0f780f3cffffcfffffffffffffffffff87b80f3fffffcfffffffffffffffffffe790dfffffffcfffffffffffffffffffefc39fffffffcfffffffffffffffffffef8f3fffffff8fffffffffffffffffffee3f7fffdfff1fffffffffffffffffffe4feff9f1fff1ffffffffffffffffffff1fcffc07ffe3fffffffffffffffffffc7f9fff9fffc3fffffffffffffffffff9ffbfff9fff87ffffffffffffffffffe3000fffbffe1fffffffffffffffffffc00043ff3ffc1fffffffffffffffffff8ffef9ff7ff07fffffffffffffffffff3ffcfcfe7fc1ffffffffffffffffffffbff9fe7cff07ffffffffffffffffffff1ffbff3dfe0fffffffffffffffffffff0ff3ffb9ff9fffffffffffffffffffff27e7ff9bff9fffffffffffffffffffff33efff80079fffffffffffffffffffffb9cff800001fffffffffffffffffffffbc9ff1cbc807ffffffffffffffffffffbe1ff81b9f81ffffffffffffffffffffb73ff0003f9fffffffffffffffffffff879ffe700f9ffffffffffffffffffffe0bdff8f8019ffffffffffffffffffff8e3cfc3f8103fffffffffffffffffffe383800ff8de1fffffffffffffffffffcf383ffff1cf87ffffffffffffffffff1e7e7ffff1ef11fffffffffffffffffe7cff3ffff3ef9c3ffffffffffffffffcf9ffbfffe3ef980ffffffffffffffff9f3ffdfffe02f83effffffffffffffff3e7ffcfffc787cfe7ffffffffffffffffcfffe7ffcfe79ff7ffffffffffffffff9ffff1ff1fff7ff3ffffffffffffffffbffffc7c1ffffff3ffffffffffffffff3ffffe001ff0fff3ffffffffffffffffffffffc1bfe67ff3ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01fffffffffffffffffffffffffffffe447ffffffffffffffffffffffffffffc127ffffffffffffffffffffffffffffd243ffffffffffffffffffffffffffff8913ffffffffffffffffffffffffffffa281ffffffffffffffffffffffff0fff8bc9fffffffffffffffffffffffe03ffa6a1fffffffffffffffffffffffc01ff9391fffffffffffffffffffffffc00ff8e43fffffffffffffffffffffffe007fa527fffffffffffffffffffffffff07f8887fffffffffffffffffffffffef87c121fffffffffffffffffffffffffbe00493fffffffffffffffffffcffffae621223fffffffffffffffffff87ffff8014907fffffffffffffffffff07fffe80a2251ffffffffffffffffffe03fffb8014889ffffffffffffffffffc07ffcf00a1290ffffffffffffffffe7c0bfe0dc01442affffffffffffffff8181ed817e4a1140fffffffffffffffe0881bf145b91242a7ffffffffffffffc2003f5812e449140fffffffffffffff88a03408241112a28fffffffffffffff91103c001144a8084fffffffffffffff8740ec00242905251fffffffffffffffa9a83c00124528909fffffffffffffff874076000490404a3fffffffffffffffa9e81a00524801503fffffffffffffff84a4be000912120a7fffffffffffffffd209080014484450ffffffffffffffffe0a2400042921289fffffffffffffffffe09000028484851ffffffffffffffffffc48000df111203ffffffffffffffffffd200006ac0494fffffffffffffffffffc40000bf42921fffffffffffffffffff900001edc0001fffffffffffffffffffc800015b40001fffffffffffffffffff800001f400003fffffffffffffffffffc60003580000bfffffffffffffffffffeb8005f740001ffffffffffffffffffffee006a800000fffffffffffffffffffedd80bfa800006fffffffffffffffffffb6a1d55800007ffffffffffffffffffeebfd7fe11c403ffffffffffffffffffe0aa884007e021ffffffffffffffffffe17fc0038ff29031ffffffffffffffffe31697ae9ff824c1ffffffffffffffffb67b9d7dbffc90c1ffffffffffffffff78d63bdb3ffe2441fffffffffffffffff87c2f6ebffe0809fffffffffffffffbff80f6d6bfff8505fffffffffffffff7fffb5b7d7fffc809ffffffffffffffffffffeed77fffc505ffffffffffffffbffffebbbe7fffe0a1ffffffffffffffffbfff6d6cfffff209ffffffffffffffdf7fff37d9fffff8a5ffffffffffffffffffffdd73fffff817ffffffffffffffffffffe3c7fffff8abfffffffffffffffffffff83ffffff81ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
'fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffcfffcfffffffffffffffffffffffffffc7ffc5ffffffffffffffffffffffffff07ff83ffffffffffffffffffffffffff07ffc1fffffffffffffffffffffffffc0fffa07ffffffffffffffffffffffffc05ffe03ffffffffffffffffffffffff837ff981ffffffffffffffffffffffff041ff041ffdfffffffffffffffffffff08c7c723f83fffffffffffffffffff1f93e7cf9fe00ffffffffffffffffff807d7f3dfc7e2c3fffffffffffffffff063e7fa9fefe6a3ffffffffffffffffe1f3effa9febcc11ffffffffffffffffe2d3ef381ce72591ffffffffffffffffc633ae381c60e5a1ffffffffffffffffc822ce3918600593ffffffffffffffffc5d20e3a9c628263ffffffffffffffffc5d007399ce56c8fffffffffffffffffc450e7f24fe5bb0fffffffffffffffffe316a7f027c6d53ffffffffffffffffff0db73e1038b5d1ffffffffffffffffff85560828815f63ffffffffffffffffffa3dde0ac2b45a8ffffffffffffffffffcd52a3f616c158ffffffffffffffffff86f160080da014ffffffffffffffffff9b478000075258ffffffffffffffffff1682000000f168ffffffffffffffffff2801c0000c06a9ffffffffffffffffff1547c0003f0db1ffffffffffffffffff8a8ff0003f9547ffffffffffffffffff87dff000ffcf87ffffffffffffffffffe11ffc01ffca1ffffffffffffffffffff01fff07ffc47ffffffffffffffffffffd3fffcfff813ffffffffffffffffffffd27ffdfffc0fffffffffffffffffffffc1fffdfff160fffffffffffffffffffe393fe005f8d71ffffffffffffffffff1c8fe00000037ebffffffffffffffffcb50f027f403cdffffffffffffffffffbecc476b0257f3ffffffffffffffffffff3f1d24005ffcfffffffffffffffffffcffff2c009fff3ffffffffffffffffff9ffff6000bfffdfffffffffffffffffefffff48009fffffffffffffffffffffdfffff4001bffffffffffffffffffffffffffe4001bffffffffffffffffffffffffffe00077ffffffffffffffffffffffffffe000f7ffffffffffffffffffffffffffef07f7ffffffffffffffffffffffffffefffe7ffffffffffffffffffffffffffefffefffffffffffffffffffffffffffe7ffcffffffffffffffffffffffffffff7ffdffffffffffffffffffffffffffff3ff9ffffffffffffffffffffffffffffbffbffffffffffffffffffffffffffff9ff3ffffffffffffffffffffffffffffcfc7ffffffffffffffffffffffffffffc78fffffffffffffffffffffffffffffe01ffffffffffffffffffffffffffffff03fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
'fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffff7f87ffffffffffffffffffffffffffffbf03ffffffffffffffffffffffffffffbf00fffffffffffffffffffffffffffddc007ffffffffffffffffffffffffffcc0007fffffffffffffffffffffffffff47003fffc1ffffffffffffffffffffff8e001ffe01ffffffffffffffffffffff9f000ffe01ffffffffffffffffffffff3f0007ff03fffffffffffffffffffffe7f0003fffffffffffffffffffffffffeff0001fffffffffffffffffffffffffcff0008fffffffffffffffffffffffffcff000c7ffffffffffffffffffffffffcfe00063ffffffffffffffffffffffffc7cf8073e07f87ffffffffffffffffff9b9ff079803f87ffffffffffffffffff3c3ffc380f1f87ffffffffffffffffff3e7fff0c478f87fffffffffffffffffe7f7fff4267cf87fffffffffffffffffe7f7ffe6133cf87fffffffffffffffffeff3ffe7011cf87fffffffffffffffffcff3ffe7c08cf87fffffffffffffffffcff3ffcfe009ffffffffffffffffffffcff9ffcff0000fffffffffffffffffffcff9fd8ffc0007ffffffffffffffffffcffcee9fff03f3ffffffffffffffffffcffcee1fff93f3ffffffffffffffffffcffe6e3fff31f1ffffffffffffffffffe7ff243ffe79f9ffffffffffffffffffe3ffb03ffe78f9fffffffffffffffffff1ffc19ffe70f1fffffffffffffffffffc00039f9e60e0ffffffffffffffffffff00118f3e64e4ffffffffffffffffffffff804e3e60ce7fffffffffffffffffffffc00e7e300e7ffffffffffffffffffffff1007f0e1e7ffffffffffffffffffffff8c07f803c7ffffffffffffffffffffff8267fc000ffffffffffffffffffffffff123fff01fffffffffffffffffffffffd831fff9ffffffffffffffffffffffffdc38fffce0fffffffffffffffffffffffe3cfffc803fffffffffffffffffffffff1ffffc0f1fffffffffffffffffffffff8ffffc3f9fffffffffffffffffffffffc7fffec38fffffffffffffffffffffffe3ffff018ffffffffffffffffffffffff1ffff789ffffffffffffffffffffffff8fff89c9ffffffffffffffffffffffffc7ff8889ffffffffffffffffffffffffe3ff0613fffffffffffffffffffffffff3ff3133fffffffffffffffffffffffff9fe7807fffffffffffffffffffffffff8fc7c0ffffffffffffffffffffffffffc00ffffffffffffffffffffffffffffff03ffffffffffffffffffffffffffffffcfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe3fffffffffffffffffffffffffefffbe1fffffffffffffffffffffffffffff1f9fffffffffffffffffffffffffffff3e3ffcfffffffc3fffffffffffffffff05df007ffffff81ffffffffffffffff61968007ffffff91003ffffffffffff41bd00003ffffffc4001fffffffffffc09be00003ffffffc8429fffffffffffc24364a701ffffffc9283ffffffe7fff9009e927cdffffffc4100ffffff83fffc010954fd9ffffffc8084fffffe13fffc02a088fe9ffffffc0020fffffc23fffe005514fd1ffffff90831fffff883fffe08a4001ebffffff8251fffffe043fffc1029007c7ffffff21047ffffe287fffc945200397fffffc05023ffffc527fffc121401867fffff884108ffff948ffff94a0800fe7ffffe0045147fff094ffff8848010fcfffff82084821ffe250ffff852823e7cffffe081110487fc549ffff889501f03ffff8225084851c02a3ffffc4aa89fcffffe28c2208e140a043ffffe15204ffffff843c852470405027fffff0048000afd0087920727ca8040ffffffe010220001251f084f13e15010fffffffe20109554943e549f93f04a29ffffffff9144255240fe211f89f055007fffffff2293880007fca83fc9e011047ffffffe454bc00ffff9227fc8c98cc11ffffffe8151f1fffff0a0ffe403c1008ffffffc4514fc7ffff240ffe207f28c07fffff8942a3f0fffe281fff20ff83e23fffff108090fe8000943fff11fffff847fffe5118543fbff8507fff09fffffc20fdfc423e290fffe2a87fff04ffffff0800f8847f0a83ffe120fffe0affffff82a8f140ff8520ffca91fffc847ffffff001e281ffe1520004a3fff0093801fffc07e203fff0aa221523ffe26400003fffffc50ffffc25492a47ff88e282a407ffff841ffffe14aa4947fc11f1295521ffff943fffff82a4924fc047f2a52481ffff107fffffe0955500041ff092a850ffff21fffffff82a4a15507ff95493007fff01fffffffe02a80003fff8aaa801ffff87ffffffff8000d03ffffc00001ffffffffffffffff003ffffffff0001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
'ffffffffffffffffffe03fffffffffffffffffffffffffffffc21ffffffffffffffffffff03fffffff8f8fffffffffffffffffffc01fffffff99cfffffffffffffffffffc78fffffff30cfffffffffffffffffff9fcfffffff2ccfffffffffffffffffff9c67ffe8006d9fffffffffffffffffff9967e00000691fffffffffffffffffff99a78025a0e33fffffffffffffffffff9db4001ee06cffffffffffffffffffffccb0f007c071ffffffffffffffffffffc637ce058399ffffffffffffffffffffe3badf8287ccfffffffffffffffffffff0ff9fc18fccfffffffffffffffffffffc6d9ff01fccfffffffffffffffffffffc7f9ff807cefffffffffffffffffffffcea8fc4079e7ffffffffffffffffffff8bfc7c1062c7ffffffffffffffffffff9f75003607cfffffffffffffffffffff97dfd5e79acfffffffffffffffffffff9b7aff674f9fffffffffffffffffffff8edfd7efe31fffffffffffffffffffffcbed7dab783fffffffffffffffffffffe6bfdfefe03fffffffffffffffffffffe1f57568043ffffffffffffffffffffff80fbfc2aa1ffffffffffffffffffffffe000d85550fffffffffffffffffffffffe05f61aa07fffffffffffffffffffffffe4b7056c7ffffffffffffffffffffffff6efc28e7ffffffffffffffffffffffff26fe15e7ffffffffffffffffffffffff35ff8be3ffffffffffffffffffffffff35f7c7c3ffffffffffffffffffffffff3017e7603fffffffffffffffffffffff01e3f783e3fffffffffffffffffffffe191fe7e0fcfffffffffffffffffffff338e1f7e71fbfffffffffffffffffffef01cc00c7e7ffffffffffffffffffffbf0dbfefc7fbffffffffffffffffffffff3dffffcffefffffffffffffffffffffc15ffff8fff7ffffffffffffffffffff81effeb9fffffffffffffffffffffffe01eff003fffffffffffffffffffffffe3377f1e3fffffffffffffffffffffffe79ebc3b9fffffffffffffffffffffffe5d79c5edfffffffffffffffffffffffe7df40f7c1ffffffffffffffffffffffe6adf03d807fffffffffffffffffffffc7ff5f071e3fffffffffffffffffffffcbb5af822f3fffffffffffffffffffffceff77f07d3fffffffffffffffffffffcbab7b78bf07ffffffffffffffffffffcfbe70ecea03ffffffffffffffffffffcaaee0397ce3ffffffffffffffffffffcfbec719d9f1ffffffffffffffffffffcdb4cf89f351ffffffffffffffffffffcfbd97c0a5f1ffffffffffffffffffffcdbd9ede07b9ffffffffffffffffffffdebd9bfbaed1ffffffffffffffffffffd7b98f5f65b3ffffffffffffffffffffdebd81fb6733ffffffffffffffffffffd7b9c03f7067ffffffffffffffffffffdabbfe0d2de7ffffffffffffffffffffdfbbfe8fffcffffffffff',
'ffffffffffffffffffffffcfffffffffffffffe3fffffffffffffe01ffffffffffffff00fffffffffffff800fffffffffffffe003ffffffffffff0207ffffffffffffc141f83fffffe1ff1f43ffffffffffff87e1f01fffff81fe2ac3ffffffffffff8aa8f007fffe00fe3f63ffffffffffff0ff8f803fffc00fc68c1ffffffffffff0a2c7800fff801fc3071ffffffffffff1c987c007fe001fce661ffffffffffff14cc7e003fc003fc4ee3ffffffffffff8e667c400f8019fcd8a3ffffffffffff8d367c7007006dfcd1c7ffffffffffffc71670d40003b8f48287ffffffffffffc282017f800df4008f8fffffffffffffe3e251ddc06f5c513a1ffffffffffffff0baa97739a1f85fec3ffffffffffffff86ff1dc16c0b4b5587ffffffffffffffe3aa96c1de1f8dff1fffffffffffffffe17f1d817c1a8aaa3fffffffffffffffc3aa86e187af17ff1fffffffffffffff86ff8fbb01781aaa8fffffffffffffff85aac36e300057ff8fffffffffffffff1eff41f05821baaa87fffffffffffffe15aad000ea16d7ff47fffffffffffffe2f7fb8017f1dbaaac7fffffffffffffe1ad56fe1aa86d7ff47fffffffffffffe376fdaa2ffe3baaac3fffffffffffffc1adab7cfa00157ff67fffffffffffffe37b7ea800000faaa87fffffffffffffc2d6d5f00000015ffc7fffffffffffffe37b7ea000000c6aa87fffffffffffffe2d6d5e000001f1ff87fffffffffffffe37b7a8000003f8aa8ffffffffffffffe2d6d79c00003fc7f1fffffffffffffff1bb6a3f0000ffe541fffffffffffffff0d5bc7fc000fff383fffffffffffffff87ed4ffe003fff30ffffffffffffffffc15b9ff7807fff83ffffffffffffffffe0ee1fffc0ffc707fffffffffffffffff03b3ffff1ffe803fffffffffffffffffc0e3f1ffbffdf3e1fffffffffffffffff00307ffbffc03ff0ffffffffffffffffc007effbffbe0fff1ffffffffffffffff83e1ff85fc278ffe3ffffffffffffffe731f7e000f87f9ffcffffffffffffff1f0f8f87f81c9fe3ffbffffffffffffcfe1e3e3fff80f3fc7ffffffffffffff3f981fcffffe1fc7f9fffffffffffffcfe7c7f1ffffe7ff9fe7ffffffffffff3f9f83f7ffffcfffe7f9fffffffffffefe7ee3ffffffcffff9fefffffffffffff9f9f0ffffff8ffffe7ffffffffffffff7e7f87fffff1fffffbfffffffffffffcfdffc3fffff1fffffcfffffffffffffbf3ffe1ffffe3ffffff7ffffffffffff7effff07fffc3ffffffbfffffffffffffdffff81fff87ffffffffffffffffffff3ffffc07fe0ffffffffffffffffffffeffffff00201ffffffffffffffffffffeffffff00003fffffffffffffffffffffffffffe0007ffffffffffffffffffffffffffff801fffffffffffffffffffffffffffffffffffffffffffffff',
'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdfffc1ffffffffffffffffffffffffffc7ff007fffffffffffffffffffffffff81fe001fffffffffffffffffffffefffc07e3f0fffffffffffffffffffffc7ffc00c7f87ffffffffffffffffffff87ff000c7fc3ffffffffffffffffffff0ffe3e1e7be3fffffffffffffffffffe00fe7f1e70f1fffffffffffffffffffc007cff9e6671ffffffffffffffff80f81e3cffde6673fffffffffffffffe00703f9dffce66f3fffffffffffffffc3e387f9dffec64e7fffffffffffffff8ff3cffc9ffe071cffffffffffffffff1ff3cffc1ffe3fb9ffffffffffffffff1ff3cffe1fbe7ff3fffffffffffffffe3c73dffe5f1e7fe7fffffffffffffffe38339ffe4eceffe3fffffffffffffffe39931f9e6e4effe1fffffffffffffffe39d00f4e670cfff1ffffffffffffffff3cd1cf6e771dfff8ffffffffffffffff1e1fcf0efbf9fff8ffffffffffffffff87ffe78cfce3fffc7fffffffffffffffc1ffe7dd0607fffc7ffffffffffffffff07ff3f803ff007c7ffffffffffffffffe7ff8e0f0ffc0fcfffffffffffffffffe7ffc09fc3ff3f8fffffffffffffffffc7ffff3e01fe7f9fffffffffffffffffcffffe6000fe7f3fffffffffffffffff8ffe7c8000047e7fffffffffffffffff8ffcfc0000007cffffffffffffffffff8ff1f80001f819ffffffffffffffffff8e03e00001ff03ffffffffffffffffff8783c00003ff87ffffffffffffffffffc3f11e0007ffc7ffffffffffffffffffc1f83f000fffc7ffffffffffffffffffe0f87f800fffe7fffffffffffffffffff838ffe01fff003ffffffffffffffffffe01fff03fffc7c0ffffffffffffffffff83fffc7fffcfffdfffffffffffffffffe7fffe7fff07ffffffffffffffffffffe7fffe7fffc1ffffffffffffffffffffe67fff3fff8e3fffffffffffffffffffe0fffe00701fcfffffffffffffffffffe1fff000001ff9ffffffffffffffffff913f800383ffff7ffffffffffffffffe787c0007f3ffffcffffffffffffffff9f800100fe3fffffffffffffffffffff3f2007e3fe7ffffffffffffffffffffcfe780ffffe7ffffffffffffffffffff3fdffcffffe7fffffffffffffffffffeffbffc7fffe7fffffffffffffffffffffe7ffe3fffcffffffffffffffffffffffcfffe3fffcffffffffffffffffffffff9ffff1fff8ffffffffffffffffffffff7ffff8fff1fffffffffffffffffffffffffff87fe1fffffffffffffffffffffffffffc1f83fffffffffffffffffffffffffffe0007ffffffffffffffffffffffffffff801fffffffffffffffffffffffffffffe07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
'7ffffffffffffffffffffffffffffffffffffffffc1ffffffffffffffffffffffffffffff18fffcffcfffffffffffffffffffffff7e7fc07fc7fffffffffffffffffffffefe3f007fc3fffbfffffffffffffffffcff3e3ffff1ffe07ffffffffffffffffde33feffffc7fc61ffffffffffffffff9c99f33fffe7fcf9ffffffffffffffff99d9cfc07ff3f9fcffffffffffffffff98981fe007fbf9ce7fffffffffffffff9c2c3fe7e39dfb867fffffffffffffff9ffd3ff7cfeff3333fffffffffffffff8fff7ff7dff3f37b3fffffffffffffffc3ff7ff7bff8337b3ffffffffffffffff81e7ff77ff9077b3fffffffffffffffff3efff77ffde7833ffffffffffffffffe7efffffffdffe73ffffffffffffffffe7effeffffdfffe7ffffffffffffffffcfe7fc7fffdfef0fffffffffffffffffcfe1fdb7ffdff01fffffffffffffffffcff0fbb7ff9ff3ffffffffffffffffffcff0efb4ffbff3ffffffffffffffffffe7fc1fb87f3ff9ffffffffffffffffffe3fe3fbc7e7ff9fffffffffffffffffff1fc7fbe78fffdfffffffffffffffffff8f8ffbf83fffdfffffffffffffffffffc23ff3ffffff9fffffffffffffffffffe07ff3ffffff9ffffffffffffffffffff0fff3ffffff1ffffffffffffffffffff000f7ffffff3ffffffffffffffffffff00007fe03fc7ffffffffffffffffc7f800007fe1ff0ffffffffffffffffff9f180001feffc1fffffffffffffffffff67c000efcff07fffffffffffffffffffcfe001fb9f87ffffffffffffffffffffe7e003fc3e1ffffffffffffffffffff86bf007fc787fffffffffffffffffffffc7fc1ffe71ffffffffffffffffffffffe0fe7fff27ffffffffffffffffffffffe7fefffe007ffffffffffffffffffffc03fefffc9fe1ffffffffffffffffffcfe3ffffff9ffffffffffffffffffffffff3ff7fff9ffffffffffffffffffffffff1ff7ffe003ffffffffffffffffffffff8ff3fffbff3fffffffffffffffffffffc7f1fffbfffffffffffffffffffffffff088ffa07ffffffffffffffffffffffff80c7fe7c7ffffffffffffffffffffffffdc0f87ffffffffffffffffffffffffff9c0007ffffffffffffffffffffffffff38f3e7ffffffffffffffffffffffffff30fbe7ffffffffffffffffffffffffff60f3e7ffffffffffffffffffffffffff7807e7ffffffffffffffffffffffffff3fffe7ffffffffffffffffffffffffff3fffe7ffffffffffffffffffffffffff9fffcfffffffffffffffffffffffffff9fffcfffffffffffffffffffffffffffc7ff8fffffffffffffffffffffffffffe3ff1ffffffffffffffffffffffffffff0fc3ffffffffffffffffffffffffffffc003fffffffffffffffffffffffffffff01ffffffffffffffffffffffffffffffffffffffffffffffff',
'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9ffe7ffffffffffffffffffffffffffe0ffe1ffffffffffffffffffffffffffc0ffc07fffffffffffffffffffffffff807fc03ffffffffffffffffffffffffe007f800ffffffffffffffffffffffffc01ffe003fffffffffffffffffffffff803fff803fffffffffffffffffffffff80ffffe07fffffffffffffffffffffff8207fc007ffffffffffffffffffffe7fc403f8027fc3fffffffffffffffff80ff801f001ff807fffffffffffffffe007f0f0f1e1ff003fffffffffffffffc003e1f8e3f0fe001fffffffffffffff83c3e3fce3f8fe3e0fffffffffffffff87e3e3fce7f8fe3f07ffffffffffffff0fe3c7fc47fc7c7387ffffffffffffff1863c780000c1c61c7fffffffffffffe192004000000004cc7fffffffffffffe13a000de66c8005c87fffffffffffffe1980069ee70c00598fffffffffffffff1c9f0e1ce79ccfc30fffffffffffffff0e1fe7bc13fcffc61fffffffffffffff83bfe7f803fcfff83fffffffffffffffc0fff3f0c1f9fff07fffffffffffffffe1ff91e1e0f3ffe1ffffffffffffffffe1ff9803f003ffe1ffffffffffffffffc3ff3e07f889fff1ffffffffffffffffc7ff3f9ffc7cff30ffffffffffffffffc7387f3ffe3e7e30ffffffffffffffff8700fc00000f0070ffffffffffffffff87c0f800000781f0ffffffffffffffff87fe200000711fe0ffffffffffffffffc3ff0e0000f83fc1ffffffffffffffffc1ff1f0001fe7f83ffffffffffffffffe0fe3f8003ff3e0ffffffffffffffffff03c7fe007ff181ffffffffffffffffffc08fff00fff807fffffffffffffffffff01fffc1fff81ffffffffffffffffffffe1ffff3fff87fffffffffffffffffffff03fff7ffc0fffffffffffffffffffff007fff7ffc00fffffffffffffffffff800dfff3ffb000fffffffffffffffff803c1ff007f81e00fffffffffffffffe03e03f07e07e07e01ffffffffffffff03f80107ffe0000fe07ffffffffffffc1fe0f01ffff80f03fe3ffffffffffffeff83f80ffff81fe0fffffffffffffffffe0fff8ffff1fff83ffffffffffffffff83fffc7fff1fffe0fffffffffffffffe0ffffc7fff3ffff83ffffffffffffffc3ffffe7ffe3ffffe1ffffffffffffff8fffffe3ffe3fffffcfffffffffffffffffffff1ffc7fffffffffffffffffffffffffff0ff87fffffffffffffffffffffffffff87f0ffffffffffffffffffffffffffffc001ffffffffffffffffffffffffffffe003fffffffffffffffffffffffffffff007ffffffffffffffffffffffffffffff1fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'
)
VOICES = ("""
D#43, E44 R4, F#43, G44 R4, D#43, E43,-2, R2, F#43, G43,-2, R2, C53, B43,-2, R2, E43, G43,-2, R2, B43, Bb45-3, A43, G43, E43, D43, E43,-4 R5-3,
D#43, E44 R4, F#43, G44 R4, D#43, E43,-2, R2, F#43, G43,-2, R2, C53, B43,-2, R2, E43, G43,-2, R2, E53, Eb56.. R4,
D#43, E44 R4, F#43, G44 R4, D#43, E43,-2, R2, F#43, G43,-2, R2, C53, B43,-2, R2, E43, G43,-2, R2, B43, Bb45-3, A43, G43, E43, D43, E43,-4 R5-4,
R3... Eb51 E54, D53, B44, A43, G44, E43, Bb42 A43. Bb42 A43. Bb42 A43. Bb42 A43. G43, E43, D43, E44, E43,-5 R6
F#44, G43,-3 R3-5 A44, Bb43-4-3 C#53, D54, F55 -4, Eb53, Db54, Bb44, -4.
R4. G43, Bb42 C52 Bb42 G42 A44, G43, -4 R4, Bb43, Db52 Eb52 Db52 Bb42 C54, Bb44, -4, G44, F43,-6.-4, F#44, G43
R5 G43, Bb44, C53, D54, D53, Db53, C53, Bb43, G44, Bb43, R4, G43, Bb44, C54, Db54, Db54, C54, Bb44, G45. F44, D43, -4 R5.
G42 Bb42 R3 Bb44 F#42 A42 R3 A44 G44
""",)
pantherDelay = 60 // len(PANTHER)
pantherPict = 0
cumul = 0
led = rand()
LED_LIMITER = 15
ledValue = [rand(), rand(), rand()]
main()
With GUI #
Boilerplate #
A minimalist application to switch on/off the 7-segment display (simulation) or the embedded LED (physical kit).
import ucuq, atlastk
led = ucuq.GPIO(8)
async def atk(dom):
await dom.inner("", BODY)
led.high()
async def atkSwitch(dom, id):
if await dom.getValue(id) == "true":
led.low()
else:
led.high()
BODY = """
<fieldset>
<label>
<span>On/off: </span>
<input type="checkbox" xdh:onevent="Switch">
</label>
</fieldset>
"""
atlastk.launch(globals=globals())
Chernoff faces #
Application displaying Chernov faces.
import atlastk, math, ucuq
lcd = ucuq.HD44780_I2C(16, 2, ucuq.SoftI2C(6, 7))
oled = ucuq.SSD1306_I2C(128, 64, ucuq.I2C(8, 9))
class ChernoffFace:
def __init__(self, fb, width, height, cx, cy):
self.fb = fb
self.width = width
self.height = height
self.cx = cx
self.cy = cy
self.face_color = 1
# 18 paramètres du visage de Chernoff
self.face_width = 1.0 # 1. Largeur du visage (ratio)
self.face_height = 1.0 # 2. Hauteur du visage (ratio)
self.face_shape = 1.0 # 3. Forme du visage (-1 à 1, rond vs ovale)
self.eye_size = 0.5 # 4. Taille des yeux (0 à 1)
self.eye_spacing = 0.5 # 5. Écartement des yeux (0 à 1)
self.eye_height = 0.0 # 6. Position verticale des yeux (-1 à 1)
self.eye_eccentricity = 0.5 # 7. Excentricité des yeux (0=rond, 1=ovale)
self.eye_angle = 0.0 # 8. Inclinaison des yeux (-45 à 45 degrés)
self.pupil_size = 0.5 # 9. Taille des pupilles (0 à 1)
self.pupil_position = 0.0 # 10. Position horizontale pupilles (-1 à 1)
self.eyebrow_length = 0.5 # 11. Longueur des sourcils (0 à 1)
self.eyebrow_angle = 0.0 # 12. Inclinaison sourcils (-45 à 45 degrés)
self.eyebrow_height = 0.2 # 13. Position verticale sourcils (0 à 1)
self.nose_length = 0.5 # 14. Longueur du nez (0 à 1)
self.nose_width = 0.5 # 15. Largeur du nez (0 à 1)
self.mouth_width = 0.5 # 16. Largeur de la bouche (0 à 1)
self.mouth_height = 0.0 # 17. Position verticale bouche (-1 à 1)
self.mouth_curve = 0.0 # 18. Courbure de la bouche (-1=triste, 1=souriant)
# --- Méthodes de configuration individuelles ---
def set_face_dimensions(self, width_ratio, height_ratio, shape):
self.face_width = max(0.5, min(1.5, width_ratio))
self.face_height = max(0.5, min(1.5, height_ratio))
self.face_shape = max(-1, min(1, shape))
def set_eyes(self, size, spacing, height, eccentricity, angle):
self.eye_size = max(0, min(1, size))
self.eye_spacing = max(0, min(1, spacing))
self.eye_height = max(-1, min(1, height))
self.eye_eccentricity = max(0, min(1, eccentricity))
self.eye_angle = max(-45, min(45, angle))
def set_pupils(self, size, position):
self.pupil_size = max(0, min(1, size))
self.pupil_position = max(-1, min(1, position))
def set_eyebrows(self, length, angle, height):
self.eyebrow_length = max(0, min(1, length))
self.eyebrow_angle = max(-45, min(45, angle))
self.eyebrow_height = max(0, min(1, height))
def set_nose(self, length, width):
self.nose_length = max(0, min(1, length))
self.nose_width = max(0, min(1, width))
def set_mouth(self, width, height, curve):
self.mouth_width = max(0, min(1, width))
self.mouth_height = max(-1, min(1, height))
self.mouth_curve = max(-1, min(1, curve))
def set_from_dict(self, params):
"""Configure tous les paramètres depuis un dictionnaire"""
for key, value in params.items():
if hasattr(self, key):
setattr(self, key, value)
# --- Méthode de dessin ---
def draw(self):
self._draw_face_outline()
self._draw_eyebrows()
self._draw_eyes()
self._draw_pupils()
self._draw_nose()
self._draw_mouth()
# --- Détails du dessin ---
def _draw_face_outline(self):
rx = int(self.width * self.face_width / 2)
ry = int(self.height * self.face_height / 2)
# Ajustement de forme (ellipse vs cercle)
if self.face_shape > 0:
ry = int(ry * (1 + self.face_shape * 0.3))
else:
rx = int(rx * (1 - self.face_shape * 0.3))
self.fb.ellipse(self.cx, self.cy, rx, ry, self.face_color)
def _draw_eyes(self):
spacing = int(self.width * 0.3 * self.eye_spacing)
y_offset = int(self.height * 0.15 * self.eye_height)
eye_y = self.cy - self.height // 6 + y_offset
rx = int(self.width * 0.08 * self.eye_size)
ry = int(rx * (1 - self.eye_eccentricity * 0.5))
angle = math.radians(self.eye_angle)
for side in (-1, 1):
ex = self.cx + side * spacing
self._ellipse_rotated(ex, eye_y, rx, ry, angle, self.face_color)
def _draw_pupils(self):
if self.pupil_size == 0:
return
spacing = int(self.width * 0.3 * self.eye_spacing)
y_offset = int(self.height * 0.15 * self.eye_height)
eye_y = self.cy - self.height // 6 + y_offset
pupil_r = int(self.width * 0.03 * self.pupil_size)
pupil_x_offset = int(self.width * 0.02 * self.pupil_position)
for side in (-1, 1):
px = self.cx + side * spacing + pupil_x_offset
self._fill_circle(px, eye_y, pupil_r, self.face_color)
def _draw_eyebrows(self):
if self.eyebrow_length == 0:
return
spacing = int(self.width * 0.3 * self.eye_spacing)
y_offset = int(self.height * 0.15 * self.eye_height)
base_y = self.cy - self.height // 6 + y_offset
brow_y = base_y - int(self.height * 0.1 * self.eyebrow_height) - 5
length = int(self.width * 0.1 * self.eyebrow_length)
angle = math.radians(self.eyebrow_angle)
for side in (-1, 1):
bx = self.cx + side * spacing
dx = int(length * math.cos(angle)) * side
dy = int(length * math.sin(angle)) * side
self.fb.line(bx - dx, brow_y - dy, bx + dx, brow_y + dy, self.face_color)
def _draw_nose(self):
if self.nose_length == 0:
return
nx = self.cx
nose_h = int(self.height * 0.15 * self.nose_length)
nose_w = int(self.width * 0.08 * self.nose_width)
ny_top = self.cy - nose_h // 2
ny_bottom = self.cy + nose_h // 2
half_w = nose_w // 2
x1, y1 = nx - half_w, ny_bottom
x2, y2 = nx + half_w, ny_bottom
x3, y3 = nx, ny_top
self.fb.line(x1, y1, x2, y2, self.face_color)
self.fb.line(x2, y2, x3, y3, self.face_color)
self.fb.line(x3, y3, x1, y1, self.face_color)
def _draw_mouth(self):
mw = int(self.width * 0.4 * self.mouth_width)
if mw < 2:
return
y_offset = int(self.height * 0.15 * self.mouth_height)
y_base = self.cy + self.height // 4 + y_offset
curve_amp = int(self.height * 0.1 * self.mouth_curve)
x1 = self.cx - mw // 2
steps = mw
lastx, lasty = x1, y_base
for i in range(1, steps + 1):
x = x1 + i
t = math.pi * i / steps
y = y_base - int(curve_amp * math.sin(t))
self.fb.line(lastx, lasty, x, y, self.face_color)
lastx, lasty = x, y
# --- Utilitaires de dessin ---
def _ellipse_rotated(self, x0, y0, rx, ry, angle, color):
steps = 20
for i in range(steps):
theta1 = 2 * math.pi * i / steps
theta2 = 2 * math.pi * (i + 1) / steps
x1 = rx * math.cos(theta1)
y1 = ry * math.sin(theta1)
x2 = rx * math.cos(theta2)
y2 = ry * math.sin(theta2)
px1 = x0 + x1 * math.cos(angle) - y1 * math.sin(angle)
py1 = y0 + x1 * math.sin(angle) + y1 * math.cos(angle)
px2 = x0 + x2 * math.cos(angle) - y2 * math.sin(angle)
py2 = y0 + x2 * math.sin(angle) + y2 * math.cos(angle)
self.fb.line(int(px1), int(py1), int(px2), int(py2), color)
def _fill_circle(self, x, y, r, color):
for dy in range(-r, r + 1):
dx = int(math.sqrt(r * r - dy * dy))
self.fb.hline(x - dx, y + dy, 2 * dx + 1, color)
chernoff = ChernoffFace(oled, 60, 60, 64, 32)
async def atk(dom):
await dom.inner("", BODY)
chernoff.draw()
oled.show()
async def atkSet(dom, id):
value = await dom.getValue(id)
lcd.clear().moveTo(0,0).putString(f"{id}:").moveTo(0,1).putString(f"{value}").backlightOn()
exec(f"chernoff.{id} = {float(value)}")
oled.fill(0)
chernoff.draw()
oled.show()
BODY = """
<fieldset style="display: flex;">
<legend>Chernoff's faces (AI-generated)</legend>
<span>
<fieldset style="display:flex; flex-direction: column">
<legend>Eyes</legend>
<input type="range" id="eye_size" min="0" max="1" step="0.01" value="0.5" xdh:onevent="Set">
<input type="range" id="eye_spacing" min="0" max="1" step="0.01" value="0.5" xdh:onevent="Set">
<input type="range" id="eye_height" min="-1" max="1" step="0.01" value="0" xdh:onevent="Set">
<input type="range" id="eye_eccentricity" min="0" max="1" step="0.01" value="0.5" xdh:onevent="Set">
<input type="range" id="eye_angle" min="-45" max="45" step="1" value="0" xdh:onevent="Set">
</fieldset>
<fieldset style="display:flex; flex-direction: column">
<legend>Pupil</legend>
<input type="range" id="pupil_size" min="0" max="1" step="0.01" value="0.5" xdh:onevent="Set">
<input type="range" id="pupil_position" min="-1" max="1" step="0.01" value="0" xdh:onevent="Set">
</fieldset>
<fieldset style="display:flex; flex-direction: column">
<legend>Nose</legend>
<input type="range" id="nose_length" min="0" max="1" step="0.01" value="0.5" xdh:onevent="Set">
<input type="range" id="nose_width" min="0" max="1" step="0.01" value="0.5" xdh:onevent="Set">
</fieldset>
</span>
<span>
<fieldset style="display:flex; flex-direction: column">
<legend>Face</legend>
<input type="range" id="face_width" min="0" max="1" step="0.01" value="1" xdh:onevent="Set">
<input type="range" id="face_height" min="0" max="1" step="0.01" value="1" xdh:onevent="Set">
<input type="range" id="face_shape" min="-1" max="1" step="0.01" value="1" xdh:onevent="Set">
</fieldset>
<fieldset style="display:flex; flex-direction: column">
<legend>Eyebrow</legend>
<input type="range" id="eyebrow_length" min="0" max="1" step="0.01" value="0.5" xdh:onevent="Set">
<input type="range" id="eyebrow_angle" min="-15" max="45" step="1" value="0" xdh:onevent="Set">
<input type="range" id="eyebrow_height" min="0" max="1" step="0.01" value="0.2" xdh:onevent="Set">
</fieldset>
<fieldset style="display:flex; flex-direction: column">
<legend>Mouth</legend>
<input type="range" id="mouth_width" min="0" max="1" step="0.01" value="0.5" xdh:onevent="Set">
<input type="range" id="mouth_height" min="-1" max="1" step="0.01" value="0" xdh:onevent="Set">
<input type="range" id="mouth_curve" min="-1" max="1" step="0.01" value="0" xdh:onevent="Set">
</fieldset>
</span>
</fieldset>"""
atlastk.launch(globals=globals())
Simon’s game #
A game inspired by Simon.
NOTA: for use with the simulator, uncomment (remove # and following space) the expression SIMULATOR = True at line 3.
import atlastk, ucuq, json, math, random
# SIMULATOR = True
seq = ""
userSeq = ""
lcd = ucuq.HD44780_I2C(16, 2, ucuq.SoftI2C(6, 7))
rgb = ucuq.WS2812(8, 20)
buzzer = ucuq.Buzzer(ucuq.PWM(5))
oled = ucuq.SSD1306_I2C(128, 64, ucuq.I2C(8, 9))
lcd.backlightOn()
trueBuzzer = buzzer
rgbCount = 8
rgbOffset = 2
rgbLimiter = 255 if 'SIMULATOR' in globals() and SIMULATOR else 30
def buzzerBeep_(note, delay=0.29, sleep=0.01):
buzzer.play(57 + note)
ucuq.sleep(delay)
buzzer.off()
if sleep:
ucuq.sleep(sleep)
def rgbFlash_(button):
rgb.fill([0, 0, 0])
if button in BUTTONS:
for i in range(1 + rgbCount // 4):
rgb.setValue(
(
list(BUTTONS.keys()).index(button) * rgbCount // 4
+ i
+ rgbOffset
)
% rgbCount,
[rgbLimiter * item // 255 for item in BUTTONS[button][0]],
)
rgb.write()
def oledDigit_(n, offset):
oled.draw(DIGITS[n], 8, offset, mul=8)
def playJingle_(jingle):
prevButton = ""
prevPrevButton = ""
for n in jingle:
while True:
button = random.choice(list(BUTTONS.keys()))
if (button != prevButton) and (button != prevPrevButton):
break
prevPrevButton = prevButton
rgbFlash_(prevButton := button)
buzzerBeep_(n, 0.15, 0)
rgbFlash_("")
def oledNumber(n):
try:
oledDigit_(n // 10, 12)
oledDigit_(n % 10, 76)
except:
oled.fill(0)
oled.show()
def lcdClear():
lcd.clear()
def lcdDisplay(line, text):
lcd.moveTo(0, line).putString(text.ljust(16))
def lcdBacklightOn():
lcd.backlightOn()
def lcdBacklightOff():
lcd.backlightOff()
def lcdDisplaySequence(seq, l10n):
lcdClear()
lcdDisplay(0, "".join(l10n(12)["RGBY".index(char)] for char in seq))
lcdBacklightOn()
def begin(l10n):
lcdClear()
lcdDisplay(0, l10n(10).format(**l10n(new=8)))
lcdDisplay(1, l10n(11))
oledNumber(None)
commit()
return not isinstance(buzzer, ucuq.Nothing)
def new(seq, l10n):
oledNumber(None)
lcdClear()
lcdDisplay(0, l10n(3))
lcdDisplay(1, l10n(4))
oledNumber(0)
ucuq.sleep(0.75)
play(seq)
commit()
def restartHW(seq, l10n):
oledNumber(None)
lcdClear()
lcdDisplay(0, l10n(1))
lcdDisplay(1, l10n(2))
lcdBacklightOn()
playJingle_(LAUNCH_JINGLE)
ucuq.sleep(0.5)
new(seq, l10n)
def success(l10n):
lcdDisplay(0, l10n(5))
oledNumber(None)
oled.draw(HAPPY_MOTIF, 16, mul=4, ox=32).show()
playJingle_(SUCCESS_JINGLE)
ucuq.sleep(0.5)
lcdClear()
commit()
def failure(l10n):
lcdDisplay(0, l10n(6).format(**l10n(new=8)))
lcdDisplay(1, l10n(7))
oledNumber(len(seq))
buzzer.on(30)
oledNumber(None)
oled.fill(0).draw(SAD_MOTIF, 16, mul=4, ox=32).show()
ucuq.sleep(1)
buzzer.off()
commit()
def buzzerSwitch(status):
global buzzer
if status:
buzzer = trueBuzzer
else:
buzzer = ucuq.Nothing()
def displayButton(button):
rgb.fill([0, 0, 0])
if button in BUTTONS:
for i in range(1 + rgbCount // 4):
rgb.setValue(
(
list(BUTTONS.keys()).index(button) * rgbCount // 4
+ i
+ rgbOffset
)
% rgbCount,
[rgbLimiter * item // 255 for item in BUTTONS[button][0]],
)
rgb.write()
buzzer.play(57 + BUTTONS[button][2])
ucuq.sleep(0.29)
buzzer.off()
rgb.fill([0, 0, 0]).write()
ucuq.sleep(0.01)
def play(sequence):
seq = ""
for s in sequence:
oledNumber(len(seq) + 1)
displayButton(s)
seq += s
if len(seq) % 5:
commit()
def commit():
ucuq.commit()
def getValuesOfVarsBeginningWith(prefix):
return [value for var, value in globals().items() if var.startswith(prefix)]
def remove(source, items):
return [item for item in source if item not in items]
BUTTONS = {
"R": [[255, 0, 0], 5, 9],
"B": [[0, 0, 255], 7, 12],
"Y": [[255, 255, 0], 1, 17],
"G": [[0, 255, 0], 3, 5],
}
LAUNCH_JINGLE = [3, 10, 0, 8, 7, 5, 3, 7, 10, 8, 5, 3]
SUCCESS_JINGLE = [7, 10, 19, 15, 17, 22]
async def restartAwait(dom):
global seq
seq = random.choice("RGBY")
restartHW(seq, lambda m: dom.getL10n(m))
await dom.setValue("Length", str(len(seq)))
async def atk(dom):
global seq, userSeq
body = BODY.format(**dom.getL10n(new=8, repeat=9))
ucuq.setCommitBehavior(ucuq.CB_MANUAL)
await dom.inner("", body)
seq = ""
userSeq = ""
if begin(lambda *args, **kwargs: dom.getL10n(*args, **kwargs)):
await dom.setAttribute("Buzzer", "checked", "")
async def atkRepeat():
play(seq)
commit()
async def atkNew(dom):
await restartAwait(dom)
async def atkClick(dom, id):
global seq, userSeq
if not seq:
return
userSeq += id
lcdDisplaySequence(userSeq, lambda m: dom.getL10n(m))
oledNumber(len(seq) - len(userSeq))
displayButton(id)
if seq.startswith(userSeq):
if len(userSeq) >= len(seq):
success(lambda m: dom.getL10n(m))
userSeq = ""
seq += random.choice("RGBY")
new(seq, lambda m: dom.getL10n(m))
await ucuq.sleepAwait(1.5)
await dom.setValue("Length", str(len(seq)))
else:
lcdBacklightOff()
else:
failure(lambda *args, **kwargs: dom.getL10n(*args, **kwargs))
userSeq = ""
seq = ""
commit()
async def atkSwitchSound(dom, id):
buzzerSwitch(await dom.getValue(id) == "true")
ATK_HEAD = """
<style>
#outer-circle {
background: #385a94;
border-radius: 50%;
height: 360px;
width: 360px;
position: relative;
border-style: solid;
border-width: 5px;
margin: auto;
box-shadow: 8px 8px 15px 5px #888888;
}
#G {
position: absolute;
height: 180px;
width: 180px;
border-radius: 180px 0 0 0;
-moz-border-radius: 180px 0 0 0;
-webkit-border-radius: 180px 0 0 0;
background: darkgreen;
top: 50%;
left: 50%;
margin: -180px 0px 0px -180px;
border-style: solid;
border-width: 5px;
box-sizing: border-box;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
}
#R {
position: absolute;
height: 180px;
width: 180px;
border-radius: 0 180px 0 0;
-moz-border-radius: 0 180px 0 0;
-webkit-border-radius: 0 180px 0 0;
background: darkred;
top: 50%;
left: 50%;
margin: -180px 0px 0px 0px;
border-style: solid;
border-width: 5px;
box-sizing: border-box;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
}
#Y {
position: absolute;
height: 180px;
width: 180px;
border-radius: 0 0 0 180px;
-moz-border-radius: 0 0 0 180px;
-webkit-border-radius: 0 0 0 180px;
background: goldenrod;
top: 50%;
left: 50%;
margin: 0px -180px 0px -180px;
border-style: solid;
border-width: 5px;
box-sizing: border-box;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
}
#B {
position: absolute;
height: 180px;
width: 180px;
border-radius: 0 0 180px 0;
-moz-border-radius: 0 0 180px 0;
-webkit-border-radius: 0 0 180px 0;
background: darkblue;
top: 50%;
left: 50%;
margin: 0px 0px -180px 0px;
border-style: solid;
border-width: 5px;
box-sizing: border-box;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
}
#inner-circle {
position: absolute;
background: grey;
border-radius: 50%;
height: 180px;
width: 180px;
border-style: solid;
border-width: 10px;
top: 50%;
left: 50%;
margin: -90px 0px 0px -90px;
box-sizing: border-box;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
}
button {
font-size: x-large;
margin-top: 5px;
}
</style>
"""
BODY = """
<fieldset>
<legend xdh:onevent="dblclick|UCUqXDevice">Simon's game</legend>
<input id="Color" type="hidden">
<div id="outer-circle">
<div id="G" xdh:onevent="Click"></div>
<div id="R" xdh:onevent="Click"></div>
<div id="Y" xdh:onevent="Click"></div>
<div id="B" xdh:onevent="Click"></div>
<div id="inner-circle" style="display: flex;justify-content: center;align-items: center; flex-direction: column;">
<label style="display: flex; background-color: white; margin-top: 1px;">
<span style="font-size: large;">🎵:</span>
<input id="Buzzer" type="checkbox" xdh:onevent="SwitchSound">
</label>
<div>
<button xdh:onevent="New">{new}</button>
</div>
<div>
<button xdh:onevent="Repeat">{repeat}</button>
</div>
<output id="Length" style="background-color: white; display: inline-grid; width: 2rem; margin-top:5px; justify-content: center;"> </output>
</div>
</div>
</fieldset>
"""
ATK_L10N = (
(
"en",
"fr",
"de"
),
(
"Watch, Remember,",
"Observer, memo-",
"Achten, merken,"
),
(
"Repeat!",
"riser, repeter !",
"wiederholen! "
),
(
"Reproduce the",
"Reproduire la",
"Sequenz"
),
(
"sequence...",
"sequence...",
"wiederholen..."
),
(
"Well done!",
"Bravo!",
"Gut gemacht!"
),
(
"Game over! '{new}'",
"Perdu! '{new}'",
"Verloren! '{new}'"
),
(
"to play again!",
"pour rejouer!",
"zum Neustart!"
),
(
"New",
"Nouveau",
"Neu"
),
(
"Repeat",
"Répéter",
"Wiederhol."
),
(
"Click '{new}' to",
"'{new}'",
"'{new}' anklicken",
),
(
"begin the game!",
"pour commencer !",
"um zu beginnen!"
),
(
"RGBY",
"RVBJ",
"RGBY",
),
)
DIGITS = (
"708898A8C88870",
"20602020202070",
"708808304080f8",
"f8081030088870",
"10305090f81010",
"f880f008088870",
"708880f0888870",
"f8081020404040",
"70888870888870",
"70888878088870",
)
HAPPY_MOTIF = "03c00c30181820044c32524a80018001824181814812442223c410080c3003c0"
SAD_MOTIF = "03c00c30181820044c3280018001824181814002400227e410080c3003c0"
OLED_COEFF = 8
atlastk.launch(globals=globals())
Rock-Paper-Scissors #
import atlastk, ucuq, random, math
lcd = ucuq.HD44780_I2C(16, 2, ucuq.SoftI2C(6, 7))
rgb = ucuq.WS2812(8, 20, 2)
buzzer = ucuq.Buzzer(ucuq.PWM(5))
oled = ucuq.SSD1306_I2C(128, 64, ucuq.I2C(8, 9))
RGB_MAX = 10
COLOR_1 = (0, RGB_MAX, 0)
COLOR_2 = (0, 0, RGB_MAX)
COLOR_TIE = (RGB_MAX, RGB_MAX, 0)
ROCK = 0
PAPER = 1
SCISSORS = 2
NOTHING = 3
noMaster = True
def soundCallback(buzzer, events, duration):
if duration:
ucuq.sleepStart()
for event in events:
if event[1] != -1:
buzzer.off()
if event[1] != 0:
buzzer.play(int(event[1]))
if duration:
ucuq.sleepWait(duration)
def play(song):
ucuq.polyphonicPlay((song,), 120, buzzer, soundCallback)
def lcdScoreAndWinner(score1, score2, winner, L10n):
score = score1 if winner == 1 else score2
scoreString = str(score - 1).center(8) + str(score).center(8)
line2 = " " * 16 + (L10n(6) if winner == 0 else L10n(5).format(winner)).center(16)
for i in range(17):
if winner != 0:
lcd.moveTo(0 if winner == 1 else 8, 0).putString(scoreString[i // 2: i // 2 + 8])
lcd.moveTo(0, 1).putString(line2[i : i + 16])
# ucuq.sleep(0.00001 * (2 if winner else 1) )
def newGame(dom):
global player1, score1, score2
player1 = None
score1, score2 = 0, 0
rgb.fill((0, 0, 0)).setValue(0, COLOR_TIE).write()
play(INTRO_SONG)
lcd.moveTo(0,0).putString("0".center(8) * 2).moveTo(0,1).putString(dom.getL10n(4).center(16)).backlightOn()
async def atkbDisplay(dom, extra):
def getClass(winner, player):
if winner == 3:
return "tie"
elif winner == player:
return "winner"
else:
return "pending"
winner = int(extra[:1])
if not str(id(dom)) == extra[3:] or winner != 0:
await dom.removeClasses({"Player1": ALL_CLASSES, "Player2": ALL_CLASSES})
await dom.addClasses(
{
"Player1": [CLASSES[int(extra[1])], getClass(winner, 1)],
"Player2": [CLASSES[int(extra[2])], getClass(winner, 2)],
}
)
await dom.setValues(
{
"Status": dom.getL10n(1).format(1 if player1 is None else 2),
"Score1": score1,
"Score2": score2,
}
)
async def atk(dom):
global noMaster
if noMaster:
noMaster = False
dom.isMaster = True
newGame(dom)
await dom.inner("", BODY)
await atkbDisplay(dom, f"0{NOTHING}{NOTHING}{id(dom)}")
await dom.setValues({"Title": dom.getL10n(2), "New": dom.getL10n(3), "Score": dom.getL10n(7)})
async def atkNew(dom):
newGame(dom)
atlastk.broadcastAction(atkbDisplay, f"0{NOTHING}{NOTHING}dummyObjectId")
async def handleMove(dom, move):
global player1, score1, score2
rgb.fill((0, 0, 0))
toAll = f"0{NOTHING}{NOTHING}{id(dom)}"
if player1 is None:
player1 = move
oled.fill(0).show()
song = MOVE_SONG
await dom.removeClasses({"Player1": ALL_CLASSES, "Player2": ALL_CLASSES})
await dom.addClasses(
{
"Player1": [CLASSES[move], "pending"],
"Player2": [CLASSES[NOTHING], "pending"],
}
)
else:
toAll = f"{player1}{move}{id(dom)}"
match (winner := WINNER[(player1, move)]):
case 0:
for led in range(3, 6):
rgb.setValue(led, COLOR_TIE)
song = TIE_SONG
toAll = "3" + toAll
case 1:
score1 += 1
for led in range(4, 7):
rgb.setValue(led, COLOR_1)
song = SONG_1
toAll = "1" + toAll
case 2:
score2 += 1
for led in range(2, 5):
rgb.setValue(led, COLOR_2)
song = SONG_2
toAll = "2" + toAll
oled.draw(FLIPPED_OLED_PICTURES[player1], 64).draw(
OLED_PICTURES[move], 64, ox=64
)
if winner != 0:
oled.rect(0 if winner == 1 else 64, 0, 64, 64, 1)
oled.show()
player1 = None
if score1 < score2:
rgb.setValue(0, COLOR_2)
elif score1 == score2:
rgb.setValue(0, COLOR_TIE),
else:
rgb.setValue(0, COLOR_1),
rgb.write()
atlastk.broadcastAction(atkbDisplay, toAll)
play(song)
if player1 is None:
lcdScoreAndWinner(
score1,
score2,
winner,
lambda i : dom.getL10n(i)
)
async def atkClick(dom, id):
await handleMove(dom, int(id))
async def atkAIMove(dom):
await handleMove(dom, random.randrange(3))
def flip(hexString: str, width: int) -> str:
bits = "".join(f"{int(c, 16):04b}" for c in hexString)
flippedBitsLines = []
for y in range(len(bits) // width):
line = bits[y * width : (y + 1) * width]
flippedLines = line[::-1]
flippedBitsLines.append(flippedLines)
flippedBits = "".join(flippedBitsLines)
result = ""
for i in range(len(flippedBits) // 4):
nibble = flippedBits[4 * i : 4 * (i + 1)]
result += f"{int(nibble, 2):x}"
return result
ATK_L10N = (
("en", "fr", "de"),
("To player {}…", "Au joueur {}…", "An Spieler {} …"),
("Rock paper scissors", "Pierre-feuille-ciseaux", "Schere, Stein, Papier"),
("New game", "Nouvelle partie", "Neues Spiel"),
("Let's play!", "C'est parti!", "Los geht's!"),
("Player {} wins!", "Joueur {} gagne !", "Spieler {} siegt!"),
("It's a tie!", "Match nul !", "Unentschieden!"),
("Score", "Score", "Stand")
)
WINNER = {
(ROCK, ROCK): 0,
(ROCK, PAPER): 2,
(ROCK, SCISSORS): 1,
(PAPER, ROCK): 1,
(PAPER, PAPER): 0,
(PAPER, SCISSORS): 2,
(SCISSORS, ROCK): 2,
(SCISSORS, PAPER): 1,
(SCISSORS, SCISSORS): 0,
}
INTRO_SONG = "C42 C52 B42 D42 E42 A42 G42"
TIE_SONG = "C24"
SONG_1 = "C42 E42 G42"
SONG_2 = "C42 G42 E42"
MOVE_SONG = "G41"
OLED_PICTURES = (
"""000000000000000000000000000000000000000000000000000000000000000000000000000000000000001e000000000000003f000000000000007f80000000000000ffc0000000000000ffe00000000000007f000000000000007e00000000000000787000000000007e31fe0000000000ff03ff8000000000ff87ffe000000000ff1ffff000000000fc3ffff800000000f8fffffc0000000071fffffe0000000003fe7fff0000000f07fe3fff0000001f8ffc1fff8000003fc7f80fff8000003fe7e00fff8000003fe1c30fff8000001ff8071fffc000000ffe1e1fffc0000007ff801fffc0000001ffc09fffc0000000ffc71fffc000007c7fcf1fffc000007e1fcf1fffc00000ff0fcf9fffc00000ffc79f8fffe00000ffe03fcffff00000fff07fcffffc00003ff8ffe7fffe00001ffcffe3ffff000007fcfff1ffff000003fdfff8ffff000000f9fffdfffe00000071fffffffe00000003fffffffc00000007fffffffc0000007ffffffff80000007ffffffff00000003ffffffff00000000fffffffe000000003ffffffc000000001ffffff80000000001fffff80000000000007fe00000000000007fc00000000000003f800000000000001f000000000000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000""",
"""000000000000000000000000000000000000000000000000000000000000000000000000000000000000038000000000000007c000000000000007e000000000001e07f000000000001f03f800000000003f83fc00000000001fc1fe00000000000fe0fe000000000007f07f000000000007f87f800600000003fc3fc00f000001e1fe1fe01f800001f0ff0fe01f800003f87f87f01fc00001fc3fc3f81fe00000fe1fe3fc1fe00000ff0ff1fc0ff000007f87f8ff0ff800003fc7f87f87f800001fe3fc7fc3f800000ff1ffffe3fc000307f8fffff3fc000383fc7fffe3fe0003c1fe3fffe3fe0003e0ff1fcfe7fe0001f07f9fcfe7fe0001fc3fff8fe7ff0000fe1fff9fcfff00007f0fff9fcffe00003f87ff1fcffe00003fc7ff3fcffe00001fe1fe3fcffe00000ff1fe7fcffe000007fffcffeffe000003fff8fffffe000001fff1fffffe0000007ff3fffffe0000003ffffffffe0000001ffffffffe00000007fffffffe00000007ffffffff00000003ffffffff00000001ffffffff00000001ffffffff80000000ffffffff800000003fffffff800000001fffffff0000000007ffffff0000000003fffffe0000000000fffffc00000000007ffff800000000000ffff0000000000001ffe00000000000000fc00000000000000f00000000000000000000000000000000000000000000000000000000000000000000""",
"""00000000000000000000000000000000000000000000000000000e000000000000000f000000000000001f800000000000000fc00000000000000fc000000000000007e000000000000007e000000000000007f000000000000003f800000000000001fc00000000000001fc00000000078001fe000000000ff000fe000000000ff800ff0000000007fe007f0000000003ff807fc000000001ffc07fc000000000ffe03fe0000000003ffc3ff0000000000ffe10000000000007ff83000000000000ff87e000000000007f1ffe00000000001e3fff80000000000cfffff00000000001fffffc0000000003ffbffe0000000007fe1fff0000000003f80fff000000000001c7ff800000000003e3ff800000007e0ff8ff800000007f81fc7fc00000007ff0fe7fc00000003ffc1e7fc00000003fff0e7fe00000000fffc67fe000000003ffc67fe000000000ffe63fe0000000001fe63fe00000003e03c73ff00000003fc00f3ff00000003ff01f9ff80000003ffc7f8ffc0000000ffe3fcfff00000007ff3fffff00000000ff3fffff000000003f3fffff00000000007ffffe0000000000fffffc000000000ffffffc0000000007fffff80000000003fffff00000000000ffffe000000000003fffc00000000000007f000000000000003e00000000000000180000000000000000000000000000000000000000000000000000""",
)
CLASSES = {ROCK: "rock", PAPER: "paper", SCISSORS: "scissors", NOTHING: "nothing"}
ALL_CLASSES = list(claz for claz in CLASSES.values()) + ["tie", "winner", "pending"]
print(ALL_CLASSES)
FLIPPED_OLED_PICTURES = tuple(flip(picture, 64) for picture in OLED_PICTURES)
ATK_HEAD = """
<style>
.hand {
background-repeat: no-repeat;
background-size: 100% 100%;
display: inline-block;
width: 94px;
height: 94px;
border: transparent 5px solid;
}
.sensitive {
cursor: pointer;
}
.rock {
background-image: url(data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDADUlKC8oITUvKy88OTU/UIVXUElJUKN1e2GFwarLyL6qurfV8P//1eL/5re6////////////zv//////////////2wBDATk8PFBGUJ1XV53/3Lrc////////////////////////////////////////////////////////////////////wAARCABeAF4DASIAAhEBAxEB/8QAGQABAAMBAQAAAAAAAAAAAAAAAAIDBAUB/8QAKhAAAgIBAwMEAQQDAAAAAAAAAQIAAxEEEiExQVETImGBMiMzobE0cZH/xAAYAQADAQEAAAAAAAAAAAAAAAAAAQIDBP/EAB4RAQEBAQACAwEBAAAAAAAAAAABEQIhMQMSMkFR/9oADAMBAAIRAxEAPwDDERAEREASTI6jLKQPkTVoUU7nPJHA+JrYAqd2Md8zLr5MuKnOxyIlt6Ij/puGU+D0lU0l1JERGCIiAIiIB6il3CjqTidBdLUFwVyfJmRKrlxYEOBzNR1QYAVKWdu3iZd230vnP6rIGjtyMlG7dxJhLNQc2ZSvsvcyddGG32ne/wDAljuqKWY4Amd6/wA9niB01JGNgma3RleazuHjvNqkMoIOQZ7FO7DslceJu1dAZTYo9w6/Mwzo56+01nZhERKIllG31l3/AI57yuTqqa1sKP8AZ8RX0I6sz2oarRci5HRgP7kVNmmwH99fkdppVldQynIM5vy19qTq6tuQST4xKrEsvRnf2KBlVlmqrHpmxRh15BE8Wl7UU22kqRnaOJUyTYV30hoA3uOTt6Y+ZsmfRfsfZmiT3+qfPonJtXZay+DOtObq/wDJf6/qX8V8l2piIm7Mm3QugQrnDE/9mKJPXP2mHLjsHkYMzNS1TF6PtPMqo1ZX228jzNVl6JXu3A+Md5hnXNxeyqN1mrUALsrzyc9ZqAAAA6CVaZClIDdTzLour5yHGek+le9R6MdyzRK76vVXg4YcgySbtg343d8QvnyIlM2p03qEun5dx5mmIpbLsOzXHIIOCMERLdS4suJHQcSqdU8xiRERgm7TLQ4DKvuHUE9JhnqsVOVODJ6mw5cdeJhr1rAYdc/Ilo1tXhh9TC8dRp9o0xKE1dbuFGRnuZfJss9nuko1V3p17Qfc38Sd1y0rk8nsJzXdrHLMeTL443zU9XEYiJ0MyIiAIiIAiIgCaV1jCrbjLeTM0RWS+zlx6zF23MckzyIjIiIgH//Z);
}
.paper {
background-image: url(data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDADUlKC8oITUvKy88OTU/UIVXUElJUKN1e2GFwarLyL6qurfV8P//1eL/5re6////////////zv//////////////2wBDATk8PFBGUJ1XV53/3Lrc////////////////////////////////////////////////////////////////////wAARCABeAF4DASIAAhEBAxEB/8QAGQABAAMBAQAAAAAAAAAAAAAAAAIDBAUB/8QAKhAAAgIBAwMEAQQDAAAAAAAAAQIAAxEEITESQVETIjJhcnGBobEzNEL/xAAYAQADAQEAAAAAAAAAAAAAAAAAAQIDBP/EACARAAICAgICAwAAAAAAAAAAAAABAhEhMQMSIjJRYXH/2gAMAwEAAhEDEQA/AMMRAGSAO8AETadCvRsx6vviZ6x6WoX1BjB3kqSeh1RBkdPkpGfIkZ12VXUhhkGcy+o0vg8djJhPsNxoriImhIiIgAiIgAklrdgWVSQO4kZ0tKytQoXtsRInLqrGlZ5prxauD8xz9z3UUC5dtmHBlWooKt6tOxG5Al1FwuXww5ExePKJf0ynS3FT6NmxHGf6mi2pbUKt+x8SvU0eoOpdnH8xprvUXpbZ15+4PPkgXwzBYjVuVbkSIGTgczpaigXJtsw4Mw0+zUL17YO+ZtGdolqmWto2Wot1ZYb4madic/V0+m/Uo9rfwZMJ26Y5RrRniImpAl1Zt0zBypCnt5lI2M6iMmoq3GQeR4kTdFRVkkdbFDKcgzPdSyN6tOxHI8yBSzSP1L7qzzNaOtihlOQZj65Wit7I02rcmRz3HiQtoLWCyshXHP3DUEXCyo9Jz7h5l8V07Q97Ez6nTer7l2f+5KuxnvsH/C7fvLLG6K2bwMwVxYbRVpLC9ZVvkm0suT1KmXzxK9IvTQD3bcy+EsSwC0cfiJZqBi9wPMrnSsoyEtouNL55U8iVRBq8AdZWWxMjBUzM6tpX603rPI8TPRe1LeVPInRR1sXKnImDTh+GidnqkMoI4IzIXWCqst37frLJQamsv6rPgvxHn7kKryNktNWa6hn5NuZ5qv8AWfEuggEYIyIXmwrFEKtqU/EScREM5V5ze/5GQk7v81n5H+5XOtaMWexERgJOm1qnDDjuPMhEGrA6yMHUMpyDJTl03tSdt17idCq5LRlTv3Hec0oOJqnZZERIGIiIAc7WV9FxPZt5nmnWXCxwq8L3madUbrJk9nsREoQiIgAgEqcg4P1E8gBqr1rqMOOr74MuXW1k4IYTnxIfHFldmdjIxnIx5mPU6oEFKz+rTKXYqFLEgcDMjJjxpZYOQiImpJ//2Q==);
}
.scissors {
background-image: url(data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDADUlKC8oITUvKy88OTU/UIVXUElJUKN1e2GFwarLyL6qurfV8P//1eL/5re6////////////zv//////////////2wBDATk8PFBGUJ1XV53/3Lrc////////////////////////////////////////////////////////////////////wAARCABeAF4DASIAAhEBAxEB/8QAGQAAAgMBAAAAAAAAAAAAAAAAAAEDBAUC/8QAKRAAAgIBAwQCAgEFAAAAAAAAAQIAAxEEITESE0FRInEygaEzQmFisf/EABgBAAMBAQAAAAAAAAAAAAAAAAACAwEE/8QAHREAAgICAwEAAAAAAAAAAAAAAAECESExAxJBIv/aAAwDAQACEQMRAD8AowhCABJKKTc+M4A5Mjk2ks6LgDw20yV1g1bFqKDSRvlTwZDNa2sWVlT5mWylGKtyIkJdlk2SoUIRSgo4RRwAIQhABohscKvJlltCwHxcE+iMSvW5rcMORNGnUJbsDhvRk5uS0NFJma6MjYYEGKaz1rYuHGRKN2kZN0+S/wAiEeRPYONFyizuVK3ng/ch1tPUvcUbjn6kWhs6XKHhuPuXjjgyT+JDLKMeEsamjtNlfwP8SvOhO1aJtUEIQmgEcUIAOEIoAWqdYybWfIe/MupYti5Q5EyJ3WQHGWKg8kScuNPKGUi3cEe0Clc2g5JHA+52NKrDNrFnPJzJKu0iYrK4+5zZqUXZPm3gLJ29Ial6cNpD0lVtYA+DuJVs01te5XI9iXabuslHHTYORJod5R2FJmNCaOp06ujMow4328zOloyUkI1QQhCMYOKEIAE1EppNYwikY5xMuT6ey5P6all8jGREmm1gaLJ7NEp3rPSfR4glnY2spC/7KJ2mqRjhwUb0ZPsw8EGRbepDUvCJ0r1ChlbccMPEKLHZnR8Ep5HmcdAp1KCvYPnKx1/HWWqf7gCIeAWJl6hO3cy+ORNSUteu6P8AqbxumElgpwhCdBMcUcIAKX9JcnbCEhWHvzKMUWUeyo1OjXetLBh1BlW3TNWC9LsAORmV6r7K8ANt6PEuF73TArUZH5dW0lUose0zqitQBYGLlh+TRXo3UttYyy+PYklSdutUznE7iXmzawE4trW1Olv0fU7iJABJOAIqNMq2tqn6W/R9zmSX2920t44EinWrrJFjhCE0AijigASanUPVwcr6MhjmNXsDQTWVMPllT/mSJdW7dKsCZlwBIIIOCJN8S8G7M12YKCWOAJQ1OpNvxXZP+yGy17Dl2JnM2PHWWDlYo4oSgp//2Q==);
}
.nothing {
background-image: url(data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDADUlKC8oITUvKy88OTU/UIVXUElJUKN1e2GFwarLyL6qurfV8P//1eL/5re6////////////zv//////////////2wBDATk8PFBGUJ1XV53/3Lrc////////////////////////////////////////////////////////////////////wAARCABeAF4DASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAT/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFAEBAAAAAAAAAAAAAAAAAAAAAP/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhEDEQA/AIQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf/2Q==);
}
.winner {
border-color: red;
}
.tie {
border-color: transparent;
}
.pending {
border-color: transparent;
}
</style>
"""
BODY = """
<fieldset>
<legend id="Title"></legend>
<fieldset>
<div style="white-space: nowrap;">
<span id="0" xdh:onevent="Click" class="hand sensitive rock"></span>
<span id="1" xdh:onevent="Click" class="hand sensitive paper"></span>
<span id="2" xdh:onevent="Click" class="hand sensitive scissors"></span>
</div>
<br />
<div style="display: flex;">
<h2 style="margin:auto;" id="Status"></h2>
</div>
<br />
<div style="display: flex;">
<button xdh:onevent="AIMove" style="margin:auto; width: 5em; font-size: larger;">🤖</button>
</div>
</fieldset>
<fieldset>
<div style="width: 100%;">
<div style="display: flex; justify-content: space-around;">
<span id="Player1" style="transform: scaleX(-1)" class="hand nothing pending"></span>
<span id="Player2" class="hand nothing pending"></span>
</div>
<br />
<fieldset style="display: flex; justify-content: space-around;">
<span id="Score1"></span>
<span id="Score"></Span>
<span id="Score2"></span>
</fieldset>
</fieldset>
<br>
<div style="display: flex;">
<button xdh:onevent="New" style="margin: auto;" id="New"></button>
</div>
</fieldset>
"""
atlastk.launch(globals=globals())
Morpion #
Tic-tac-toe game.
import atlastk, ucuq, random, math
lcd = ucuq.HD44780_I2C(16, 2, ucuq.SoftI2C(6, 7))
rgb = ucuq.WS2812(8, 20)
buzzer = ucuq.Buzzer(ucuq.PWM(5))
oled = ucuq.SSD1306_I2C(128, 64, ucuq.I2C(8, 9))
RGB_MAX = 5
CROSS_COLOR = (0, RGB_MAX, 0)
CIRCLE_COLOR = (0, 0, RGB_MAX)
TIE_COLOR = (RGB_MAX, RGB_MAX, 0)
SOUND_DELAY = 0.5
CROSS_SOUND = "C42"
CIRCLE_SOUND = "F42"
TIE_SOUND = "F42 E42 D42 C43"
WIN_SOUND = "C42 D42 E42 F43"
INTRO_SOUND = "F43 G42 A42 Bb42 C52 D52 E52 F53"
currentPlayer = ""
winningCombos = []
board = None
def soundCallback(buzzer, events, duration):
if duration:
ucuq.sleepStart()
for event in events:
if event[1] != -1:
buzzer.off()
if event[1] != 0:
buzzer.play(int(event[1]))
if currentPlayer == "":
for led in range(8):
rgb.setValue(
led,
tuple(
int(math.exp(random.uniform(0, math.log(RGB_MAX))))
for _ in range(3)
),
)
rgb.write()
if duration:
ucuq.sleepWait(duration)
def soundPlay(sound):
ucuq.polyphonicPlay((sound,), 120, buzzer, soundCallback)
def oledGrid():
oled.fill(0).rect(30, 0, 64, 64, 1).hline(31, 21, 63, 1).hline(31, 42, 63, 1).vline(51, 0, 63, 1).vline(72, 0, 63, 1).show()
def oledConvert(i, j):
return (41 + 21 * int(j), 11 + 21 * int(i))
def oledCircle(i, j):
x, y = oledConvert(i, j)
oled.ellipse(x, y, 6, 6, 1, False).show()
def oledCross(i, j):
x, y = oledConvert(i, j)
oled.line(x - 5, y - 5, x + 5, y + 5, 1).line(x - 5, y + 5, x + 5, y - 5, 1).show()
def oledWin(combo):
def sign(a, b):
return (a < b) - (a > b)
x1, y1 = oledConvert(*combo[0])
x2, y2 = oledConvert(*combo[2])
ox = 9 * sign(x1, x2)
oy = 9 * sign(y1, y2)
oled.line(x1 - ox, y1 - oy, x2 + ox, y2 + oy, 1).show()
def initWinningCombos():
global winningCombos
for i in range(3):
winningCombos.append(tuple((i, j) for j in range(3)))
winningCombos.append(tuple((j, i) for j in range(3)))
winningCombos.append(tuple((i, i) for i in range(3)))
winningCombos.append(tuple((i, 2 - i) for i in range(3)))
async def setStatus(dom, text):
if dom.isMaster:
text = " " * 16 + text.replace("…", "...") + " "
for _ in range(16):
text = text[1:]
lcd.moveTo(0, 1).putString(text[:16])
ucuq.sleep(0.01)
await dom.setValue("Status", text)
def newGame():
global currentPlayer, board
currentPlayer = ""
lcd.moveTo(0, 1).putString(" " * 16)
board = [["" for _ in range(3)] for _ in range(3)]
oledGrid()
soundPlay(INTRO_SOUND)
rgb.fill((0, 0, 0))
currentPlayer = "x"
def getWinner(board):
for combo in winningCombos:
values = [board[i][j] for i, j in combo]
if values[0] != "" and len(set(values)) == 1:
return (values[0], combo)
for i in range(3):
for j in range(3):
if board[i][j] == "":
return (None, None)
return ("", None)
async def atkbDisplay(dom):
global currentPlayer
crossCount = 0
circleCount = 0
removedClasses = {}
addedClasses = {}
for i in range(3):
for j in range(3):
await dom.setValue(f"cell{i}{j}", board[i][j])
match board[i][j]:
case "x":
if dom.isMaster:
oledCross(i, j)
rgb.setValue(crossCount, CROSS_COLOR)
crossCount += 1
removedClasses[f"cell{i}{j}"] = ["o", "win"]
addedClasses[f"cell{i}{j}"] = "x"
case "o":
if dom.isMaster:
oledCircle(i, j)
rgb.setValue((circleCount + 4) % 8, CIRCLE_COLOR)
circleCount += 1
removedClasses[f"cell{i}{j}"] = ["x", "win"]
addedClasses[f"cell{i}{j}"] = "o"
case "":
removedClasses[f"cell{i}{j}"] = ["x", "o", "win"]
await dom.removeClasses(removedClasses)
await dom.addClasses(addedClasses)
winner, combo = getWinner(board)
if combo:
await dom.addClasses(dict(zip([f"cell{i}{j}" for i, j in combo], ["win"] * 3)))
if dom.isMaster:
oledWin(combo)
rgb.fill(CROSS_COLOR if winner == "x" else CIRCLE_COLOR).write()
soundPlay(WIN_SOUND)
await setStatus(dom, dom.getL10n(6).format(winner.lower()))
currentPlayer = ""
return
elif winner == "":
if dom.isMaster:
rgb.fill(TIE_COLOR).write()
soundPlay(TIE_SOUND)
await setStatus(dom, dom.getL10n(5))
currentPlayer = ""
return
if dom.isMaster:
if crossCount + circleCount:
rgb.write()
await setStatus(dom, dom.getL10n(4).format(currentPlayer.lower()))
async def atk(dom):
await dom.inner("", BODY)
await dom.setValues({"Title": dom.getL10n(1), "Button": dom.getL10n(3)})
dom.isMaster = board is None
if dom.isMaster:
lcd.clear().backlightOn().moveTo(0, 0).putString(dom.getL10n(2))
newGame()
await atkbDisplay(dom)
async def atkNew(dom):
newGame()
atlastk.broadcastAction(atkbDisplay)
def move(moves):
global currentPlayer
if currentPlayer == "":
return
i, j = moves[random.randrange(len(moves))]
if board[i][j] == "":
soundPlay(CROSS_SOUND if currentPlayer == "x" else CIRCLE_SOUND)
board[i][j] = currentPlayer
currentPlayer = "o" if currentPlayer == "x" else "x"
atlastk.broadcastAction(atkbDisplay)
async def atkClick(dom, id):
i = int(id[4])
j = int(id[5])
move(((i, j),))
def minimax(board, player):
winner, _ = getWinner(board)
match winner:
case "x":
return -1
case "o":
return 1
case "":
return 0
if player == "o":
score = -2
for i in range(3):
for j in range(3):
if board[i][j] == "":
board[i][j] = "o"
score = max(score, minimax(board, "x"))
board[i][j] = ""
return score
else:
score = 2
for i in range(3):
for j in range(3):
if board[i][j] == "":
board[i][j] = "x"
score = min(score, minimax(board, "o"))
board[i][j] = ""
return score
def bestMoves(board, player):
bestScore = -2 if player == "o" else 2
for i in range(3):
for j in range(3):
if board[i][j] == "":
board[i][j] = player
score = minimax(board, "x" if player == "o" else "o")
board[i][j] = ""
if score == bestScore:
moves.append((i, j))
elif (player == "o") ^ (score < bestScore):
bestScore = score
moves = [(i, j)]
return moves
def noCircle(board):
xMove = (0, 0)
for i in range(3):
for j in range(3):
match board[i][j]:
case "o":
return None
case "x":
xMove = (i, j)
return xMove
async def atkIAMove(dom):
if currentPlayer == "":
return
xMove = noCircle(board)
if xMove is None:
move(bestMoves(board, currentPlayer))
elif currentPlayer == "x":
move(((random.randrange(3), random.randrange(3)),))
else:
angles = tuple((i, j) for i in (0, 2) for j in (0, 2))
if xMove == (1, 1):
move(angles)
elif xMove in angles:
move(((1, 1),))
else:
moves = ((0, 0), (0, 2), (2, 1), (1, 1))
if xMove == (1, 0):
moves = tuple((j, i) for i, j in moves)
elif xMove == (2, 1):
moves = tuple((abs(i - 2), j) for i, j in moves)
elif xMove == (1, 2):
moves = tuple((j, abs(i - 2)) for i, j in moves)
move(moves)
ATK_L10N = (
("en", "fr", "de"),
("Tic-tac-toe", "Morpion", "Dodelschach"),
("Tic-tac-toe game", " Jeu du morpion ", " Dodelschach "),
("New game", "Nouvelle partie", "Neues Spiel"),
("To player {}…", "Au joueur {}…", "An Spieler {}…"),
("It's a tie!", "Match nul !", "Unentschieden!"),
("Winner: {}!", "Vainqueur : {} !", "Sieger: {}!"),
)
initWinningCombos()
ATK_HEAD = """
<link href="https://fonts.googleapis.com/css?family=Indie+Flower" rel="stylesheet">
<style>
h1,
h2 {
font-family: 'Indie Flower', 'Comic Sans', cursive;
text-align: center;
margin: 10px;
}
#board {
font-family: 'Indie Flower', 'Comic Sans', cursive;
position: relative;
font-size: 120px;
margin: 1% auto;
border-collapse: collapse;
}
#board td {
border: 4px solid rgb(60, 60, 60);
width: 90px;
height: 90px;
vertical-align: middle;
text-align: center;
cursor: pointer;
}
#board td div {
width: 90px;
height: 90px;
line-height: 90px;
display: block;
overflow: hidden;
cursor: pointer;
position: relative;
font-size: 1.2em;
}
.x {
color: lightgreen;
cursor: default !important;
}
.o {
color: lightblue;
cursor: default !important;
}
.win {
background-color: yellow;
}
</style>
"""
BODY = """
<h1 id="Title"></h1>
<fieldset>
<table id="board">
<tbody>
<tr>
<td>
<div id="cell00" class="cell" xdh:onevent="Click"></div>
</td>
<td>
<div id="cell01" class="cell" xdh:onevent="Click"></div>
</td>
<td>
<div id="cell02" class="cell" xdh:onevent="Click"></div>
</td>
</tr>
<tr>
<td>
<div id="cell10" class="cell" xdh:onevent="Click"></div>
</td>
<td>
<div id="cell11" class="cell" xdh:onevent="Click"></div>
</td>
<td>
<div id="cell12" class="cell" xdh:onevent="Click"></div>
</td>
</tr>
<tr>
<td>
<div id="cell20" class="cell" xdh:onevent="Click"></div>
</td>
<td>
<div id="cell21" class="cell" xdh:onevent="Click"></div>
</td>
<td>
<div id="cell22" class="cell" xdh:onevent="Click"></div>
</td>
</tr>
</tbody>
</table>
<h2 id="Status"></h2>
<div style="display: flex;">
<button xdh:onevent="IAMove" style="margin:auto; width: 5em; font-size: larger;">🤖</button>
</div>
</fieldset>
<br/>
<div style="display: flex;">
<button xdh:onevent="New" style="margin: auto;" id="Button"></button>
</div>
"""
atlastk.launch(globals=globals())