Le kit Ravel
#
Pour une utilisation avec simulateur (plus d’explications ici ) :
Bouton Run pour (re)lancer l’application.
Headless (sans interface graphique)
#
RAZ
#
Remise à zéro du kit
import ucuq
ucuq.Ravel.raz()
Buzzer
#
Beep
#
import ucuq
FREQ = 440
buzzer = ucuq.Ravel.Buzzer()
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.Ravel.Buzzer()
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()
OLED
#
Texte
#
import ucuq, datetime
oled = ucuq.Ravel.OLED()
oled.text("Nous sommes le".center(16), 0, 20).text(f"{datetime.datetime.now().strftime("%d/%m/%Y %H:%M").center(16)}", 0, 40).show()
Dessin
#
import ucuq
oled = ucuq.Ravel.OLED()
oled.draw("03c00c30181820044c32524a80018001824181814812442223c410080c3003c0", 16, mul=4, ox=32).show()
Anneau RGB
#
Uni
#
import ucuq
R = 10
G = 8
B = 1
ring = ucuq.Ravel.Ring()
ring.fill((R, G, B)).write()
Rainbow
#
import ucuq, random
MIN = 1
MAX = 5
ring = ucuq.Ravel.Ring()
for _ in range(15):
for led in range(8):
ring.setValue(led, tuple((lambda: random.randint(MIN, MAX))() for _ in range(3))).write()
ucuq.sleep(.03)
ucuq.sleep(2)
ring.fill((0, 0, 0)).write()
LCD
#
Simple affichage
#
import ucuq
lcd = ucuq.Ravel.LCD()
lcd.putString("Salutations !".center(16)).backlightOn()
Vagues
#
import ucuq
DELAY = 0.07
COUNT = 2
lcd = ucuq.Ravel.LCD()
lcd.uploadGaugeChars().backlightOn()
gauges = ()
def mirror(str):
return str[:8] + tuple(str[i] for i in range(7, -1, -1,))
def morph(a, b):
steps = max(abs(x - y) for x, y in zip(a, b))
for s in range(1, steps):
ucuq.sleep(DELAY)
lcd.putGauges(0, [int(ai + s / (steps - 1) * (bi - ai)) for ai, bi in zip(a, b)])
for j in range(0, 32 * COUNT):
gauges = ((abs((j + 16) % 32 - 16),) + gauges)[:16]
ucuq.sleep(DELAY)
lcd.putGauges(0, gauges)
morph(gauges, mirror(gauges))
for j in range(0, 32 * COUNT):
gauges = ((abs((j + 16) % 32 - 16),) + gauges)[:16]
ucuq.sleep(DELAY)
lcd.putGauges(0, mirror(gauges))
morph(mirror(gauges), (0,) * 16)
ucuq.sleep(DELAY)
lcd.backlightOff()
Multi-composants
#
Panthère rose
#
import ucuq, random, zlib, base64
RING_MAX = 10
RING_MIN = 2
ravel = ucuq.Ravel()
buzzer = ravel.buzzer()
oled = ravel.oled()
lcd = ravel.lcd()
ring = ravel.ring()
unpack = lambda data: zlib.decompress(base64.b64decode(data)).decode()
def rainbowGradient(n, max):
colors = []
for i in range(n):
h = (i * 6) / n
x = int((1 - abs((h % 2) - 1)) * max)
if 0 <= h < 1: r, g, b = max, x, 0
elif 1 <= h < 2: r, g, b = x, max, 0
elif 2 <= h < 3: r, g, b = 0, max, x
elif 3 <= h < 4: r, g, b = 0, x, max
elif 4 <= h < 5: r, g, b = x, 0, max
else: r, g, b = max, 0, x
colors.append((r, g, b))
return colors
RAINBOW = rainbowGradient(50, RING_MAX)
def voiceCallback(freq):
global led
if freq !=-1:
buzzer.off()
if freq != 0:
buzzer.on(freq)
ring.setValue(led % 8, RAINBOW[led % len(RAINBOW)]).write()
led += 1
ring.setValue(led % 8,(0,0,0)).write()
ravel.displayRingGauges()
def durationCallback(duration, cumul):
global pantherPict, pantherDelay
if cumul > pantherDelay * pantherPict:
oled.fill(0).show()
oled.draw(unpack(PANTHER[pantherPict % len(PANTHER)]), 128).show()
pantherPict += 1
ucuq.sleepWait(duration)
ucuq.sleepStart()
def main():
lcd.backlightOn().uploadGaugeChars()
ucuq.sleepStart()
ucuq.playVoices(VOICES, 120, lambda freq: voiceCallback(freq), lambda duration, cumul: durationCallback(duration, cumul))
lcd.clear()
TEXT = " " * 14 + "That's all folks!" + " " * 16
for i in range(64):
ring.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()
ring.fill((0, 0, 0)).write()
PANTHER = (
'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'
)
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
led = random.randrange(len(RAINBOW))
LED_LIMITER = 15
main()
Avec interface graphique
#
Orientation
#
Cette application ne fonctionne que sur un dispositif mobile (tablette, smartphone…) équipé des capteurs appropriés et affiche un cube 3D représentant l’orientation du dispositif.
Cette application doit être ouverte dans son propre onglet pour fonctionner (cliquer sur dans l’encart ci-dessous) !
import atlastk
import ucuq
import math
WIDTH_ = 128
HEIGHT_ = 64
buffer_ = [[0]*WIDTH_ for _ in range(HEIGHT_)]
def setPixel_(x, y):
if 0 <= x < WIDTH_ and 0 <= y < HEIGHT_:
buffer_[y][x] = 1
def drawLine_(x0, y0, x1, y1):
dx = abs(x1 - x0)
dy = -abs(y1 - y0)
sx = 1 if x0 < x1 else -1
sy = 1 if y0 < y1 else -1
err = dx + dy
while True:
setPixel_(x0, y0)
if x0 == x1 and y0 == y1:
break
e2 = 2 * err
if e2 >= dy:
err += dy
x0 += sx
if e2 <= dx:
err += dx
y0 += sy
def rotatePoint_(x, y, z, pitch, roll, yaw):
p = math.radians(pitch)
r = math.radians(roll)
w = math.radians(yaw)
# Pitch (X)
y2 = y * math.cos(p) - z * math.sin(p)
z2 = y * math.sin(p) + z * math.cos(p)
# Yaw (Y)
x3 = x * math.cos(w) + z2 * math.sin(w)
z3 = -x * math.sin(w) + z2 * math.cos(w)
# Roll (Z)
x4 = x3 * math.cos(r) - y2 * math.sin(r)
y4 = x3 * math.sin(r) + y2 * math.cos(r)
return x4, y4, z3
def project_(x, y, z, scale=40):
distance = 3
factor = scale / (z + distance)
xp = int(WIDTH_/2 + x * factor)
yp = int(HEIGHT_/2 - y * factor)
return xp, yp
cubeVertices_ = [
(-1,-1,-1), (1,-1,-1), (1,1,-1), (-1,1,-1),
(-1,-1, 1), (1,-1, 1), (1,1, 1), (-1,1, 1)
]
cubeEdges_ = [
(0,1),(1,2),(2,3),(3,0),
(4,5),(5,6),(6,7),(7,4),
(0,4),(1,5),(2,6),(3,7)
]
def clearBuffer_():
for y in range(HEIGHT_):
for x in range(WIDTH_):
buffer_[y][x] = 0
def bufferToHex_():
hex_string = []
for y in range(HEIGHT_):
for x in range(0, WIDTH_, 4):
nibble = (
(buffer_[y][x] << 3) |
(buffer_[y][x+1] << 2) |
(buffer_[y][x+2] << 1) |
(buffer_[y][x+3] << 0)
)
hex_string.append(f"{nibble:X}")
return "".join(hex_string)
def draw3DCube(x, y, z):
"""
x = alpha (0–360°) → yaw
y = beta (-180–180°) → pitch
z = gamma (-90–90°) → roll
"""
yaw = -x
pitch = -y
roll = -z
clearBuffer_()
projected = []
for (vx, vy, vz) in cubeVertices_:
xr, yr, zr = rotatePoint_(vx, vy, vz, pitch, roll, yaw)
xp, yp = project_(xr, yr, zr)
projected.append((xp, yp))
for a, b in cubeEdges_:
x0, y0 = projected[a]
x1, y1 = projected[b]
drawLine_(x0, y0, x1, y1)
return bufferToHex_()
async def atk(dom):
global lcd_, oled_
lcd_ = ucuq.Ravel.LCD()
oled_ = ucuq.Ravel.OLED()
await dom.inner("", BODY)
async def atkDisplayOrientation(dom, values):
x, y, z = (float(value) for value in values.split(","))
lcd_.backlightOn().moveTo(0, 0).putString(f"{f"{int(z):+3d} {int(x):4d} {int(y):+4d}".center(16)}".ljust(32))
oled_.draw(draw3DCube(x, y, z), 128).show()
ATK_HEAD = """
<script>
async function getOrientation() {
if (!window.DeviceOrientationEvent || !window.DeviceOrientationEvent.requestPermission) {
return alert("Your current device does not have access to the DeviceOrientation event");
}
let permission = await window.DeviceOrientationEvent.requestPermission();
if (permission !== "granted") {
return alert("You must grant access to the device's sensor for this demo");
}
}
var previousTime = 0;
window.addEventListener("deviceorientation", function (e) {
let x = Math.round(e.alpha);
let y = Math.round(e.beta);
let z = Math.round(e.gamma);
document.getElementById('PartnerOrientationX').innerHTML = `${x}°`;
document.getElementById('PartnerOrientationY').innerHTML = `${y}°`;
document.getElementById('PartnerOrientationZ').innerHTML = `${z}°`;
if ((performance.now() - previousTime) < 333)
return;
previousTime = performance.now();
launchEvent(`${x},${y},${z}|BUTTON|click||(DisplayOrientation)`);
});
</script>
"""
BODY = """
<fieldset style="display: flex; justify-content: space-around;">
<legend>Orientation</legend>
<span>
<span>Z: </span>
<output style="display: inline-block; font-family: monospace; width: 4ch; font-size: larger;" id="PartnerOrientationZ"></output>
</span>
<span>
<span>X: </span>
<output style="display: inline-block; font-family: monospace; width: 4ch; font-size: larger;" id="PartnerOrientationX"></output>
</span>
<span>
<span>Y: </span>
<output style="display: inline-block; font-family: monospace; width: 4ch; font-size: larger;" id="PartnerOrientationY"></output>
</span>
</fieldset>
"""
atlastk.launch(globals=globals())
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())
Application affichant des figures de Chernoff .
import atlastk, math, ucuq
ravel = ucuq.Ravel()
lcd = ravel.lcd()
oled = ravel.oled()
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 = ""
ravel = ucuq.Ravel()
lcd = ravel.lcd()
ring = ravel.ring()
buzzer = ravel.buzzer()
oled = ravel.oled()
lcd.backlightOn()
trueBuzzer = buzzer
ringCount = 8
ringOffset = 2
ringLimiter = 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 ringFlash_(button):
ring.fill([0, 0, 0])
if button in BUTTONS:
for i in range(ringCount // 4):
ring.setValue(
(
list(BUTTONS.keys()).index(button) * ringCount // 4
+ i
+ ringOffset
)
% ringCount,
[ringLimiter * item // 255 for item in BUTTONS[button][0]],
)
ring.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
ringFlash_(prevButton := button)
buzzerBeep_(n, 0.15, 0)
ringFlash_("")
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)).center(16))
lcdDisplay(1, l10n(11).center(16))
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):
ringFlash_(button)
buzzer.play(57 + BUTTONS[button][2])
ucuq.sleep(0.29)
buzzer.off()
ring.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))
ucuq.sleep(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}' pour",
"'{new}' anklicken",
),
(
"begin the game!",
"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
ravel = ucuq.Ravel(ringOffset=2)
lcd = ravel.lcd()
ring = ravel.ring()
buzzer = ravel.buzzer()
oled = ravel.oled()
class LED_:
def __init__(self, position):
self.position_ = position
def setValue(self, col):
ring.setValue(self.position_, col)
def groupLeds_(leds, positions):
for position in positions:
leds.add(LED_(position))
RING_MAX_ = 10
COLOR_1_ = (0, RING_MAX_, 0)
COLOR_2_ = (0, 0, RING_MAX_)
COLOR_TIE_ = (RING_MAX_, RING_MAX_, 0)
ROCK_ = 0
PAPER_ = 1
SCISSORS_ = 2
NOTHING_ = 3
noMaster_ = True
ledsStatus_ = ucuq.Multi()
ledsPlayer1_ = ucuq.Multi()
ledsPlayer2_ = ucuq.Multi()
ledsTie_ = ucuq.Multi()
def setAllLedGroups_():
groupLeds_(ledsStatus_, (0, 7))
groupLeds_(ledsPlayer1_, (4, 5))
groupLeds_(ledsTie_, (3, 4))
groupLeds_(ledsPlayer2_, (2, 3))
def play_(song):
ucuq.sleepStart()
ucuq.playVoices((song,), 120,
lambda freq: (
buzzer.off(),
buzzer.on(freq) if freq != 0 else None
) if freq != -1 else None,
lambda duration: (
ucuq.sleepWait(duration),
ucuq.sleepStart()
)
)
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
ring.fill((0, 0, 0))
ledsStatus_.setValue(COLOR_TIE_)
ring.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_
ring.fill((0, 0, 0))
toAll = f"0{NOTHING_}{NOTHING_}{id(dom)}"
if player1_ is None:
player1_ = move
oled.fill(0).draw(OLED_PICTURES_[3], 128).show()
lcd.moveTo(0,1).putString(dom.getL10n(1).format(2).replace("…", "...").center(16))
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:
ledsTie_.setValue(COLOR_TIE_)
song = TIE_SONG_
toAll = "3" + toAll
case 1:
score1_ += 1
ledsPlayer1_.setValue(COLOR_1_)
song = SONG_1_
toAll = "1" + toAll
case 2:
score2_ += 1
ledsPlayer2_.setValue(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_:
ledsStatus_.setValue(COLOR_2_)
elif score1_ == score2_:
ledsStatus_.setValue(COLOR_TIE_)
else:
ledsStatus_.setValue(COLOR_1_)
ring.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""",
"""00000000000001c0000000000000000000000000000003e0000000000000000000000000000007f00000001fe00000000000000000000ff0000003ffff0000000000000000001ff000000fffffe000000000000000003ff000003ffffff800000000000000007ff00000fffffffe0000000000000000ffe00001ffffffff0000000000000001ffc00003ffffffff8000000000000003ff800007ffffffffc000000000000003ff80000fffffffffe000000000000007ff00001ffffffffff00000000000000ffe00001ffffffffff00000000003801ffc00003ffffffffff800000000fffe3ff800003ffffc7ffff800000007fffc7ff000007fffe00ffffc0000003ffff8ffe000007fff8003fffc000000fffff1ffc000007fff0003fffc000003fffff3ff8000007ffe0001fffc000007ffffe3ff800000fffe0000fffe00001fffffc7ff180000fffc0000fffe00003fffff8ffe3c0000fffc0000fffe00007fffff1ffc7e0000fffc0000fffe0000fffffe3ff8ff0000fffc0000fffc0001ffefcc7ff1ff8000fffc0000fffc0003ff8f80ffe1ffc00000000001fffc0007ff0f01ffc0ffe00000000001fffc000ffc0f01ffc03ff00000000003fff8001ff80f03ff881ff80000000003fff8001ff01f07ff180ff80000000007fff0003fe01f0ffe3807fc000000001ffff0007fc00f1ffc7003fe000000003fffe0007f800e3ff8f001fe000000007fffc000ff800c7ff1f001ff00000000ffff80007f8008ffe3f001fe00000003ffff00007fc001ffc7e003fe00000007fffc00003fe001ffcfe007fc0000000ffff800001ff003ff8fc00ff80000000fffe000001ff807ff1fc01ff80000001fffc000000ffe0ffe3f807ff00000001fff80000007ff1ffc7f00ffe00000003fff00000003fe3ff8fe01ffc00000003ffe00000001fc7ff1f807ff800000003ffc00000000f8ffe1c01fff000000003ffc0000000078ffc000fffe000000003ff80000000031ffc41ffffc000000003ff80000000003ff8ffffff8000000003ff80000000007ff1fffffe0000000000000000000000ffe3fffffc0000000000000000000001ffc7fffff00000000000000000000003ff8fffffc00000000000000000000007ff1ffffe00000000000000000000000ffe0ffff00000000000007c000000000ffe000000000000000001ff000000001ffc000000000000000003ff800000003ff8000000000000000007ff800000007ff0000000000000000007ffc0000000ffe0000000000000000007ffc0000000ffc0000000000000000007ff80000000ff80000000000000000003ff80000000ff00000000000000000001ff00000000ff000000000000000000007c000000007e0000000000000000000000000000003800000000000000000000000000000"""
)
CLASSES_ = {
ROCK_: "rock",
PAPER_: "paper",
SCISSORS_: "scissors",
NOTHING_: "nothing"
}
ALL_CLASSES_ = list(claz for claz in CLASSES_.values()) + ["tie", "winner", "pending"]
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>
"""
setAllLedGroups_()
atlastk.launch(globals=globals())
Morpion
#
Le jeu du morpion .
import atlastk, ucuq, random, math
ravel = ucuq.Ravel()
lcd = ravel.lcd()
ring = ravel.ring()
buzzer = ravel.buzzer()
oled = ravel.oled()
RING_MAX = 5
CROSS_COLOR = (0, RING_MAX, 0)
CIRCLE_COLOR = (0, 0, RING_MAX)
TIE_COLOR = (RING_MAX, RING_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"
W_AI_MOVE = "AIMove"
W_CLEAR = "Clear"
currentPlayer = ""
winningCombos = []
board = None
def soundCallback(freq):
if freq != -1:
buzzer.off()
if freq != 0:
buzzer.on(freq)
if currentPlayer == "":
for led in range(8):
ring.setValue(
led,
tuple(
int(math.exp(random.uniform(0, math.log(RING_MAX))))
for _ in range(3)
),
)
ring.write()
def soundPlay(sound):
ucuq.sleepStart()
ucuq.playVoices((sound,), 120, lambda freq: soundCallback(freq), lambda duration: (ucuq.sleepWait(duration), ucuq.sleepStart()))
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)
ring.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)
ring.setValue(1 - 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)
ring.setValue(2 + circleCount, 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"]
winner, combo = getWinner(board)
if winner is not None:
addedClasses[W_AI_MOVE] = "hidden"
removedClasses[W_CLEAR] = "hidden"
else:
removedClasses[W_AI_MOVE] = "hidden"
addedClasses[W_CLEAR] = "hidden"
await dom.removeClasses(removedClasses)
await dom.addClasses(addedClasses)
if combo:
await dom.addClasses(dict(zip([f"cell{i}{j}" for i, j in combo], ["win"] * 3)))
if dom.isMaster:
oledWin(combo)
ring.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:
ring.fill(TIE_COLOR).write()
soundPlay(TIE_SOUND)
await setStatus(dom, dom.getL10n(5))
currentPlayer = ""
return
if dom.isMaster:
if crossCount + circleCount:
ring.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), "Clear": 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 atkAIMove(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 "),
("Clear", "Effacer", "Löschen"),
("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;
}
.hidden {
display: none;
}
</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="AIMove" style="margin:auto; width: 5em; font-size: larger;" id="AIMove">🤖</button>
<button xdh:onevent="New" style="margin: auto;" class="hidden" id="Clear"></button>
</div>
</fieldset>
"""
atlastk.launch(globals=globals())
Dépannage