Райтап на задачу «Скоростные Пазлы #2»

musa2ap

Автор:

musa2ap

7 ноября 2026

10

07.11.2026
Скоростные Пазлы #2
Категория:

Misc

Задача «Скоростные Пазлы #2»

LastTeamLead

Автор:

LastTeamLead

Первая кровь:
vanderson
Сложность:
medium
Дата публикации:
21.09.2025
Решений:
91
Рейтинг:
4.82/5
Ссылка:
Решать

В первой части было всего 16 кусочков, которые я расставил руками), в этой уже 64, руками не получается, значит, попробуем написать скрипт. Смотрим исходный код страницы.

В script.js игра общается с сервером через два запроса. GET /api/new-game (fetch('/api/new-game') без параметра method — это GET) возвращает originalImage, массив puzzle из 64 кусочков, gridSize и pieceSize.

Скоростные Пазлы #2: Получение новой игры и исходного изображения в script.js

POST /api/check-solution принимает JSON {"solution": [...]}, где solution — ID кусочков в порядке клеток поля. При правильном порядке в ответе приходит flag.

Скоростные Пазлы #2: Отправка порядка фрагментов на проверку через API

Перетаскивать кусочки мышью не обязательно. Сервер сам отдаёт оригинальное изображение и все кусочки, причём каждый кусочек — точный фрагмент оригинала. Поэтому правильный порядок можно вычислить: режем оригинал на 8 × 8 плиток и для каждого кусочка ищем плитку с минимальной разницей пикселей, по которой наш скрипт сопоставляет куски и присваивает значение их порядка.

Важная деталь: состояние игры хранится в сессии express-session в cookie connect.sid. Если открыть new-game в одном запросе, а check-solution отправить без этой cookie, сервер ответит No active game session. Значит, оба запроса надо делать из одной сессии.

Порядок решения:

  1. Запускаем новую игру и сохраняем cookie сессии.
  2. Декодируем originalImage и все puzzle[i].image из base64.
  3. Сравниваем каждый кусочек с каждой плиткой оригинала по MSE.
  4. По минимальным расстояниям получаем соответствие «кусочек — плитка».
  5. Собираем ID кусочков в порядке слева направо, сверху вниз, отправляем в check-solution и забираем флаг.

Скрипт на Python:

import base64
import io

import numpy as np
import requests
from PIL import Image

BASE = "https://fast-puzzles-2.t1.bug-makers.ru"


def decode(uri):
    payload = uri.split(",", 1)[1]
    return Image.open(io.BytesIO(base64.b64decode(payload))).convert("RGB")


session = requests.Session()
game = session.get(f"{BASE}/api/new-game", timeout=30).json()

n = game["gridSize"]
original = np.asarray(decode(game["originalImage"]), dtype=np.float32)
h, w, _ = original.shape
th, tw = h // n, w // n

tiles = [
    original[r * th:(r + 1) * th, c * tw:(c + 1) * tw]
    for r in range(n)
    for c in range(n)
]

pieces = [
    np.asarray(decode(p["image"]).resize((tw, th)), dtype=np.float32)
    for p in game["puzzle"]
]

dist = np.array([
    [np.mean((piece - tile) ** 2) for tile in tiles]
    for piece in pieces
])

try:
    from scipy.optimize import linear_sum_assignment
    rows, cols = linear_sum_assignment(dist)
    match = dict(zip(rows, cols))
except ImportError:
    match = {}
    used = set()
    for _, i, j in sorted(
        (dist[i, j], i, j)
        for i in range(len(pieces))
        for j in range(len(tiles))
    ):
        if i not in match and j not in used:
            match[i] = j
            used.add(j)

solution = [None] * (n * n)
for i, piece in enumerate(game["puzzle"]):
    solution[match[i]] = piece["id"]

response = session.post(
    f"{BASE}/api/check-solution",
    json={"solution": solution},
    timeout=30,
).json()

print(response)
print("Флаг:", response["flag"])

Что делает скрипт: сначала запускает новую игру и сохраняет значения cookie. После декодирует наше изображение из base64, сравнивает куски изображения по MSE, собирает куски по плиткам в порядке слева направо, сверху вниз и отправляет в check-solution.

Ставим зависимости, если у вас их нет (python -m pip install requests pillow numpy scipy), и запускаем скрипт, получая флаг.

Если вы нашли опечатку или ошибку, пожалуйста, выделите фрагмент текста и нажмите Ctrl+Enter

Также по теме:

Райтап на задачу «Двери»

/Разборы задач

Райтап на задачу «Двери»

Авторский разбор задачи «Двери»: принцип Port Knocking, поиск слушателя в подсети и последовательное открытие трёх TCP-портов.
Прочитать

10

Райтап на задачу «Скоростные Пазлы #3»

/Разборы задач

Райтап на задачу «Скоростные Пазлы #3»

Собираем 256 фрагментов пазла с помощью OpenCV: находим кусочки в оригинале и восстанавливаем порядок по координатам.
Прочитать

7