인벤에 글을 처음 써보는 거라 가독성이 조금 떨어질 수도 있습니다.
요즘 카드 메모리 게임이 너무 귀찮아서, 직접 자동화 스크립트를 만들어봤어요.
예전에 플레이크 자동 스크립트를 써보신 분이라면 쉽게 따라하실 수 있을 겁니다.
제가 테스트할 때는 별다른 버그나 문제는 없었습니다.
혹시 사용 중 오류나 비정상 동작이 발견되면
댓글로 알려주시면 감사하겠습니다.
(function () {
if (window.__lostArkCardHelperInstalled) return;
window.__lostArkCardHelperInstalled = true;
(function installCardMemoCore() {
if (window.__cardMemoInstalled) return;
window.__cardMemoInstalled = true;
window.__cardMemo = window.__cardMemo || {};
window.__cardDone = window.__cardDone || [];
const API_PATH = "/Promotion/Card/GetCard251105";
(function hookXHR() {
const OriginalXHR = window.XMLHttpRequest;
function WrappedXHR() {
const xhr = new OriginalXHR();
let method = null;
let url = null;
let body = null;
const origOpen = xhr.open;
xhr.open = function (m, u) {
method = m;
url = u;
return origOpen.apply(xhr, arguments);
};
const origSend = xhr.send;
xhr.send = function (data) {
body = data;
xhr.addEventListener("load", function () {
try {
if (!url || !method) return;
const fullUrl = new URL(url, location.href);
if (!fullUrl.pathname.endsWith(API_PATH)) return;
if (method.toUpperCase() !== "POST") return;
let indexStr = null;
try {
if (typeof body === "string") {
const m = body.match(/(?:^|&)index=([^&]+)/);
if (m) indexStr = decodeURIComponent(m[1]);
} else if (body instanceof FormData || body instanceof URLSearchParams) {
indexStr = body.get("index");
}
} catch (e) {}
try {
const text = xhr.responseText;
if (!text) return;
const json = JSON.parse(text);
if (!json) return;
if (json.img != null) {
const idx = indexStr != null ? Number(indexStr) : null;
if (idx != null && !Number.isNaN(idx) && idx >= 0 && idx < 18) {
const imgUrl = new URL(json.img, "https://cdn-lostark.game.onstove.com").href;
window.__cardMemo[idx] = imgUrl;
}
}
if (json.isMatch && Array.isArray(json.index)) {
window.__cardDone = [...(window.__cardDone || []), ...json.index];
}
if (json.complete) {
window.__cardDone = [];
window.__cardMemo = {};
}
} catch (e) {}
} catch (e) {}
});
return origSend.apply(xhr, arguments);
};
return xhr;
}
WrappedXHR.prototype = OriginalXHR.prototype;
window.XMLHttpRequest = WrappedXHR;
if (!window.__xhrRestoreInstalled) {
window.__xhrRestoreInstalled = true;
window.addEventListener("beforeunload", function () {
try { window.XMLHttpRequest = OriginalXHR; } catch (e) {}
});
}
})();
})();
(function installAutoPlayer() {
if (window.__lostArkAutoPlayerInstalled) return;
window.__lostArkAutoPlayerInstalled = true;
if (!window.__cardMemo) window.__cardMemo = {};
if (!window.__cardDone) window.__cardDone = [];
window.__cardBusy = false;
window.__cardAutoPlayRunning = false;
window.__cardHasPlayedOnce = window.__cardHasPlayedOnce || false;
function sleep(ms) { return new Promise((r) => setTimeout(r, ms)); }
function getCardElement(index) { return document.querySelector(`section#grid button.card[data-idx="${index}"]`); }
function isBoardReady() { return !!document.querySelector('section#grid button.card[data-idx="0"]'); }
function isStateEmpty() {
const memo = window.__cardMemo || {};
const done = window.__cardDone || [];
return Object.keys(memo).length === 0 && done.length === 0;
}
function isClickable(el) {
if (!el) return false;
if (el.disabled) return false;
const rect = el.getBoundingClientRect();
if (rect.width === 0 || rect.height === 0) return false;
return true;
}
async function waitForBoardReadyFull(timeout = 5000) {
const start = Date.now();
while (Date.now() - start < timeout) {
const all = document.querySelectorAll('section#grid button.card[data-idx]');
if (all && all.length >= 18) return true;
await sleep(100);
}
return false;
}
async function ensureBoardReady() {
if (!await waitForBoardReadyFull(5000)) return false;
return true;
}
async function clickWhenReady(index, timeout = 4000) {
const start = Date.now();
while (Date.now() - start < timeout) {
const el = getCardElement(index);
if (isClickable(el)) { el.click(); return true; }
await sleep(50);
}
return false;
}
function findMatchedPairFromMemory() {
const memo = window.__cardMemo || {};
const done = new Set(window.__cardDone || []);
const map = new Map();
for (let i = 0; i < 18; i++) {
const img = memo[i];
if (!img) continue;
if (done.has(i)) continue;
if (!map.has(img)) map.set(img, []);
map.get(img).push(i);
}
for (const [img, idxs] of map.entries()) {
if (idxs.length >= 2) return idxs.slice(0, 2);
}
return null;
}
function findOneUnknownCard(exceptIdx = null) {
const memo = window.__cardMemo || {};
const done = new Set(window.__cardDone || []);
for (let i = 0; i < 18; i++) {
if (i === exceptIdx) continue;
if (done.has(i)) continue;
if (!memo[i]) return i;
}
return null;
}
function findAnyUnmatchedCard(exceptIdx = null) {
const done = new Set(window.__cardDone || []);
for (let i = 0; i < 18; i++) {
if (i === exceptIdx) continue;
if (done.has(i)) continue;
return i;
}
return null;
}
function findSameImageCard(imgUrl, exceptIdx) {
const memo = window.__cardMemo || {};
const done = new Set(window.__cardDone || []);
for (let i = 0; i < 18; i++) {
if (i === exceptIdx) continue;
if (done.has(i)) continue;
if (memo[i] === imgUrl) return i;
}
return null;
}
function isGameComplete() {
const done = new Set(window.__cardDone || []);
return done.size >= 18;
}
async function playOneStep() {
if (window.__cardBusy) return true;
if (!(await ensureBoardReady())) return false;
if (isGameComplete()) return false;
window.__cardBusy = true;
try {
let pair = findMatchedPairFromMemory();
if (pair) {
const ok1 = await clickWhenReady(pair[0]); if (!ok1) return false;
await sleep(600);
const ok2 = await clickWhenReady(pair[1]); if (!ok2) return false;
await sleep(1200);
return true;
}
let firstIndex = findOneUnknownCard();
if (firstIndex == null) {
firstIndex = findAnyUnmatchedCard();
if (firstIndex == null) return false;
}
const okFirst = await clickWhenReady(firstIndex); if (!okFirst) return false;
await sleep(600);
const imgFirst = (window.__cardMemo || {})[firstIndex];
let secondIndex = null;
if (imgFirst) secondIndex = findSameImageCard(imgFirst, firstIndex);
if (secondIndex == null) {
secondIndex = findOneUnknownCard(firstIndex);
if (secondIndex == null) secondIndex = findAnyUnmatchedCard(firstIndex);
}
if (secondIndex != null) {
const okSecond = await clickWhenReady(secondIndex); if (!okSecond) return false;
await sleep(1200);
}
return true;
} finally {
window.__cardBusy = false;
}
}
async function autoPlayAll() {
if (window.__cardAutoPlayRunning) return;
window.__cardAutoPlayRunning = true;
window.__cardHasPlayedOnce = true;
while (window.__cardAutoPlayRunning) {
const moved = await playOneStep();
if (!moved || isGameComplete()) break;
}
window.__cardAutoPlayRunning = false;
}
function stopAutoPlay() { window.__cardAutoPlayRunning = false; }
window.autoPlayAll = autoPlayAll;
window.stopAutoPlay = stopAutoPlay;
if (!window.__watchInstalled) {
window.__watchInstalled = true;
(async function watchForNewGame() {
let pendingToken = null;
while (true) {
if (window.__cardHasPlayedOnce && !window.__cardAutoPlayRunning && isBoardReady() && isStateEmpty() && !pendingToken) {
const token = {};
pendingToken = token;
await sleep(3000);
if (pendingToken === token && window.__cardHasPlayedOnce && !window.__cardAutoPlayRunning && isBoardReady() && isStateEmpty()) {
autoPlayAll();
}
if (pendingToken === token) pendingToken = null;
}
await sleep(1000);
}
})();
}
autoPlayAll();
})();
})();