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은 다음과 같다
아래는 이 데이터를 게임에 적용한 모습이다