Ayuda, no una partida
A diferencia del go o el gomoku, el sudoku no tiene rival. Si la IA lo resuelve todo sola, a la persona no le queda nada que hacer. Por eso, en el sudoku web (stoku.mariago.kr), la persona resuelve el sudoku por sí misma y solo le pide una jugada a la IA cuando se atasca.

Cómo pedir una jugada
- Se pide con el botón "Jugada de la IA" de la pantalla o por el chat, con frases como "haz una jugada" o "pista".
- El servidor de chat usa Qwen2.5-1.5B-Instruct y expresiones regulares para distinguir si es una petición de jugada.
- Si es una petición de jugada, envía el tablero actual al servidor de sudoku, recibe una casilla y un número, y los coloca en el tablero de la persona.
Por ahora, el servidor web no carga el VLM y elige las jugadas seguras solo con código. Primero busca una caja, fila o columna a la que le quede una sola casilla vacía y, si no la hay, busca para cada número una casilla que sea el único lugar posible. En la práctica, el código hace todo el trabajo que en la entrada 4 se repartían el VLM y el código. La lista de modelos de la pantalla muestra lora_move, pero la jugada real la decide esta estrategia. Todavía no muestra por qué se eligió esa casilla.
Importar desde una foto
La app de Qt puede importar un sudoku en papel o en pantalla a partir de una foto. Recorta la cuadrícula, reúne solo las casillas que tienen números y se las da a leer a la LoRA 8B de lectura del tablero de la entrada 3.

Registros de resolución en la web
En algo más de tres semanas se abrieron 40 partidas en la web. 26 se resolvieron hasta el final, con un tiempo medio de unos 12 minutos. 9 se abandonaron y 5 siguen en curso. De las partidas terminadas, 20 eran de nivel medio, 3 difíciles, 2 infierno y 1 extremo.
Las acciones registradas fueron 1,430 números colocados, 135 notas, 62 borrados y 23 deshacer.

El código real de esta etapa
Este es el orden en el que el servidor web elige una jugada segura.
vlm_digit_strategy.py
def pick_code_certain_move(
board: list[list[int]],
) -> tuple[int, int, int, str] | None:
"""우선순위: last-empty → digit 유일 → human solver → 탐색 강제수."""
lasts = last_empty_unit_moves(board)
if lasts:
return lasts[0]
for digit in range(1, 10):
illegal = set(illegal_cells_for_digit(board, digit))
moves = absolute_placements(board, illegal, digit)
if moves:
r, c, v, reason = moves[0]
return r, c, v, f"code_digit{digit}_{reason}"
from sudoku_human_solver import find_human_move
hm = find_human_move(board)
if hm is not None:
return hm.row, hm.col, hm.value, f"human_{hm.kind.value}"
# 극한: 국소 후보는 여러 개인데, 하나만 해가 남는 칸 (예: 2행 끝 7)
return pick_search_forced_move(board)Esta es la parte en la que el servidor de chat recibe un mensaje y, si es una petición de jugada, lo pasa al servidor de sudoku.
chat_bridge_server.py
MOVE_RE = re.compile(
r"(한\s*수|다음\s*수|힌트|추천|어디|둬|놓|채워|풀어|solve|hint|move|next)",
re.I,
)
# ...
def handle_chat(body: dict[str, Any]) -> dict[str, Any]:
message = str(body.get("message") or "").strip()
if not message:
return {"error": "empty message"}
model_id = str(body.get("modelId") or "lora_move")
meta = TRAINED_MODELS.get(model_id, TRAINED_MODELS["lora_move"])
board = body.get("board")
puzzle = body.get("puzzle")
difficulty = body.get("difficulty") or "medium"
if not isinstance(board, list) or len(board) != 9:
return {"error": "board required"}
blanks = _empty_count(board)
intent = classify_intent(message)
llm_text = chat_llm_reply(
message,
{
"model_label": meta["label"],
"blanks": blanks,
"difficulty": difficulty,
},
)
if llm_text:
if "CMD:MOVE" in llm_text.upper() or intent == "move":
intent = "move"
reply_base = re.sub(r"CMD:\w+", "", llm_text, flags=re.I).strip()
else:
reply_base = ""
if intent == "move" or (intent == "unknown" and MOVE_RE.search(message)):
result = call_trained_vlm(board, puzzle if isinstance(puzzle, list) else None, model_id)
if not result.get("ok"):
return {
"reply": (
f"{meta['label']} 호출 실패: {result.get('error')}\n"
f"(채팅 AI → 학습 모델 명령 전달)"
),
"action": None,
"vlm": result,
}
r, c, v = result["row"], result["col"], result["value"]
spoken = reply_base or (
f"{meta['label']}에 「한 수」 명령을 전달했습니다."
)
return {
"reply": f"{spoken}\n→ ({r},{c}) = {v}",
"action": {"type": "place", "row": r, "col": c, "value": v},
"vlm": result,
}La siguiente entrada es "6. El código fuente completo y los modelos utilizados".
COMMENTS · CHAPTER 05
Una IA que ayuda a las personas 0 comentarios
Todavía no hay comentarios. Deja una pregunta o algo que quieras añadir.
Para publicar un comentario, empieza con Google y registra un apodo y un correo.
Empezar con Google