팁과 노하우 게시판

전체보기

모바일 상단 메뉴

본문 페이지

[일반] 7주년 카드 메모리 자동화 개선판

아이콘 텔루라이드
댓글: 45 개
조회: 23247
추천: 42
비공감: 1
2025-11-25 19:54:41

우선 해당 글은 다른 분이 올려주신 코드에서 개선한 버전으로 아래 기능이 추가되었습니다.
화요일에 몰빵으로 하려니 20~21판을 해야하는데, 기존 스크립트는 게임 플레이 버튼을 계속 눌러줘야 해서 이 부분에 대한 개선을 진행했습니다.

해당 스크립트는 위 게시글을 기반으로 개선한 스크립트입니다.


개선 내용
 1. 게임이 끝난 이후 자동으로 게임 재시작 (토큰 모두 사용할 때 까지)
 2. 게임 중 ESC를 누르면 해당 판은 마무리 한 상태에서 다음 게임 진행하지 않음
 3. 게임중 뜨는 팝업창 자동으로 닫음

해당 스크립트는 크롬 개발자 모드(F12)에서 "Cources -> Snippets"으로 진행을 권장드립니다.
자세한 사용 방법은 원글 참조해주세요.

Snippets 을 못 찾는 분들을 위해 사진 첨부합니다.

" >> "를 눌러서 Snippets 으로 변경해야 그 아래에 New Snippets 가 보입니다.


스크립트 사용에 대한 책임은 지지 않습니다.

[아래 코드부터 복사하세요]
[메모장도 추가적으로 첨부합니다]

