팁과 노하우 게시판

전체보기

모바일 상단 메뉴

본문 페이지

[일반] 7주년 카드 메모리 게임 자동화

킹암살
댓글: 74 개
조회: 23847
추천: 39
2025-11-09 18:16:44

인벤에 글을 처음 써보는 거라 가독성이 조금 떨어질 수도 있습니다.
요즘 카드 메모리 게임이 너무 귀찮아서, 직접 자동화 스크립트를 만들어봤어요.
예전에 플레이크 자동 스크립트를 써보신 분이라면 쉽게 따라하실 수 있을 겁니다.


사용 방법 (마이크로소프트 엣지 기준)


  1. F12 키를 눌러 개발자 도구를 엽니다.

  2. 상단 탭에서 ‘Sources’를 선택한 뒤 ‘New snippet’을 클릭합니다.

  3. 아래 코드를 그대로 복사해서 붙여넣고 저장합니다.

  4. 게임플레이를 눌러 카드들이 모두 보이는 상태에서 만든 스니펫을 오른쪽 클릭 → Run 하면 됩니다.


제가 테스트할 때는 별다른 버그나 문제는 없었습니다.
혹시 사용 중 오류나 비정상 동작이 발견되면
댓글로 알려주시면 감사하겠습니다.

수정사항
새로운 게임 시작시 웹페이지가 멈추는 문제가 있어서 코드를 수정했습니다.(2025/11/12)

(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();
  })();
})();


Lv2 킹암살

모바일 게시판 하단버튼

댓글

새로고침
새로고침

모바일 게시판 하단버튼

지금 뜨는 인벤

더보기+

모바일 게시판 리스트

모바일 게시판 하단버튼

글쓰기

모바일 게시판 페이징

최근 HOT한 콘텐츠

  • 로아
  • 게임
  • IT
  • 유머
  • 연예
AD