팁과 노하우 게시판

전체보기

모바일 상단 메뉴

본문 페이지

[일반] 눈사람 구출 작전 자동화 (12/17 최적화)

킹암살
댓글: 130 개
조회: 33405
추천: 91
2025-12-11 15:06:42




안녕하세요 이전에 카드 메모리 게임 자동화 스크립트를 만들었던 킹암살입니다. 

단순히 귀찮아서 혼자 쓰려고 만든 스크립트를 공유해드렸는데 많은 관심 주셔서 감사했습니다. 

유튜브에 소개해주신 갓냥이님도 감사드립니다.


(12/13)

5스테이지에서 층수가 증가하는 것을 확인하여 이에 맞게 코드를 수정했습니다.
다만 이후 스테이지에서도 동일하게 층수가 늘어나는지, 혹은 다른 기믹이 추가되는지는 아직 확실하지 않아 댓글을 통해 빠르게 확인해보려 합니다.


현재 버전은 스테이지별로 층수를 자동으로 감지하도록 구현되어 있어, 이후 층수가 증가하더라도 문제없이 사용할 수 있습니다.
추가로 게임 플레이 과정까지 완전 자동화를 시도해보았으나, 의도적으로 제한된 것인지 혹은 제 스크립트의 한계인지 플레이 전후로 페이지 새로고침이 발생하여 해당 방식으로는 구현이 어려웠습니다.


이 부분은 제 구현 능력이 부족한 탓도 있는 만큼, 더 나은 개선 코드나 조언이 있다면 공유해주시면 감사하겠습니다.


(12/15)

9스테이지부터 문이 5개가 되는 것을 확인하여 코드를 수정했습니다.

아직 9스테이지에서 확인해 본 것은 아니나, 자동 층수, 문 개수 감지 코드로 이전 스테이지들까지 문제 없이 실행되는 것을 확인하여 코드를 공유드립니다. 버그 발생시 알려주시면 감사하겠습니다.


해당 스크립트는 웹페이지에서 실행하여야 정상적으로 작동합니다.

https://lostark.game.onstove.com/Promotion/Mission/251210


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

  1. F12 키를 눌러 개발자 도구를 엽니다.
  2. 상단 탭에서 ‘Sources’를 선택한 뒤 ‘New snippet’을 클릭합니다.
  3. 아래 코드를 그대로 복사해서 붙여넣고 저장합니다.
  4. 게임플레이를 눌러 문들이 모두 보이는 상태에서 만든 스니펫을 오른쪽 클릭 → Run 하면 됩니다.

요청 딜레이로 인한 버그들이 많아 이들을 방지하려다 보니 한층 한층 플레이가 오래걸립니다. 

더 개선하고 싶었으나 토큰이 부족하여 우선은 작동시 문제가 없었던 버전을 공유드립니다.

사용 중 오류나 비정상 동작이 발견되면 댓글로 알려주시면 감사하겠습니다.


수정 사항 

(12/12)

1. 층을 인식하는 방식과 문을 찾는 방식을 더 단순하고 안정적으로 바꿨습니다.

2. 정답/오답을 기록하는 로직을 최소한으로 정리하여 속도를 올렸습니다.

3. 자동 클릭 흐름을 간단하게 개선해 불필요한 시도를 줄였습니다.


(12/13)

1. 층을 자동으로 인식하게 하여 4층 ,5층에서도 문제가 없도록 변경하였습니다.


(12/15)

1. 문을 자동으로 인식하게 하여 9스테이지에서도 문제가 없도록 변경하였습니다.

2. 층수 확인 딜레이를 줄여 게임 속도가 빨라졌습니다.


(12/16)
1. 일부 환경에서 문 개수를 잘못 인식하는 문제가 있어 로직을 변경하였습니다.

(12/17)
1. 이제 오답 문을 클릭해도 1층으로 리셋되지 않음에 따라 코드를 개선하였습니다.

(function () {
    'use strict';

    if (window.__snowmanAutoLight) return;
    window.__snowmanAutoLight = true;

    const sleep = ms => new Promise(r => setTimeout(r, ms));

    function getCurrentFloor() {
        const floors = document.querySelectorAll('[class^="floor"]');
        for (const el of floors) {
            if (el.querySelector('.obj_character')) {
                const name = el.className;
                const num = Number(name.slice(5));
                if (!isNaN(num)) return num;
            }
        }
        return null;
    }

    function getDoors(floor) {
        const el = document.querySelector('.floor' + floor);
        if (!el) return [];
        return Array.from(el.querySelectorAll('button.door'));
    }

    const nextDoorIndex = {};
    let lastClicked = null;
    let stopFlag = false;

    function clickNextDoor(floor) {
        const doors = getDoors(floor);
        if (!doors.length) return;

        if (nextDoorIndex[floor] == null) {
            nextDoorIndex[floor] = 0;
        }

        const idx = nextDoorIndex[floor];
        const btn = doors[idx];

        if (!btn || btn.disabled) return;

        lastClicked = { floor, idx };
        btn.click();
    }

    (function hookXHR() {
        if (window.__snowmanXHRHooked) return;
        window.__snowmanXHRHooked = true;

        const OriginalXHR = window.XMLHttpRequest;

        function WrappedXHR() {
            const xhr = new OriginalXHR();
            let url = null;

            const open = xhr.open;
            xhr.open = function (m, u) {
                url = u;
                return open.apply(xhr, arguments);
            };

            const send = xhr.send;
            xhr.send = function () {
                xhr.addEventListener('load', () => {
                    try {
                        if (!url || url.indexOf('SetDoor') === -1) return;
                        if (!lastClicked) return;

                        const res = JSON.parse(xhr.responseText);
                        const { floor } = lastClicked;

                        if (res.isCorrect) {
                            nextDoorIndex[floor + 1] = 0;
                        } else {
                            nextDoorIndex[floor]++;
                        }
                    } catch {}
                });
                return send.apply(xhr, arguments);
            };
            return xhr;
        }

        WrappedXHR.prototype = OriginalXHR.prototype;
        window.XMLHttpRequest = WrappedXHR;
    })();

    async function loop() {
        while (!stopFlag) {
            const floor = getCurrentFloor();
            if (floor != null) {
                clickNextDoor(floor);
            }
            await sleep(150);
        }
    }

    window.addEventListener('keydown', e => {
        if (e.key === 'Escape') stopFlag = true;
    });

    loop();
})();


모바일 게시판 하단버튼

댓글

새로고침
새로고침

모바일 게시판 하단버튼

지금 뜨는 인벤

더보기+

모바일 게시판 리스트

모바일 게시판 하단버튼

글쓰기

모바일 게시판 페이징

최근 HOT한 콘텐츠

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