제이월드 팀이 한 게임을 웹, 게임 포털, 앱 스토어에 같은 코드로 내는 방법이에요. 빌드 모드, 플랫폼 어댑터, 광고 규칙, 테스트 방식을 한판의 실제 코드로 보여 드려요.
게임을 하나 만들면 내보낼 곳이 많아요. 우리 웹사이트, 게임 포털, 구글 플레이, 앱 스토어, 토스, 원스토어까지요. 채널마다 광고 SDK도, 저장 방식도, 뒤로 가기 키도 달라요.
제이월드 팀은 이 차이를 게임 코드 밖으로 밀어내는 방식으로 풀었어요. 이 글은 장기·오목 게임 한판의 실제 소스를 예로 들어, 코드 하나가 여러 채널로 나가는 구조를 정리한 개발 노트예요.

게임 코드는 채널을 모른다
한판은 Vite와 TypeScript로 만들었어요. 폴더는 크게 이렇게 나뉘어요.
| 폴더 | 하는 일 |
|---|---|
src/core | 장기·오목 규칙, AI, 진행 저장 같은 순수 게임 로직. 화면(DOM)이 없어요 |
src/ui, 렌더 파일 | 보드 그리기, 패널, 연출 |
src/platform | 채널마다 하나씩 있는 어댑터 |
src/i18n* | 12개 언어 문구 |
핵심은 src/core가 순수하다는 점이에요. 화면이 없으니 터미널에서 node --test로 규칙을 바로 검사할 수 있어요. 채널이 달라져도 이 폴더는 건드리지 않아요.
채널은 빌드할 때 하나만 고른다
게임 코드는 어느 채널인지 직접 묻지 않고 @platform이라는 이름만 불러와요. 이 이름이 어떤 파일을 가리킬지는 빌드 설정(vite.config.ts)이 정해요.
| 빌드 | 대상 | 결과물 폴더 |
|---|---|---|
| 기본 | 웹(games.jworld.dev에 올릴 때는 익명 플레이 횟수가 추가돼요) | dist-web |
--mode portal | Playgama Bridge로 연결되는 웹 게임 포털 | dist-portal |
--mode toss | 앱인토스 | dist |
--mode onestore | 원스토어 웹 상품 | dist-onestore |
--mode play | 안드로이드 앱(Capacitor). iOS 빌드도 같은 모드를 써요 | dist-play |
| 별도 설정 파일 | TikTok 미니게임 | dist-tiktok |
모드를 바꾸면 같은 소스에서 어댑터 파일만 바뀐 번들이 나와요. 한 채널에서만 쓰는 코드가 다른 채널 번들에 섞여 들어가지 않는 점도 장점이에요.
어댑터가 맡는 일
모든 어댑터는 같은 Platform 인터페이스를 채워요. 한판 기준으로 들어 있는 항목이에요.
- 저장:
get,getMany,set,remove. 포털은 시작할 때 값을 한꺼번에 읽어야 해서getMany가 따로 있어요. - 보상형 광고: 지금 광고를 보여 줄 수 있는지 묻는
available과 실제로 보여 주는show. - 전면 광고: 광고가 없는 채널에서는 아무 일도 안 하는 함수예요.
- 플레이 신호: 첫 화면이 떴을 때(
ready), 대국을 시작하고 멈출 때(gameplayStart,gameplayStop). 포털 SDK가 이 신호를 받아요. - 익명 통계: 우리 사이트 빌드에서만 켜져요. 다른 주소에서는 아무것도 보내지 않아요.
채널 이름은 web, portal, toss, onestore, twa, play, tiktok 일곱 가지로 정해져 있어요. 구글 플레이의 TWA 방식은 웹 빌드를 ?src=twa로 여는 거라서 따로 번들을 만들지 않아요.
광고 규칙도 코드 한 곳에 모은다
광고는 채널마다 달라서 사고가 나기 쉬운 부분이에요. 한판은 전면 광고 규칙을 DOM도 광고 SDK도 없는 순수 함수로 써 두고 테스트해요. 규칙은 이래요.
- 지금까지 센 대국이 3판 이상이어야 해요(말을 10수 이상 둔 대국만 세요). 처음 두 판에는 절대 안 나와요.
- 직전 전면 광고 뒤로 3판 이상 지나야 해요. 3판에 한 번이 최대예요.
- 직전 광고 뒤로 180초 이상 지나야 해요.
- 처음 실행하고 15분이 지나야 해요.
- 결과 창에서 보상형 광고를 본 판에는 안 나와요.
- 같은 단계에서 연속 2번 졌다면 '다시 하기'에는 안 나와요.
- 둘이 두기 대국에는 어느 채널에서도 안 나와요.
전면 광고 자체가 있는 채널은 포털, 구글 플레이 앱, TikTok뿐이에요. 웹 빌드, 토스, 원스토어, TWA에서는 규칙을 통과해도 아무것도 안 떠요. 보상형 광고는 항상 '보너스'로만 붙어서, 광고를 볼 수 없는 채널에서는 버튼 자체를 그리지 않아요.
규칙이 한 파일에 있으면 채널이 늘어도 "어디서는 이렇고 어디서는 저렇고"가 코드에 퍼지지 않아요.
같은 게임, 다른 뒤로 가기
안드로이드에서는 뒤로 가기 키를 누르면 앱이 닫히고, 웹에서는 브라우저 기록이 뒤로 가요. 한판은 가짜 브라우저 기록을 만들어 크롬의 뒤로 가기처럼 움직이게 한 뒤, 어댑터의 back 동작을 back.test.ts로 검사해요. 이런 채널 차이는 사람이 휴대폰으로 눌러 보기 전에 테스트로 먼저 잡는 편이에요.

