I implemented this Rubik's Cube game in Python, using Canvas and Context2d for user interaction. This was mainly an exercise in "DRY" principles, which were quite difficult to follow: the code really wanted to have long repeating sections spelling out the geometry of the cube or the types of different moves, which were suppressed at some loss to readability.
import numpy as np import json from scipy.linalg import logm, expm import itertools def rigid_perms(D, prefix=[]): return ( sum( ( rigid_perms(D, prefix + [i]) for i in range(len(D)) if ( D[prefix + [i]] @ D[i] == D[: len(prefix) + 1] @ D[len(prefix)] ).all() ), [], ) if len(prefix) < len(D) else [np.eye(len(D))[prefix]] ) def np2js(arr): real_arr = np.block([[arr.real, arr.imag], [-arr.imag, arr.real]]) return json.dumps(real_arr.tolist()) sticker_coords = np.array( [ s for s in itertools.product(range(-2, 3), repeat=3) if (np.abs(s) == 2).sum() == 1 ] ) sticker_colors = 127 + 127 * np.round(sticker_coords / 2) global_perms = rigid_perms(sticker_coords) slice_perms = rigid_perms(sticker_coords[:21]) move = np.eye(len(sticker_coords)) move[:21, :21] = slice_perms[3] all_moves = [] for perm in global_perms: new_move = perm @ move @ perm.T if any( (new_move == m).all() for m in all_moves ): continue all_moves.append(new_move) all_moves.append(new_move.T) view = np.linalg.qr(np.random.randn(3, 3))[0] with open("output.html", "w") as static_site: static_site.write( f""" <!DOCTYPE html> <html> <body> <canvas id="canvas" height=500 width=500></canvas> <script> const ctx = document.getElementById("canvas").getContext('2d'); let mul = (A, B) => A.map((row, i) => B[0].map((_, j) => row.reduce((acc, _, n) => acc + A[i][n] * B[n][j], 0))) var state = {np2js(np.eye(len(sticker_coords)))} const coords = {np2js(sticker_coords @ view * 70 + 255)} var moves = [state] document.addEventListener("keypress", (event) => {{ """ ) for i, generator in enumerate(all_moves[::2]): static_site.write( f""" if (event.key == {i}) {{ moves = (new Array(10).fill( {np2js(expm(.1 * logm(generator)))})).concat( moves); }} """ ) static_site.write( """ }); step = () => { if (!moves.length) { requestAnimationFrame(step) return; } state = mul(state, moves.pop()); const locations = mul(state, coords); ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); drawlist = [ """ ) for i, color in enumerate(sticker_colors): static_site.write( f""" [...locations[{i}], 'rgb({color[0]} {color[1]} {color[2]})'], """ ) static_site.write( """ ].sort((a, b) => a[0]-b[0]) for (var elem of drawlist){ ctx.beginPath() ctx.arc(elem[1], elem[2], 40, 0, 2 * Math.PI) ctx.fillStyle=elem[6] ctx.fill() ctx.stroke() } requestAnimationFrame(step); } requestAnimationFrame(step); </script> </body> </html> """ )