직접 클릭하다가 현타와서 ai로 스크립트 만들어봤습니다.
크롬 사용하셔야되고
크롬 개발자도구(F12)의 console 탭에서 사용하시면 됩니다.
많은 사람들이 볼 수 있게 추천좀 해주시면 감사하겠습니다.
제발 이런 이벤트 좀 안만들었으면 좋겠네요
요기 아래 스크립트 복사해서 이벤트 페이지에서 사용하시면 됩니다.
===================요기 아래가 스크립트=====================================
(() =>
{
// =========================================================
// Options
// =========================================================
const OPT =
{
stageSelector: "section.stage",
floorSelector: '[class^="floor"]',
doorSelector: "button.door",
// Timing
initialWaitMs: 1000, // "게임 시작" 후 DOM 안정화 대기
clickDelayMs: 40,
// 클릭 후 "층 변화"가 나타날 때까지 기다리는 최대 시간
waitFloorChangeMaxMs: 1600,
waitFloorChangeTickMs: 80,
// 루프 대기
loopTickMs: 80,
// STAGE CLEAR 자동 확인 클릭(대개 reload됨)
autoConfirmStageClear: false,
modalBodySelector: ".lui-modal__body",
modalTitleSelector: ".lui-modal__body .lui-modal__title",
modalConfirmSelector: ".lui-modal__body button.lui-modal__confirm",
};
// =========================================================
// Utils
// =========================================================
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
const qs = (s) => document.querySelector(s);
const qsa = (s, r=document) => Array.from(r.querySelectorAll(s));
const log = (...a) => console.log("[auto-door]", ...a);
const stage = qs(OPT.stageSelector);
if (!stage)
{
log("stage not found");
return;
}
const floors = () => qsa(OPT.floorSelector, stage);
const getDoorElByIdx = (idx) => stage.querySelector(`${OPT.doorSelector}[data-idx="${idx}"]`);
const getCurrentFloorIndex = () =>
{
const fs = floors();
for (let i = 0; i < fs.length; i++)
{
if (fs[i].querySelector(".door--current")) return i;
}
return null;
};
// "1층으로 돌아옴" 판정: floor1 안의 문들이 전부 door--current가 됨 (네가 준 조건)
const isReturnedToFloor1 = () =>
{
const fs = floors();
const f0 = fs[0];
if (!f0) return false;
const doors = qsa(OPT.doorSelector, f0);
if (!doors.length) return false;
return doors.every(d => d.classList.contains("door--current"));
};
const enabledDoorIdxListOnFloor = (floorIdx) =>
{
const fs = floors();
const f = fs[floorIdx];
if (!f) return [];
return qsa(`${OPT.doorSelector}:not(:disabled)`, f)
.map(d => Number(d.dataset.idx))
.filter(Number.isInteger);
};
// STAGE CLEAR 모달 감지
const isStageClearModal = () =>
{
const body = qs(OPT.modalBodySelector);
if (!body) return false;
const title = (qs(OPT.modalTitleSelector)?.textContent || "").trim();
return title === "STAGE CLEAR";
};
// 클릭 후 floorIndex 변화 감지(최대 waitFloorChangeMaxMs까지 polling)
const waitFloorChange = async (prevFloor) =>
{
const t0 = performance.now();
while (performance.now() - t0 < OPT.waitFloorChangeMaxMs)
{
const cur = getCurrentFloorIndex();
if (cur !== null && cur !== prevFloor)
return cur;
await sleep(OPT.waitFloorChangeTickMs);
}
// 변화가 없으면 prevFloor 그대로 반환
return prevFloor;
};
// =========================================================
// Build NxM (floor count varies)
// =========================================================
const buildBoard = () =>
{
const fs = floors();
const board = fs.map((floorEl, f) =>
qsa(OPT.doorSelector, floorEl).map(d => Number(d.dataset.idx)).filter(Number.isInteger)
);
return { N: board.length, board };
};
// =========================================================
// Main
// =========================================================
(async () =>
{
log("start");
await sleep(OPT.initialWaitMs);
const { N, board } = buildBoard();
log(`parsed floors=${N}, matrix=`, board);
// floor -> correct idx (unknown = null)
const correctByFloor = Array(N).fill(null);
// floor -> tried idx set
const triedByFloor = Array.from({ length: N }, () => new Set());
// 1층부터 연속으로 알고 있는 정답만 복구 (중간에 모르면 stop)
const restorePath = async () =>
{
for (let f = 0; f < N; f++)
{
const idx = correctByFloor[f];
if (!Number.isInteger(idx)) return;
// 현재 층이 f가 아닐 수 있으니 확인
const cur = getCurrentFloorIndex();
if (cur !== f) return;
const el = getDoorElByIdx(idx);
if (!el || el.matches(":disabled")) return;
log(`restore: floor ${f + 1} idx ${idx}`);
await sleep(OPT.clickDelayMs);
el.click();
// 정답 복구 클릭은 보통 바로 위층으로 올라가야 함
await waitFloorChange(f);
await sleep(OPT.clickDelayMs);
}
};
// 아직 모르는 층에서 "안 눌러본 문" 선택
const pickUntriedIdx = (floorIdx) =>
{
const candidates = enabledDoorIdxListOnFloor(floorIdx);
const tried = triedByFloor[floorIdx];
for (const idx of candidates)
{
if (!tried.has(idx)) return idx;
}
return null;
};
while (true)
{
// Stage clear
if (isStageClearModal())
{
log("STAGE CLEAR modal detected");
if (OPT.autoConfirmStageClear)
qs(OPT.modalConfirmSelector)?.click();
break;
}
const curFloor = getCurrentFloorIndex();
if (curFloor === null)
{
await sleep(OPT.loopTickMs);
continue;
}
// 1층으로 돌아왔고, 1층 정답을 알고 있으면 -> 연속 복구
if (curFloor === 0 && isReturnedToFloor1() && Number.isInteger(correctByFloor[0]))
{
log("returned to floor1 -> restore known path");
await restorePath();
await sleep(OPT.loopTickMs);
continue;
}
// 현재 층 정답을 알고 있으면 그걸 클릭(복구/진행)
if (Number.isInteger(correctByFloor[curFloor]))
{
const idx = correctByFloor[curFloor];
const el = getDoorElByIdx(idx);
if (el && !el.matches(":disabled"))
{
log(`known: floor ${curFloor + 1} idx ${idx}`);
await sleep(OPT.clickDelayMs);
el.click();
const newFloor = await waitFloorChange(curFloor);
// newFloor가 안 변해도 계속 루프
await sleep(OPT.loopTickMs);
continue;
}
}
// 탐색: 아직 모르는 층이면 안 눌러본 문을 클릭
const cand = pickUntriedIdx(curFloor);
if (!Number.isInteger(cand))
{
// 현재 층 문을 다 눌렀다는 뜻인데, 보통 여기까지 오면 로직 어긋난 것.
// 안전하게 tried 초기화하고 다시 시도.
triedByFloor[curFloor].clear();
await sleep(OPT.loopTickMs);
continue;
}
triedByFloor[curFloor].add(cand);
const candEl = getDoorElByIdx(cand);
if (!candEl || candEl.matches(":disabled"))
{
await sleep(OPT.loopTickMs);
continue;
}
log(`explore: floor ${curFloor + 1} idx ${cand}`);
await sleep(OPT.clickDelayMs);
candEl.click();
const newFloor = await waitFloorChange(curFloor);
// ✅ 정답 판정은 "층이 올라갔는지"로 확정
if (newFloor === curFloor + 1)
{
correctByFloor[curFloor] = cand;
log(`CONFIRMED: floor ${curFloor + 1} correct idx=${cand}`);
}
else
{
log(`FAIL: floor ${curFloor + 1} idx=${cand} -> newFloor=${newFloor + 1}`);
}
await sleep(OPT.loopTickMs);
}
log("end");
})();
})();