12개 언어는 어댑터 밖에서
언어도 채널 코드와 섞지 않아요. 12개 언어 목록과 브라우저 언어를 고르는 규칙은 apps/shared/i18n에 한 번만 있고, 모든 게임이 같이 써요. 한판은 i18n-en.ts, i18n-ja.ts 같은 언어별 파일을 두고, 없는 문구는 영어, 그다음 한국어로 내려가요. 호스트 플랫폼이 언어를 알려 주면(어댑터의 언어 값) 그걸 먼저 따라요.
지원 언어는 한국어, 영어, 일본어, 중국어 간체·번체, 스페인어, 포르투갈어(브라질), 독일어, 프랑스어, 인도네시아어, 태국어, 베트남어예요. 스토어 설명과 스크린샷도 같은 12개 언어로 만들어요.
새 채널을 붙일 때 하는 일
Platform인터페이스를 채우는 어댑터 파일을 하나 써요.vite.config.ts의 모드 표에 어댑터와 결과물 폴더를 한 줄 더해요.package.json에build:<채널>스크립트를 더해요.- 광고 규칙 함수에 그 채널이 전면 광고를 지원하는지 알려 줘요.
- 규칙 테스트와 채널 테스트를 돌려요(
npm test,npm run typecheck).
게임 로직(src/core)은 이 다섯 단계 어디에도 안 나와요. 새 채널을 붙인다고 규칙 테스트가 깨질 일이 없다는 뜻이에요.
정리
- 게임 로직은 화면도 SDK도 없는
src/core에 두고node --test로 검사해요. - 채널은 빌드 모드가
@platform어댑터를 바꿔 끼워서 골라요. - 어댑터는 저장, 보상형·전면 광고, 플레이 신호, 통계를 같은 인터페이스로 맞춰요.
- 광고 규칙은 순수 함수 한 곳에 두고, 전면 광고가 없는 채널에서는 아무것도 안 뜨게 해요.
- 언어는 공유 폴더에서 한 번만 관리해요.
한판은 웹에서 바로 해 볼 수 있어요. 앱 소개는 /apps/hanpan/에서 볼 수 있어요.
이 글은 제이월드 팀이 게임 설계 문서와 실제 플레이 수치를 바탕으로 썼고, 글을 다듬을 때 AI 도구를 함께 썼어요.