(function () {
  if (window.__lostArkCardHelperInstalled) return;
  window.__lostArkCardHelperInstalled = true;

  // ==============================
  // 1. 카드 정보 수집 코어 (XHR 훅)
  // ==============================
  (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;

              // 요청 body에서 index 추출
              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) {}
        });
      }
    })();
  })();

  // ==============================
  // 2. 자동 플레이어 + 팝업 자동 처리
  // ==============================
  (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;

    // ESC: "이번 판 끝까지 하고 이후부터는 자동 동작 중단" 플래그
    window.__laStopAfterThisGame = window.__laStopAfterThisGame || false;
    // 토큰 부족으로 인한 하드 스톱 플래그
    window.__laTokenLackStop = window.__laTokenLackStop || false;

    // ESC 키 이벤트: 종료 예약 + 콘솔 메시지
    if (!window.__laEscKeyListenerInstalled) {
      window.__laEscKeyListenerInstalled = true;
      window.addEventListener("keydown", function (e) {
        try {
          if (e.key === "Escape" || e.key === "Esc") {
            if (!window.__laStopAfterThisGame) {
              window.__laStopAfterThisGame = true;
              console.log("ESC가 입력되어 카드를 모두 맞춘 후 스크립트 종료 예정");
            } else {
              console.log("이미 스크립트 종료 예정입니다.");
            }
          }
        } catch (err) {}
      });
    }

    // --------------------------------
    // 유틸
    // --------------------------------
    function sleep(ms) {
      return new Promise((r) => setTimeout(r, ms));
    }

    // "게임 플레이" 버튼 0.5초 후 클릭
    // ESC 이후에는 실제 클릭 시점에 막는다.
    function scheduleClickPlayButton() {
      setTimeout(function () {
        try {
          if (window.__laStopAfterThisGame || window.__laTokenLackStop) return;
          const btn = document.querySelector("#playBtn");
          if (btn && !btn.disabled) {
            btn.click();
          }
        } catch (e) {}
      }, 500);
    }

    // --------------------------------
    // CLEAR 팝업 자동 "확인" + 이후 게임 플레이 버튼 클릭
    // (CLEAR는 항상 닫고, ESC/토큰부족 이후에는 게임 플레이 버튼만 생략)
    // 팝업이 감지된 뒤 0.5초 후에 확인 버튼 클릭
    // --------------------------------
    function clickClearPopupIfExists() {
      try {
        const modals = document.querySelectorAll(".lui-modal__window");
        if (!modals || modals.length === 0) return false;

        for (const modal of modals) {
          const titleEl = modal.querySelector(".lui-modal__title");
          if (!titleEl) continue;

          const titleText = (titleEl.textContent || "").trim();
          if (titleText !== "CLEAR") continue; // CLEAR 팝업만 대상

          // 이미 처리한 CLEAR 팝업이면 스킵
          if (modal.dataset.laClearHandled === "1") continue;
          modal.dataset.laClearHandled = "1";

          const confirmBtn = modal.querySelector(".lui-modal__confirm");
          if (confirmBtn && !confirmBtn.disabled) {
            // 0.5초 후에 CLEAR 확인 클릭
            setTimeout(function () {
              try {
                if (!document.body.contains(modal)) return;
                const btn = modal.querySelector(".lui-modal__confirm");
                if (btn && !btn.disabled) {
                  btn.click();
                  // 종료예약/토큰부족이 아니면 다음 게임 자동 시작
                  if (!window.__laStopAfterThisGame && !window.__laTokenLackStop) {
                    scheduleClickPlayButton();
                  }
                }
              } catch (e) {}
            }, 500);
            return true;
          }
        }
      } catch (e) {}
      return false;
    }

    // --------------------------------
    // 토큰 사용 안내 팝업 자동 "확인" (0.5초 후)
    // ESC/토큰부족 이후에는 더 이상 확인 누르지 않음
    // --------------------------------
    function watchTokenPopupAndConfirm() {
      try {
        if (window.__laStopAfterThisGame || window.__laTokenLackStop) return;

        const modals = document.querySelectorAll(".lui-modal__window");
        if (!modals || modals.length === 0) return;

        for (const modal of modals) {
          // 이미 처리 예약된 모달이면 스킵
          if (modal.dataset.laTokenConfirmScheduled === "1") continue;

          const textEl = modal.querySelector(".popup_text");
          if (!textEl) continue;

          const text = (textEl.textContent || "").replace(/s+/g, " ").trim();
          // 토큰 사용 안내 팝업 식별 (핵심 문구 포함 여부 검사)
          if (text.includes("토큰을 사용하여 카드 메모리 게임을")) {
            modal.dataset.laTokenConfirmScheduled = "1";

            setTimeout(function () {
              try {
                if (window.__laStopAfterThisGame || window.__laTokenLackStop) return;
                // 모달이 아직 존재하는지 다시 확인
                if (!document.body.contains(modal)) return;
                const confirmBtn = modal.querySelector(".lui-modal__confirm");
                if (confirmBtn && !confirmBtn.disabled) {
                  confirmBtn.click();
                }
              } catch (e) {}
            }, 500);
          }
        }
      } catch (e) {}
    }

    // --------------------------------
    // 아이템 획득 팝업 자동 "확인" (0.5초 후)
    // --------------------------------
    function watchRewardPopupAndConfirm() {
      try {
        if (window.__laTokenLackStop) return; // 하드 스톱 후에는 아무 것도 안 함

        const modals = document.querySelectorAll(".lui-modal__window");
        if (!modals || modals.length === 0) return;

        for (const modal of modals) {
          const titleEl = modal.querySelector(".lui-modal__title");
          if (!titleEl) continue;

          const titleText = (titleEl.textContent || "").trim();
          if (titleText !== "아이템 획득") continue; // 아이템 획득 팝업만 대상

          // 이미 처리 예약된 모달이면 스킵
          if (modal.dataset.laRewardConfirmScheduled === "1") continue;
          modal.dataset.laRewardConfirmScheduled = "1";

          setTimeout(function () {
            try {
              if (window.__laTokenLackStop) return;
              if (!document.body.contains(modal)) return;
              const confirmBtn = modal.querySelector(".lui-modal__confirm");
              if (confirmBtn && !confirmBtn.disabled) {
                confirmBtn.click();
              }
            } catch (e) {}
          }, 500);
        }
      } catch (e) {}
    }

    // --------------------------------
    // 토큰 부족 팝업 감지 → 즉시 전체 스크립트 중단
    // --------------------------------
    function watchTokenLackAndStop() {
      try {
        if (window.__laTokenLackStop) return;

        const modals = document.querySelectorAll(".lui-modal__window");
        if (!modals || modals.length === 0) return;

        for (const modal of modals) {
          const textEl = modal.querySelector(".popup_text");
          if (!textEl) continue;

          const text = (textEl.textContent || "").replace(/s+/g, " ").trim();
          if (text.includes("토큰이 부족합니다.")) {
            // 하드 스톱 플래그 설정
            window.__laTokenLackStop = true;
            window.__laStopAfterThisGame = true;
            window.__cardAutoPlayRunning = false;
            console.log("토큰이 부족하여 스크립트가 중단됩니다.");
            return;
          }
        }
      } catch (e) {}
    }

    // --------------------------------
    // 팝업 워처: CLEAR + 토큰 팝업 + 아이템 획득 팝업 + 토큰 부족 팝업 검사
    // --------------------------------
    if (!window.__clearPopupWatcherInstalled) {
      window.__clearPopupWatcherInstalled = true;
      setInterval(function () {
        // 1) 먼저 토큰 부족 팝업 감지
        watchTokenLackAndStop();

        // 토큰 부족이 감지되면 이후 모든 자동 동작 중단
        if (window.__laTokenLackStop) return;

        // 2) 나머지 팝업 처리
        clickClearPopupIfExists();    // CLEAR는 항상 닫음 (0.5초 딜레이 포함)
        watchTokenPopupAndConfirm();  // 토큰 팝업은 종료예약/토큰부족 후에는 더 이상 확인 안 눌림
        watchRewardPopupAndConfirm(); // 아이템 획득 팝업도 0.5초 후 자동 확인
      }, 500);
    }

    // --------------------------------
    // 게임 보드 / 카드 관련 유틸
    // --------------------------------
    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 (window.__laTokenLackStop) return false;
      if (!(await ensureBoardReady())) return false;
      if (isGameComplete()) return false;

      window.__cardBusy = true;
      try {
        // 1) 기억된 짝이 있으면 그 짝부터 처리
        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;
        }

        // 2) 새로운 카드 한 장 뒤집기
        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];

        // 3) 두 번째 카드 선택
        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;
      if (window.__laTokenLackStop) return;

      window.__cardAutoPlayRunning = true;
      window.__cardHasPlayedOnce = true;

      while (window.__cardAutoPlayRunning) {
        if (window.__laTokenLackStop) break;
        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 &&
            !window.__laStopAfterThisGame &&
            !window.__laTokenLackStop &&
            isBoardReady() &&
            isStateEmpty() &&
            !pendingToken
          ) {
            const token = {};
            pendingToken = token;
            await sleep(300);
            if (
              pendingToken === token &&
              window.__cardHasPlayedOnce &&
              !window.__cardAutoPlayRunning &&
              !window.__laStopAfterThisGame &&
              !window.__laTokenLackStop &&
              isBoardReady() &&
              isStateEmpty()
            ) {
              autoPlayAll();
            }
            if (pendingToken === token) pendingToken = null;
          }
          await sleep(1000);
        }
      })();
    }

    // 로딩 즉시 1회 자동 플레이 시작
    autoPlayAll();
  })();
})();


모바일 게시판 하단버튼

댓글

새로고침
새로고침

모바일 게시판 하단버튼

지금 뜨는 인벤

더보기+

모바일 게시판 리스트

모바일 게시판 하단버튼

글쓰기

모바일 게시판 페이징

최근 HOT한 콘텐츠

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