Le kit Ravel

Le kit Ravel #

Pour une utilisation avec simulateur (plus d’explications ici) :

  • Simulation Wokwi ;
  • configuration: .

Bouton Run pour (re)lancer l’application.

Headless (sans interface graphique) #

RAZ #

Remise à zéro du 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()

Gammes #

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 affichage #

import ucuq MESSAGE = "Salutations !" lcd = ucuq.HD44780_I2C(16, 2, ucuq.SoftI2C(6, 7)) lcd.putString(MESSAGE) lcd.backlightOn()

Scrollings #

import ucuq MESSAGE = "Hello, comment va ?" 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 #

Uni #

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 #

Dessin #

import ucuq oled = ucuq.SSD1306_I2C(128, 64, ucuq.I2C(8, 9)) oled.draw("03c00c30181820044c32524a80018001824181814812442223c410080c3003c0", 16, mul=4, ox=32).show()

Multi-composants #

Panthère rose #

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()

Avec interface graphique #

Boilerplate (exemple de base) #

Une application minimaliste pour allumer/éteindre l’afficheur 7 segments (simulation) ou la DEL embarquée (kit physique).

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())

Figures de Chernoff #

Application affichant des figures de Chernoff.

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 #

Un jeu inspiré de Simon.

NOTA : pour un utilisation avec le simulateur, décommenter (enlever le # et l’espace qui suit) l’expression SIMULATOR = True à la ligne 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())

Pierre-feuille-ciseaux #

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 #

Le jeu du morpion.

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())

Dépannage