|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import gradio as gr |
|
|
from PIL import Image, ImageDraw |
|
|
import random |
|
|
|
|
|
|
|
|
COLOR_ORDER = ["Red", "Yellow", "Green", "Blue", "White", "Orange"] |
|
|
COLOR_HEX = { |
|
|
"Red": "#e53935", |
|
|
"Yellow": "#fdd835", |
|
|
"Green": "#43a047", |
|
|
"Blue": "#1e88e5", |
|
|
"White": "#fafafa", |
|
|
"Orange": "#fb8c00", |
|
|
} |
|
|
|
|
|
|
|
|
SIDES = { |
|
|
"Side 1 (Red middle)": "Red", |
|
|
"Side 2 (Green middle)": "Green", |
|
|
"Side 3 (Orange middle)": "Orange", |
|
|
"Side 4 (Blue middle)": "Blue", |
|
|
"Side 5 (White middle)": "White", |
|
|
"Side 6 (Yellow middle)": "Yellow", |
|
|
} |
|
|
SIDE_NAMES = list(SIDES.keys()) |
|
|
|
|
|
TILE_SIZE = 120 |
|
|
GRID_SIZE = 3 |
|
|
IMG_SIZE = TILE_SIZE * GRID_SIZE |
|
|
|
|
|
def new_grid(center_color: str): |
|
|
"""Create a new 3x3 grid with the center locked to center_color and others random.""" |
|
|
grid = [random.choice(COLOR_ORDER) for _ in range(9)] |
|
|
grid[4] = center_color |
|
|
return grid |
|
|
|
|
|
def render_grid(grid): |
|
|
"""Return a PIL image of the 3x3 grid.""" |
|
|
img = Image.new("RGB", (IMG_SIZE, IMG_SIZE), color=(30, 30, 30)) |
|
|
draw = ImageDraw.Draw(img) |
|
|
for i, color_name in enumerate(grid): |
|
|
r = i // GRID_SIZE |
|
|
c = i % GRID_SIZE |
|
|
x0 = c * TILE_SIZE + 2 |
|
|
y0 = r * TILE_SIZE + 2 |
|
|
x1 = (c+1) * TILE_SIZE - 2 |
|
|
y1 = (r+1) * TILE_SIZE - 2 |
|
|
fill = COLOR_HEX[color_name] |
|
|
draw.rectangle([x0, y0, x1, y1], fill=fill, outline="#111111", width=4) |
|
|
|
|
|
draw.line([x0, y0, x1, y0], fill="#000000", width=3) |
|
|
draw.line([x0, y0, x0, y1], fill="#000000", width=3) |
|
|
return img |
|
|
|
|
|
def grid_image_bytes(grid): |
|
|
return render_grid(grid) |
|
|
|
|
|
def cycle_color(color_name): |
|
|
idx = COLOR_ORDER.index(color_name) |
|
|
return COLOR_ORDER[(idx + 1) % len(COLOR_ORDER)] |
|
|
|
|
|
|
|
|
|
|
|
def init_state(): |
|
|
state = {} |
|
|
for side, center in SIDES.items(): |
|
|
state[side] = { |
|
|
"center": center, |
|
|
"grid": new_grid(center), |
|
|
"code": None, |
|
|
"unlocked": False, |
|
|
} |
|
|
return state |
|
|
|
|
|
|
|
|
def side_to_image(state, side_name): |
|
|
return grid_image_bytes(state[side_name]["grid"]) |
|
|
|
|
|
def randomize_side(state, side_name): |
|
|
center = state[side_name]["center"] |
|
|
grid = state[side_name]["grid"] |
|
|
|
|
|
for i in range(9): |
|
|
if i == 4: |
|
|
grid[i] = center |
|
|
else: |
|
|
grid[i] = random.choice(COLOR_ORDER) |
|
|
state[side_name]["unlocked"] = False |
|
|
return state, side_to_image(state, side_name), status_text(state, side_name), code_preview(state, side_name) |
|
|
|
|
|
def set_code(state, side_name): |
|
|
|
|
|
state[side_name]["code"] = list(state[side_name]["grid"]) |
|
|
state[side_name]["unlocked"] = False |
|
|
return state, "Code saved for this side.", code_preview(state, side_name) |
|
|
|
|
|
def unlock(state, side_name): |
|
|
target = state[side_name]["code"] |
|
|
if target is None: |
|
|
return state, "No code saved yet for this side.", code_preview(state, side_name) |
|
|
if state[side_name]["grid"] == target: |
|
|
state[side_name]["unlocked"] = True |
|
|
return state, "✅ Unlocked! Pattern matches the saved code.", code_preview(state, side_name) |
|
|
else: |
|
|
state[side_name]["unlocked"] = False |
|
|
return state, "❌ Not yet. Keep trying!", code_preview(state, side_name) |
|
|
|
|
|
def status_text(state, side_name): |
|
|
u = state[side_name]["unlocked"] |
|
|
code = state[side_name]["code"] |
|
|
if u: |
|
|
return "Status: ✅ Unlocked" |
|
|
if code is None: |
|
|
return "Status: No code saved" |
|
|
return "Status: Locked (code saved, try to match & click Unlock)" |
|
|
|
|
|
def code_preview(state, side_name): |
|
|
"""Return a small preview image for the saved code, or None if not set.""" |
|
|
code = state[side_name]["code"] |
|
|
if code is None: |
|
|
return None |
|
|
|
|
|
prev_tile = 40 |
|
|
size = prev_tile * GRID_SIZE |
|
|
img = Image.new("RGB", (size, size), color=(30,30,30)) |
|
|
draw = ImageDraw.Draw(img) |
|
|
for i, color_name in enumerate(code): |
|
|
r = i // GRID_SIZE |
|
|
c = i % GRID_SIZE |
|
|
x0 = c * prev_tile + 2 |
|
|
y0 = r * prev_tile + 2 |
|
|
x1 = (c+1) * prev_tile - 2 |
|
|
y1 = (r+1) * prev_tile - 2 |
|
|
fill = COLOR_HEX[color_name] |
|
|
draw.rectangle([x0, y0, x1, y1], fill=fill, outline="#111111", width=2) |
|
|
return img |
|
|
|
|
|
def reset_side(state, side_name): |
|
|
center = state[side_name]["center"] |
|
|
state[side_name] = { |
|
|
"center": center, |
|
|
"grid": new_grid(center), |
|
|
"code": None, |
|
|
"unlocked": False, |
|
|
} |
|
|
return state, side_to_image(state, side_name), status_text(state, side_name), None |
|
|
|
|
|
def reset_all(state): |
|
|
return init_state() |
|
|
|
|
|
|
|
|
def on_click(state, side_name, evt: gr.SelectData): |
|
|
|
|
|
x, y = evt.index[0], evt.index[1] |
|
|
col = min(int(x // TILE_SIZE), GRID_SIZE - 1) |
|
|
row = min(int(y // TILE_SIZE), GRID_SIZE - 1) |
|
|
idx = row * GRID_SIZE + col |
|
|
|
|
|
if idx == 4: |
|
|
return state, side_to_image(state, side_name) |
|
|
|
|
|
grid = state[side_name]["grid"] |
|
|
grid[idx] = cycle_color(grid[idx]) |
|
|
state[side_name]["unlocked"] = False |
|
|
return state, side_to_image(state, side_name) |
|
|
|
|
|
|
|
|
with gr.Blocks(title="3x3 Rubik's-Style Color Password") as demo: |
|
|
gr.Markdown( |
|
|
""" |
|
|
# 3×3 Rubik's‑Style Color Password System |
|
|
**Click tiles to cycle colors:** Red → Yellow → Green → Blue → White → Orange → (repeat). |
|
|
The center tile is locked to the side's middle color. |
|
|
""" |
|
|
) |
|
|
|
|
|
app_state = gr.State(init_state()) |
|
|
|
|
|
with gr.Row(): |
|
|
side_dropdown = gr.Dropdown(choices=SIDE_NAMES, value=SIDE_NAMES[0], label="Choose Side") |
|
|
|
|
|
with gr.Row(): |
|
|
grid_image = gr.Image(type="pil", label="Current Grid", height=IMG_SIZE, width=IMG_SIZE, interactive=True) |
|
|
with gr.Column(): |
|
|
status = gr.Markdown("Status: No code saved") |
|
|
with gr.Row(): |
|
|
btn_random = gr.Button("🔀 Randomize") |
|
|
btn_set = gr.Button("💾 Set Code") |
|
|
with gr.Row(): |
|
|
btn_unlock = gr.Button("🔓 Unlock") |
|
|
btn_reset_side = gr.Button("♻️ Reset Side") |
|
|
with gr.Row(): |
|
|
btn_reset_all = gr.Button("🧹 Reset All (All Sides)") |
|
|
show_code = gr.Checkbox(label="Show Code Preview", value=True) |
|
|
code_image = gr.Image(type="pil", label="Saved Code (Preview)", height=120, width=120, interactive=False) |
|
|
|
|
|
def refresh_side(state, side_name, show_code_flag): |
|
|
|
|
|
return ( |
|
|
side_to_image(state, side_name), |
|
|
status_text(state, side_name), |
|
|
code_preview(state, side_name) if show_code_flag else None, |
|
|
) |
|
|
|
|
|
side_dropdown.change( |
|
|
refresh_side, |
|
|
[app_state, side_dropdown, show_code], |
|
|
[grid_image, status, code_image], |
|
|
) |
|
|
|
|
|
show_code.change( |
|
|
lambda state, side, flag: code_preview(state, side) if flag else None, |
|
|
[app_state, side_dropdown, show_code], |
|
|
[code_image], |
|
|
) |
|
|
|
|
|
btn_random.click( |
|
|
randomize_side, |
|
|
[app_state, side_dropdown], |
|
|
[app_state, grid_image, status, code_image], |
|
|
) |
|
|
|
|
|
btn_set.click( |
|
|
set_code, |
|
|
[app_state, side_dropdown], |
|
|
[app_state, gr.Textbox(visible=False), code_image], |
|
|
) |
|
|
|
|
|
btn_unlock.click( |
|
|
unlock, |
|
|
[app_state, side_dropdown], |
|
|
[app_state, status, code_image], |
|
|
) |
|
|
|
|
|
btn_reset_side.click( |
|
|
reset_side, |
|
|
[app_state, side_dropdown], |
|
|
[app_state, grid_image, status, code_image], |
|
|
) |
|
|
|
|
|
btn_reset_all.click( |
|
|
lambda state: init_state(), |
|
|
[app_state], |
|
|
[app_state], |
|
|
).then( |
|
|
refresh_side, |
|
|
[app_state, side_dropdown, show_code], |
|
|
[grid_image, status, code_image], |
|
|
) |
|
|
|
|
|
grid_image.select( |
|
|
on_click, |
|
|
[app_state, side_dropdown], |
|
|
[app_state, grid_image], |
|
|
) |
|
|
|
|
|
|
|
|
demo.load( |
|
|
refresh_side, |
|
|
[app_state, side_dropdown, show_code], |
|
|
[grid_image, status, code_image], |
|
|
) |
|
|
|
|
|
if __name__ == "__main__": |
|
|
demo.launch() |
|
|
|