File size: 9,658 Bytes
9908e01
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
# app.py
# 3x3 Rubik's-style Color Password System Simulator
# Hugging Face Spaces ready (Gradio)
#
# How it works (quick start):
# 1) Choose a side (each has a fixed center color).
# 2) Click "Randomize" to shuffle the 8 non-center tiles.
# 3) Arrange the tiles by clicking them. Each click cycles color in this exact order:
#    Red → Yellow → Green → Blue → White → Orange → (repeat)
#    (Center tile is locked to the side's middle color.)
# 4) Click "Set Code" to save the current pattern as the password for that side.
# 5) Click "Randomize" again (or leave as-is), then try to re-create the saved pattern.
# 6) Click "Unlock" to check your attempt. Success will be announced.
#
# Notes:
# - Each of the 6 sides has its own center color and its own saved code.
# - "Show Code" lets you peek at the saved code preview for the selected side.
# - "Reset Side" resets only the selected side (grid and saved code).
# - "Reset All" resets everything.
#
# Made for users with zero coding experience—just upload these files
# to a Hugging Face Space (Python/Gradio), and it will run.

import gradio as gr
from PIL import Image, ImageDraw
import random

# ----- Color model -----
COLOR_ORDER = ["Red", "Yellow", "Green", "Blue", "White", "Orange"]
COLOR_HEX = {
    "Red":    "#e53935",
    "Yellow": "#fdd835",
    "Green":  "#43a047",
    "Blue":   "#1e88e5",
    "White":  "#fafafa",
    "Orange": "#fb8c00",
}

# Six sides with fixed middle colors (index 4 in a 0..8 grid)
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  # px
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)
        # subtle "bevel" lines
        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)]

# ----- App State -----
# We keep per-side state: current grid + saved code (or None)
def init_state():
    state = {}
    for side, center in SIDES.items():
        state[side] = {
            "center": center,
            "grid": new_grid(center),
            "code": None,  # saved pattern (list of 9 color names) or None
            "unlocked": False,
        }
    return state

# ----- Helpers for events -----
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"]
    # randomize non-center tiles only
    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):
    # Save current grid as the code
    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
    # Tiny rendering
    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()

# Handle tile clicks on the image: gr.Image.select gives (x, y)
def on_click(state, side_name, evt: gr.SelectData):
    # Determine tile from coordinates
    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
    # Center is locked
    if idx == 4:
        return state, side_to_image(state, side_name)
    # cycle color
    grid = state[side_name]["grid"]
    grid[idx] = cycle_color(grid[idx])
    state[side_name]["unlocked"] = False
    return state, side_to_image(state, side_name)

# ----- Build UI -----
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):
        # update main image, status, and code preview
        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],  # hidden toast text placeholder
    )

    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],
    )

    # Initialize the first view
    demo.load(
        refresh_side,
        [app_state, side_dropdown, show_code],
        [grid_image, status, code_image],
    )

if __name__ == "__main__":
    demo.launch()