WebAPI를 nodejs에서 사용하기

게임을 만들던 중 맵을 그려야하는 상황이 생겼다.
맵은 스타크래프트나 2D RPG게임에서와 같은 바둑판처럼 생긴 모습이다.
그러기 위해서는 맵을 그리기 위한 전용의 맵에디터가 필요했다.
맵 에디터는 맵 작성자가 화면상에 요소를 배치해서 만든 최종완성된 맵을 저장하면 JSON포맷으로 변환되어 게임 내에서 코드에서 읽어들일수 있는 형태로 만들어지는것이 보통이다.
하지만 이런 맵에디터를 만드는건 꽤 귀찮은 일이다.

그래서 아이디어를 떠올렸다.
그림판에 도트를 찍어서 그렇게 만들어진 이미지 그 자체를 맵데이터로써 사용하는것이다.
도트 하나당 맵에서의 한칸이 되는것이다.
그리고 해당 칸의 속성은 색으로 정하기로 했다.
내 게임은 내가 조작하는 요소가 맵상에 준비되고 이 요소를 움직여서 목적지에 도착하는 게임이다.
그리고 맵에는 벽이 존재하고 벽에 닿으면 죽는다.

맵에서 벽은 검정색, 내 캐릭터가 위치할 곳은 빨강색, 목적지는 파란색으로 구분했다.
그렇게 해서 만들어진 그림은 아래와 같다.
그림을 그리는데 큰 노력이 들지 않았다.
이렇게 만든 그림을 json 화 하기 위해서는 이미지의 픽셀정보를 읽어들여야한다.
픽셀정보를 읽어들이는 방법은 이미지관련 라이브러리들이 다양해서 방법이 많이 있다.
나는 웹상에서 이용할 수 있는 Canvas 라는 요소를 이용하기로 했다.
이 Canvas라는 요소는 이미지를 처리할 수 있는 다양한 API 를 제공하며 이것을 웹상에서 이용할 수 있는 API라 하여 WebAPI라고 부른다.
이용을 하기 위해서는 웹페이지 형태로 만들고 브라우저상에서 구동해야한다.
그러기 위해서 아래와 같은 웹페이지를 하나 작성했다.
PCFET0NUWVBFIGh0bWw+CjxodG1sIGxhbmc9ImVuIj4KCjxoZWFkPgogICAgPG1ldGEgY2hhcnNldD0iVVRGLTgiPgogICAgPG1ldGEgaHR0cC1lcXVpdj0iWC1VQS1Db21wYXRpYmxlIiBjb250ZW50PSJJRT1lZGdlIj4KICAgIDxtZXRhIG5hbWU9InZpZXdwb3J0IiBjb250ZW50PSJ3aWR0aD1kZXZpY2Utd2lkdGgsIGluaXRpYWwtc2NhbGU9MS4wIj4KICAgIDx0aXRsZT5Eb2N1bWVudDwvdGl0bGU+CiAgICA8c3R5bGU+CiAgICAgICAgYm9keSB7CiAgICAgICAgICAgIGJhY2tncm91bmQ6IGdyZXk7CiAgICAgICAgfQogICAgPC9zdHlsZT4KICAgIDxzY3JpcHQ+CiAgICAgICAgbGV0IENvbG9ydSA9IHsKICAgICAgICAgICAgUkVEOiAnI2ZmMDAwMCcsCiAgICAgICAgICAgIEJMVUU6ICcjMDAwMGZmJywKICAgICAgICB9OwogICAgICAgIGFzeW5jIGZ1bmN0aW9uIGltZ19iNjRfdG9fYXJyYXkoZGF0YSkgewogICAgICAgICAgICBsZXQgY2RzID0gZ2ltID0+IHsKICAgICAgICAgICAgICAgIGxldCBjdiA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoJ2NhbnZhcycpOwogICAgICAgICAgICAgICAgY3YuaGVpZ2h0ID0gZ2ltLmhlaWdodDsKICAgICAgICAgICAgICAgIGN2LndpZHRoID0gZ2ltLndpZHRoOwogICAgICAgICAgICAgICAgbGV0IGMgPSBjdi5nZXRDb250ZXh0KCIyZCIpOwogICAgICAgICAgICAgICAgYy5kcmF3SW1hZ2UoZ2ltLCAwLCAwLCBnaW0ud2lkdGgsIGdpbS5oZWlnaHQpOwogICAgICAgICAgICAgICAgdmFyIGltZ0RhdGEgPSBjLmdldEltYWdlRGF0YSgwLCAwLCBnaW0ud2lkdGgsIGdpbS5oZWlnaHQpOwogICAgICAgICAgICAgICAgbGV0IHB4bCA9IFtdOwogICAgICAgICAgICAgICAgcHhsLndpZHRoID0gZ2ltLndpZHRoOwogICAgICAgICAgICAgICAgcHhsLmhlaWdodCA9IGdpbS5oZWlnaHQ7CiAgICAgICAgICAgICAgICBmb3IgKHZhciBpID0gMDsgaSA8IGltZ0RhdGEuZGF0YS5sZW5ndGg7IGkgKz0gNCkgewogICAgICAgICAgICAgICAgICAgIHB4bC5wdXNoKFsKICAgICAgICAgICAgICAgICAgICAgICAgaW1nRGF0YS5kYXRhW2kgKyAwXSwKICAgICAgICAgICAgICAgICAgICAgICAgaW1nRGF0YS5kYXRhW2kgKyAxXSwKICAgICAgICAgICAgICAgICAgICAgICAgaW1nRGF0YS5kYXRhW2kgKyAyXSwKICAgICAgICAgICAgICAgICAgICAgICAgaW1nRGF0YS5kYXRhW2kgKyAzXSwKICAgICAgICAgICAgICAgICAgICBdKTsKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgIHJldHVybiBweGw7CiAgICAgICAgICAgIH0KICAgICAgICAgICAgbGV0IGdpbSA9IG5ldyBJbWFnZSgpOwogICAgICAgICAgICByZXR1cm4gYXdhaXQgbmV3IFByb21pc2UocnIgPT4gewogICAgICAgICAgICAgICAgZ2ltLm9ubG9hZCA9ICgpID0+IHJyKGNkcyhnaW0pKTsKICAgICAgICAgICAgICAgIGdpbS5vbmVycm9yID0gKCkgPT4gcnIoKTsKICAgICAgICAgICAgICAgIGdpbS5zcmMgPSBkYXRhOwogICAgICAgICAgICB9KTsKICAgICAgICB9CiAgICAgICAgZnVuY3Rpb24gZGVjaGV4KGNvbG9yKSB7CiAgICAgICAgICAgIHZhciBoZXhhZGVjaW1hbCA9IGNvbG9yLnRvU3RyaW5nKDE2KTsKICAgICAgICAgICAgcmV0dXJuIGhleGFkZWNpbWFsLmxlbmd0aCA9PSAxID8gIjAiICsgaGV4YWRlY2ltYWwgOiBoZXhhZGVjaW1hbDsKICAgICAgICB9CiAgICAgICAgZnVuY3Rpb24gY29udmVydFJHQnRvSGV4KHJlZCwgZ3JlZW4sIGJsdWUpIHsKICAgICAgICAgICAgcmV0dXJuICIjIiArIGRlY2hleChyZWQpICsgZGVjaGV4KGdyZWVuKSArIGRlY2hleChibHVlKTsKICAgICAgICB9CiAgICAgICAgZnVuY3Rpb24gbWFrZUJveCh7IGhlaWdodCwgeCwgeSwgY29sb3IgfSkgewogICAgICAgICAgICBpZiAoY29sb3IgPT09ICcjZmZmZmZmJykgcmV0dXJuOwogICAgICAgICAgICBsZXQgc2l6ZSA9IDE7CiAgICAgICAgICAgIGxldCBib3ggPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCdkaXYnKTsKICAgICAgICAgICAgYm94LnN0eWxlLnBvc2l0aW9uID0gJ2Fic29sdXRlJzsKICAgICAgICAgICAgYm94LnN0eWxlLnRvcCA9IGAke2hlaWdodCAtICh5ICogc2l6ZSl9cHhgOwogICAgICAgICAgICBib3guc3R5bGUubGVmdCA9IGAke3ggKiBzaXplfXB4YDsKICAgICAgICAgICAgYm94LnN0eWxlLndpZHRoID0gYCR7c2l6ZX1weGA7CiAgICAgICAgICAgIGJveC5zdHlsZS5oZWlnaHQgPSBgJHtzaXplfXB4YDsKICAgICAgICAgICAgYm94LnN0eWxlLmJhY2tncm91bmQgPSBgJHtjb2xvcn1gOwogICAgICAgICAgICBkb2N1bWVudC5ib2R5LmFwcGVuZENoaWxkKGJveCk7CiAgICAgICAgICAgIHJldHVybiBib3g7CiAgICAgICAgfQogICAgICAgIGZ1bmN0aW9uIGZpbmRQaXhlbChkZCwgY29sb3IpIHsKICAgICAgICAgICAgbGV0IGxpc3QgPSBbXTsKICAgICAgICAgICAgZm9yIChsZXQgaSA9IDA7IGkgPCBkZC5sZW5ndGg7IGkrKykgewogICAgICAgICAgICAgICAgbGV0IG5hbSA9IGkgJSBkZC53aWR0aDsKICAgICAgICAgICAgICAgIGxldCB5ID0gZGQuaGVpZ2h0IC0gKChpIC0gbmFtKSAvIGRkLndpZHRoKTsKICAgICAgICAgICAgICAgIGxldCB4ID0gbmFtOwogICAgICAgICAgICAgICAgaWYgKGNvbnZlcnRSR0J0b0hleCguLi5kZFtpXSkgPT09IGNvbG9yKSBsaXN0LnB1c2goeyB4LCB5IH0pOwogICAgICAgICAgICB9CiAgICAgICAgICAgIHJldHVybiBsaXN0OwogICAgICAgIH0KICAgICAgICBhc3luYyBmdW5jdGlvbiBwcm9jSW1hZ2UoYjY0KSB7CiAgICAgICAgICAgIGxldCBkYXRhID0gImRhdGE6aW1hZ2UvcG5nO2Jhc2U2NCwiICsgKGAke2I2NH1gLnRyaW0oKS5yZXBsYWNlQWxsKCdcbicsICcnKSk7CiAgICAgICAgICAgIGxldCBkZCA9IGF3YWl0IGltZ19iNjRfdG9fYXJyYXkoZGF0YSk7CiAgICAgICAgICAgIGxldCBsaXN0ID0gW107CiAgICAgICAgICAgIGxldCB0b3RkID0geyBsaXN0LCByZWQ6IGZpbmRQaXhlbChkZCwgQ29sb3J1LlJFRClbMF0gfTsKICAgICAgICAgICAgZm9yIChsZXQgaSA9IDA7IGkgPCBkZC5sZW5ndGg7IGkrKykgewogICAgICAgICAgICAgICAgbGV0IG5hbSA9IGkgJSBkZC53aWR0aDsKICAgICAgICAgICAgICAgIGxldCB5ID0gZGQuaGVpZ2h0IC0gKChpIC0gbmFtKSAvIGRkLndpZHRoKTsKICAgICAgICAgICAgICAgIGxldCB4ID0gbmFtOwogICAgICAgICAgICAgICAgbGV0IGNvbG9yID0gY29udmVydFJHQnRvSGV4KC4uLmRkW2ldKTsKICAgICAgICAgICAgICAgIGlmICghbWFrZUJveCh7IGhlaWdodDogZGQuaGVpZ2h0LCB4LCB5LCBjb2xvcjogY29sb3IgfSkpIGNvbnRpbnVlOwogICAgICAgICAgICAgICAgbGV0IHBvcyA9IHsgeDogeCwgeTogeSwgfTsKICAgICAgICAgICAgICAgIGlmICghKHBvcy54ID09PSB0b3RkLnJlZC54ICYmIHBvcy55ID09PSB0b3RkLnJlZC55KSkgewogICAgICAgICAgICAgICAgICAgIGxldCB0eXBlID0gNTsKICAgICAgICAgICAgICAgICAgICBpZiAoY29sb3IgPT09IENvbG9ydS5CTFVFKSB0eXBlID0gMzsKICAgICAgICAgICAgICAgICAgICBsaXN0LnB1c2goeyBwb3MsIHR5cGUgfSk7CiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgIH0KICAgICAgICAgICAgcmV0dXJuIEpTT04uc3RyaW5naWZ5KHRvdGQpOwogICAgICAgIH07CiAgICAgICAgKGFzeW5jICgpID0+IHsKICAgICAgICAgICAgLy8g7YWM7Iqk7Yq4CiAgICAgICAgICAgIGNvbnNvbGUubG9nKGF3YWl0IHByb2NJbWFnZShgaVZCT1J3MEtHZ29BQUFBTlNVaEVVZ0FBQVlnQUFBRlBBZ01BQUFBcXdMRzVBQUFBREZCTVZFWC8vLzhBQUFBQUFQLy9BQUNZRVRCUkFBQUFmRWxFUVZSNG5PM2EwUW5BSUF4QVFkRVp1bzhqOU1mOVY2a2kzYUFWaVhjTFBFSStrNVFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFEZ0RGZnI2cStKVVdpM3hCYUoybGN1SWZGaElvZVlRa0pDUWtKQ1FrSkNRa0pDUWtKQ1FrSkNRa0pDUWtKaUprcUlLU1JPU3NRNDQ0WklMUGc5QUFEZzlRQzg3VnhlQU41ZFp3QUFBQUJKUlU1RXJrSmdnZz09YCkpOwogICAgICAgIH0pKCkKICAgIDwvc2NyaXB0Pgo8L2hlYWQ+Cgo8Ym9keT4KPC9ib2R5PgoKPC9odG1sPg==
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <style> body { background: grey; } </style> <script> let Coloru = { RED: '#ff0000', BLUE: '#0000ff', }; async function img_b64_to_array(data) { let cds = gim => { let cv = document.createElement('canvas'); cv.height = gim.height; cv.width = gim.width; let c = cv.getContext("2d"); c.drawImage(gim, 0, 0, gim.width, gim.height); var imgData = c.getImageData(0, 0, gim.width, gim.height); let pxl = []; pxl.width = gim.width; pxl.height = gim.height; for (var i = 0; i < imgData.data.length; i += 4) { pxl.push([ imgData.data[i + 0], imgData.data[i + 1], imgData.data[i + 2], imgData.data[i + 3], ]); } return pxl; } let gim = new Image(); return await new Promise(rr => { gim.onload = () => rr(cds(gim)); gim.onerror = () => rr(); gim.src = data; }); } function dechex(color) { var hexadecimal = color.toString(16); return hexadecimal.length == 1 ? "0" + hexadecimal : hexadecimal; } function convertRGBtoHex(red, green, blue) { return "#" + dechex(red) + dechex(green) + dechex(blue); } function makeBox({ height, x, y, color }) { if (color === '#ffffff') return; let size = 1; let box = document.createElement('div'); box.style.position = 'absolute'; box.style.top = `${height - (y * size)}px`; box.style.left = `${x * size}px`; box.style.width = `${size}px`; box.style.height = `${size}px`; box.style.background = `${color}`; document.body.appendChild(box); return box; } function findPixel(dd, color) { let list = []; for (let i = 0; i < dd.length; i++) { let nam = i % dd.width; let y = dd.height - ((i - nam) / dd.width); let x = nam; if (convertRGBtoHex(...dd[i]) === color) list.push({ x, y }); } return list; } async function procImage(b64) { let data = "data:image/png;base64," + (`${b64}`.trim().replaceAll('\n', '')); let dd = await img_b64_to_array(data); let list = []; let totd = { list, red: findPixel(dd, Coloru.RED)[0] }; for (let i = 0; i < dd.length; i++) { let nam = i % dd.width; let y = dd.height - ((i - nam) / dd.width); let x = nam; let color = convertRGBtoHex(...dd[i]); if (!makeBox({ height: dd.height, x, y, color: color })) continue; let pos = { x: x, y: y, }; if (!(pos.x === totd.red.x && pos.y === totd.red.y)) { let type = 5; if (color === Coloru.BLUE) type = 3; list.push({ pos, type }); } } return JSON.stringify(totd); }; (async () => { // 테스트 console.log(await procImage(`iVBORw0KGgoAAAANSUhEUgAAAYgAAAFPAgMAAAAqwLG5AAAADFBMVEX///8AAAAAAP//AACYETBRAAAAfElEQVR4nO3a0QnAIAxAQdEZuo8j9Mf9V6ki3aAViXcLPEI+k5QAAAAAAAAAAAAAAAAAAAAAAAAAAADgDFfr6q+JUWi3xBaJ2lcuIfFhIoeYQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJiJkqIKSROSsQ444ZILPg9AADg9QC87VxeAN5dZwAAAABJRU5ErkJggg==`)); })() </script> </head> <body> </body> </html>
이 코드를 사용하는 방법은 html 파일로 저장하고 웹브라우저로 열어서 웹브라우저의 DevTool 의 Console 에서 procImage(b64) 를 실행하는것이다.
여기서 procImage 함수의 인자인 b64의 타입은 문자열이며 이미지파일을 base64로 인코딩한 결과물을 의미한다.
파일을 base64인코딩 하는 방법은 다음과 같다
b3BlbnNzbCBiYXNlNjQgLWluIHNvdXJjZS5wbmcgLW91dCBkZXN0aW5hdGlvbi5iNjQ=
openssl base64 -in source.png -out destination.b64
이렇게 해서 만들어진 base64문자열을 procImage 함수에 넣어 수행하면 결과물로써 내가 원하는 형태의 json 을 주도록 만들었다.

일단은 완성이다.
이렇게 만든 json 을 게임에 삽입해서 적용하면 된다.

그런데 문제가 있다.
사용하기 불편하다는 점이다.

사용을 위해서 위에 나열한 사용방법대로 해야한다..
맵을 한개만 만들것이 아니고 여러개 만들건데..
위 방법은 맵을 그리기는 쉬운데 변환과정이 불편하다는 문제가 있다.
맵을 그리기 쉽다는 장점이 무색해지는 것이다.

이상적인 방법은 그냥 터미널에서 아래와 같이 실행하면 json 파일이 만들어지게 하는 방법이다
bm9kZSBjb252ZXJ0aW1ndG9tYXAgc291cmNlLnBuZyA+IGRlc3RpbmF0aW9uLmpzb24=
node convertimgtomap source.png > destination.json
하지만 이미 사용한 방식은 웹브라우저상에서 사용할수 있는 Canvas WebAPI 를 사용한 방식이다.
이 WebAPI는 어디까지나 웹브라우저에서 사용할 수 있는 방법이다.
node를 기반으로 하여 구현함에 있어서 WebAPI를 사용할 방법을 떠올려야하는것이다.
방법을 모두 찾아본것은 아니지만 꽤 다양한 방법이 있을것이라고 생각한다.
내가 선택한 방법은 Google의 Puppeteer라고 하는 모듈을 사용하는 것이다.
이 Puppeteer는 주로 웹어플리케이션의 테스트, 자동화등에 사용되는것이다.
즉 웹브라우저에서 일어나는 일을 코드로써 자동화 할 수 있는 모듈이다.
나는 이것을 이용해서 위에 이미 만들어놓은 변환코드가 담긴 html 파일을 열어서 위에 설명한 일련의 수행을 코드로 작성해서 자동화할 생각이다.
이 계획을 코드로 작성한 모습은 다음과 같다.
Y29uc3QgcHVwcGV0ZWVyID0gcmVxdWlyZSgncHVwcGV0ZWVyJyk7CmNvbnN0IGZzID0gcmVxdWlyZSgnZnMnKTsKZnVuY3Rpb24gYWJzUGF0aChwYXRoKSB7CiAgICBpZiAocGF0aFswXSA9PT0gJy8nKSByZXR1cm4gcGF0aDsKICAgIHJldHVybiBwcm9jZXNzLmN3ZCgpICsgJy8nICsgcGF0aDsKfQphc3luYyBmdW5jdGlvbiB3YWl0Rm9yQ29tcGxldGUocGFnZSkgewogICAgd2hpbGUgKHRydWUpIHsKICAgICAgICB0cnkgewogICAgICAgICAgICBhd2FpdCBwYWdlLmV2YWx1YXRlKGFzeW5jIGFjY2luZm8gPT4gewogICAgICAgICAgICAgICAgd2hpbGUgKHRydWUpIHsKICAgICAgICAgICAgICAgICAgICBpZiAod2luZG93Lm5leHRtYXJrZXIpIHsKICAgICAgICAgICAgICAgICAgICAgICAgYXdhaXQgbmV3IFByb21pc2UociA9PiBzZXRUaW1lb3V0KHIpKTsKICAgICAgICAgICAgICAgICAgICB9IGVsc2UgewogICAgICAgICAgICAgICAgICAgICAgICBicmVhazsKICAgICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgIH0sIHt9KTsKICAgICAgICAgICAgYXdhaXQgcGFnZS5ldmFsdWF0ZShhc3luYyBhY2NpbmZvID0+IHsKICAgICAgICAgICAgICAgIHdoaWxlICh0cnVlKSB7CiAgICAgICAgICAgICAgICAgICAgYXdhaXQgbmV3IFByb21pc2UociA9PiBzZXRUaW1lb3V0KHIpKTsKICAgICAgICAgICAgICAgICAgICBpZiAoZG9jdW1lbnQucmVhZHlTdGF0ZSA9PT0gJ2NvbXBsZXRlJykgeyBicmVhazsgfQogICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgd2luZG93Lm5leHRtYXJrZXIgPSB0cnVlOwogICAgICAgICAgICB9LCB7fSk7CiAgICAgICAgICAgIGJyZWFrOwogICAgICAgIH0gY2F0Y2ggKGUpIHsKICAgICAgICAgICAgYXdhaXQgbmV3IFByb21pc2UociA9PiBzZXRUaW1lb3V0KHIpKTsKICAgICAgICB9CiAgICB9Cn0KKGFzeW5jICgpID0+IHsKICAgIGxldCBmaWxlcGF0aCA9IGFic1BhdGgocHJvY2Vzcy5hcmd2WzJdKTsKICAgIGNvbnN0IGRhdGEgPSBmcy5yZWFkRmlsZVN5bmMoZmlsZXBhdGgsIHsgZW5jb2Rpbmc6ICdiYXNlNjQnIH0pOwogICAgdHJ5IHsKICAgICAgICBjb25zdCBicm93c2VyID0gYXdhaXQgcHVwcGV0ZWVyLmxhdW5jaCh7IGhlYWRsZXNzOiB0cnVlIH0pOwogICAgICAgIGNvbnN0IHBhZ2UgPSBhd2FpdCBicm93c2VyLm5ld1BhZ2UoKTsKICAgICAgICBhd2FpdCBwYWdlLmdvdG8oYGZpbGU6Ly8ke19fZGlybmFtZX0vaW1hZ2UuaHRtbGApOwogICAgICAgIGF3YWl0IHdhaXRGb3JDb21wbGV0ZShwYWdlKTsKICAgICAgICBjb25zdCByZXN1bHQgPSBhd2FpdCBwYWdlLmV2YWx1YXRlKGFzeW5jIGFjY2luZm8gPT4gewogICAgICAgICAgICB0cnkgeyByZXR1cm4gYXdhaXQgcHJvY0ltYWdlKGFjY2luZm8uZGF0YSk7IH0gY2F0Y2ggeyB9CiAgICAgICAgfSwgeyBkYXRhIH0pOwogICAgICAgIGF3YWl0IGJyb3dzZXIuY2xvc2UoKTsKICAgICAgICBjb25zb2xlLmxvZyhyZXN1bHQpOwogICAgfSBjYXRjaCAoZSkgeyB9Cn0pKCk7Cg==
const puppeteer = require('puppeteer'); const fs = require('fs'); function absPath(path) { if (path[0] === '/') return path; return process.cwd() + '/' + path; } async function waitForComplete(page) { while (true) { try { await page.evaluate(async accinfo => { while (true) { if (window.nextmarker) { await new Promise(r => setTimeout(r)); } else { break; } } }, {}); await page.evaluate(async accinfo => { while (true) { await new Promise(r => setTimeout(r)); if (document.readyState === 'complete') { break; } } window.nextmarker = true; }, {}); break; } catch (e) { await new Promise(r => setTimeout(r)); } } } (async () => { let filepath = absPath(process.argv[2]); const data = fs.readFileSync(filepath, { encoding: 'base64' }); try { const browser = await puppeteer.launch({ headless: true }); const page = await browser.newPage(); await page.goto(`file://${__dirname}/image.html`); await waitForComplete(page); const result = await page.evaluate(async accinfo => { try { return await procImage(accinfo.data); } catch { } }, { data }); await browser.close(); console.log(result); } catch (e) { } })();
이렇게 해서 만들어진 JSON은 다음과 같다
아래는 이 데이터를 게임에 적용한 모습이다