init: create Play Uniare project
This commit is contained in:
9
.dockerignore
Normal file
9
.dockerignore
Normal file
@@ -0,0 +1,9 @@
|
||||
.git
|
||||
.gitignore
|
||||
.vscode
|
||||
dist
|
||||
node_modules
|
||||
npm-debug.log
|
||||
.env
|
||||
.env.*
|
||||
docs
|
||||
2
.env.example
Normal file
2
.env.example
Normal file
@@ -0,0 +1,2 @@
|
||||
VITE_CARD_ASSET_BASE_URL=/card-assets
|
||||
VITE_SOCKET_SERVER_URL=http://localhost:3000
|
||||
27
.gitignore
vendored
Normal file
27
.gitignore
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
3
.vscode/extensions.json
vendored
Normal file
3
.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"recommendations": ["Vue.volar"]
|
||||
}
|
||||
236
AGENTS.md
Normal file
236
AGENTS.md
Normal file
@@ -0,0 +1,236 @@
|
||||
# Play Uniare AI 작업 지침
|
||||
|
||||
## 1. 문서 목적
|
||||
|
||||
이 파일은 사람과 AI 작업자가 같은 기준으로 프로젝트를 이해하고 변경하기
|
||||
위한 최상위 작업 규칙이다. 작업 시작 전에 이 파일과 `docs/`의 관련 문서를
|
||||
반드시 읽는다.
|
||||
|
||||
## 2. 프로젝트 목표
|
||||
|
||||
- 기존 UA Deck Builder가 전달한 덱 데이터로 카드 시뮬레이터를 제공한다.
|
||||
- 게임 규칙을 자동 판정하지 않는다.
|
||||
- 사용자가 카드 이동, 뒤집기, 탭, 덱·손패·무덤 이동을 직접 수행한다.
|
||||
- 혼자 연습하는 로컬 모드와 방 코드 기반 2인 세션을 제공한다.
|
||||
- 각 플레이어는 자신이 소유한 카드만 조작한다.
|
||||
- 상대 효과로 내 카드가 제거되는 경우에도 내 카드 소유자가 직접 처리한다.
|
||||
|
||||
## 3. 기술 기준
|
||||
|
||||
- 프런트엔드: Vue 3 Composition API와 `<script setup>`
|
||||
- 빌드 도구: Vite
|
||||
- 스타일: Tailwind CSS 우선
|
||||
- 상태 관리: Pinia 도입 예정
|
||||
- 화면 이동: Vue Router 도입 예정
|
||||
- 실시간 통신: Socket.IO 도입 예정
|
||||
- 배포: Docker Compose와 Nginx
|
||||
- 운영 환경: UGREEN NAS
|
||||
|
||||
새 라이브러리를 추가하기 전에는 기존 기술로 해결 가능한지 확인하고, 추가
|
||||
이유와 영향을 `docs/history.md`에 기록한다.
|
||||
|
||||
## 4. 작업 시작 절차
|
||||
|
||||
1. `AGENTS.md`를 읽는다.
|
||||
2. `docs/spec.md`, `docs/map.md`, `docs/todo.md`를 읽는다.
|
||||
3. `git status`로 기존 변경 사항을 확인한다.
|
||||
4. 관련 코드와 기존 패턴을 확인한다.
|
||||
5. 작업 범위를 작게 정하고 구현한다.
|
||||
6. 빌드와 관련 검증을 수행한다.
|
||||
7. 변경 성격에 맞는 문서를 업데이트한다.
|
||||
8. 작업 결과 형식에 맞춰 보고한다.
|
||||
|
||||
## 5. 코드 작성 규칙
|
||||
|
||||
### 5.1 공통
|
||||
|
||||
- 기존 패턴을 우선하며 최소 범위만 수정한다.
|
||||
- 하나의 컴포넌트는 하나의 분명한 역할을 갖는다.
|
||||
- 반복되는 값은 상수, 설정 또는 데이터 모델로 분리한다.
|
||||
- 서버 주소, 이미지 원본 주소, 포트, 비밀값을 코드에 하드코딩하지 않는다.
|
||||
- 임시 이름과 의미 없는 자리표시자 이름을 사용하지 않는다.
|
||||
- 검증 없이 작업을 완료 처리하지 않는다.
|
||||
|
||||
### 5.2 네이밍
|
||||
|
||||
- 의미가 드러나는 영문 이름을 사용한다.
|
||||
- 축약어를 남용하지 않는다.
|
||||
- `temp`, `tmp`, `test1`, `data1`, `foo`, `bar`, `aaa` 같은 임시 이름을
|
||||
금지한다.
|
||||
- Vue 컴포넌트 파일은 PascalCase를 사용한다.
|
||||
- 함수와 변수는 camelCase를 사용한다.
|
||||
- 상수는 의미가 분명한 경우 UPPER_SNAKE_CASE를 사용한다.
|
||||
- 이벤트 이름은 발생한 사실을 나타내는 kebab-case를 사용한다.
|
||||
- 불리언 값은 `is`, `has`, `can`, `should` 접두어를 우선한다.
|
||||
|
||||
### 5.3 Vue
|
||||
|
||||
- Composition API와 `<script setup>`을 사용한다.
|
||||
- 화면 컴포넌트는 `src/views/`, 재사용 컴포넌트는 `src/components/`에 둔다.
|
||||
- 비즈니스 상태를 컴포넌트 내부에 중복 저장하지 않는다.
|
||||
- props를 직접 변경하지 않는다.
|
||||
- 외부 효과와 구독은 컴포넌트 해제 시 반드시 정리한다.
|
||||
|
||||
### 5.4 스타일
|
||||
|
||||
- Tailwind CSS 유틸리티를 기본으로 사용한다.
|
||||
- 반복되는 디자인 패턴은 컴포넌트로 추출한다.
|
||||
- 임의 픽셀값은 게임 보드 좌표처럼 필요한 경우에만 사용하고 이유를
|
||||
문서화한다.
|
||||
- 카드 위치는 화면 픽셀이 아니라 보드 기준 정규화 좌표 또는 영역 기반으로
|
||||
저장한다.
|
||||
- 인라인 `style`은 드래그 좌표, 회전처럼 동적 계산이 필요한 경우에만
|
||||
허용한다.
|
||||
|
||||
### 5.5 주석
|
||||
|
||||
- 함수와 클래스 앞에는 한국어 JSDoc 문서화 주석을 작성한다.
|
||||
- 주석은 코드가 무엇을 하는지 반복하기보다 이유, 제약, 입력·출력을 설명한다.
|
||||
- 복잡한 타입은 `@typedef`, 함수 입력은 `@param`, 반환은 `@returns`를 쓴다.
|
||||
|
||||
```js
|
||||
/**
|
||||
* 덱 빌더에서 전달된 카드 목록을 실제 카드 인스턴스로 확장한다.
|
||||
*
|
||||
* @param {ImportedDeckCard[]} importedCards 수량이 포함된 덱 카드 목록
|
||||
* @returns {GameCard[]} 고유 식별자가 부여된 카드 인스턴스 목록
|
||||
*/
|
||||
function createGameCardInstances(importedCards) {
|
||||
return []
|
||||
}
|
||||
```
|
||||
|
||||
## 6. 상태 관리 원칙
|
||||
|
||||
### 6.1 상태 구분
|
||||
|
||||
- 서버 권위 상태: 방, 플레이어, 카드 소유권, 카드 영역, 덱 순서
|
||||
- 공유 게임 상태: 필드 카드 좌표, 앞뒷면, 탭 상태, 무덤, 공개 영역
|
||||
- 개인 비공개 상태: 내 손패 카드 식별 정보
|
||||
- 로컬 UI 상태: 선택 카드, 드래그 중 여부, 메뉴 열림, 확대 상태
|
||||
|
||||
서버 권위 상태를 UI 컴포넌트의 로컬 상태만으로 확정하지 않는다.
|
||||
|
||||
### 6.2 카드 모델
|
||||
|
||||
카드는 덱 원본 정보와 플레이 중 인스턴스를 구분한다.
|
||||
|
||||
```js
|
||||
{
|
||||
instanceId: 'player-1-card-17',
|
||||
ownerId: 'player-1',
|
||||
cardNumber: 'EX01BT/HTR-2-001',
|
||||
imageFileName: 'EX01BT_HTR-2-001.png',
|
||||
zone: 'deck',
|
||||
position: { x: 0.5, y: 0.5 },
|
||||
isFaceUp: false,
|
||||
isTapped: false,
|
||||
stackOrder: 17
|
||||
}
|
||||
```
|
||||
|
||||
- `instanceId`는 같은 카드 여러 장을 구분하는 고유값이다.
|
||||
- 소유권은 게임 시작 후 임의로 변경하지 않는다.
|
||||
- 카드 조작 요청은 요청자와 `ownerId`가 같은지 서버에서 확인한다.
|
||||
- 상대 손패에는 카드 번호와 이미지 정보를 전송하지 않는 방향을 우선한다.
|
||||
|
||||
### 6.3 실시간 이벤트
|
||||
|
||||
- 상태 전체를 매번 전송하기보다 명시적인 이벤트와 필요한 변경값을 전송한다.
|
||||
- 이벤트 이름은 `card-moved`, `card-flipped`, `deck-shuffled`처럼 작성한다.
|
||||
- 재접속 시에는 서버의 최신 전체 스냅샷으로 복구한다.
|
||||
- 같은 이벤트가 중복 수신되어도 상태가 망가지지 않도록 설계한다.
|
||||
|
||||
## 7. 데이터 처리 원칙
|
||||
|
||||
- 기존 덱 빌더 쿼리는 초기 덱 불러오기 용도로만 사용한다.
|
||||
- 외부 입력은 신뢰하지 않고 스키마 검사 후 사용한다.
|
||||
- 카드 이미지 URL은 환경변수의 원본 주소와 파일 경로를 조합한다.
|
||||
- NAS 운영 환경에서는
|
||||
`/volume1/docker/projects/apps/uniare/config/www/images`를 컨테이너에 읽기
|
||||
전용으로 연결하고 `/card-assets` 경로로 제공한다.
|
||||
- 기존 Uniare의 `config/www/images`에는 쓰기 작업을 수행하지 않는다.
|
||||
- URL 길이 문제를 고려해 장기적으로 덱 코드 또는 `deckId` 방식을 도입한다.
|
||||
- URL, 로그, Git에 사용자 토큰이나 비밀값을 남기지 않는다.
|
||||
- 오류가 있는 카드 한 장 때문에 전체 덱 불러오기가 중단되지 않도록 오류
|
||||
목록을 사용자에게 명확히 보여준다.
|
||||
|
||||
## 8. 문서 업데이트 기준
|
||||
|
||||
- 기능 변경 → `docs/spec.md`
|
||||
- 버그 수정 → `docs/update.md`
|
||||
- 이슈 발견 → `docs/todo.md`
|
||||
- 구조·기술·의사결정 변경 → `docs/history.md`
|
||||
- 화면 변경 → `docs/map.md`
|
||||
- 배포 변경 → `docs/deploy.md`
|
||||
- 개발 명령 변경 → `docs/development.md`
|
||||
|
||||
문서 업데이트 누락을 금지한다.
|
||||
|
||||
## 9. 금지 사항
|
||||
|
||||
- 기존 스타일과 구조를 확인하지 않은 변경
|
||||
- 요청 범위를 벗어난 대규모 리팩터링
|
||||
- 서버 주소, 포트, API 키와 비밀번호 하드코딩
|
||||
- `.env`, 인증서, 개인키, NAS 계정 정보 커밋
|
||||
- 조작 권한 검증을 클라이언트에만 의존
|
||||
- 상대의 비공개 손패 정보를 클라이언트로 전송
|
||||
- 테스트 또는 빌드 검증 없이 완료 처리
|
||||
- 관련 문서 업데이트 누락
|
||||
|
||||
## 10. 의사결정 원칙
|
||||
|
||||
1. 기존 패턴 우선
|
||||
2. 최소 수정
|
||||
3. 유지보수 용이성
|
||||
4. 구조 파악 용이성
|
||||
5. 사용자가 직접 운영할 수 있는 단순성
|
||||
6. 비공개 정보와 카드 소유권의 안전성
|
||||
|
||||
## 11. 단계별 개발 계획
|
||||
|
||||
### 1단계: 로컬 싱글 플레이
|
||||
|
||||
- 덱 쿼리 파싱과 검증
|
||||
- 카드 인스턴스 생성
|
||||
- 덱 셔플과 드로우
|
||||
- 카드 이동, 뒤집기, 탭
|
||||
- 손패, 필드, 무덤, 공개 영역
|
||||
- 덱 위·아래 이동과 위에서 N장 공개
|
||||
|
||||
### 2단계: 2인 세션
|
||||
|
||||
- 로비와 방 생성·참가
|
||||
- 플레이어 좌석과 소유권
|
||||
- Socket.IO 서버
|
||||
- 실시간 카드 이벤트 동기화
|
||||
- 상대 손패 비공개 처리
|
||||
- 연결 종료와 재접속 복구
|
||||
|
||||
### 3단계: 운영 안정화
|
||||
|
||||
- NAS Docker 배포
|
||||
- 상태 저장소 도입 검토
|
||||
- 모바일·태블릿 조작 개선
|
||||
- 오류 로그와 상태 복구
|
||||
- 관전자와 게임 기록은 별도 검토
|
||||
|
||||
구체적인 미완료 항목은 `docs/todo.md`에서 관리한다.
|
||||
|
||||
## 12. 작업 결과 보고 형식
|
||||
|
||||
- 수정 내용
|
||||
- 변경 파일
|
||||
- 검증 내용
|
||||
- 문서 반영 여부
|
||||
- 남은 이슈
|
||||
|
||||
## 13. 최종 체크리스트
|
||||
|
||||
- [ ] `AGENTS.md`와 관련 `docs/` 확인
|
||||
- [ ] 코드 수정 완료
|
||||
- [ ] 네이밍과 JSDoc 규칙 확인
|
||||
- [ ] 빌드 또는 관련 검증 수행
|
||||
- [ ] 변경 성격에 맞는 문서 반영
|
||||
- [ ] 민감 정보 포함 여부 확인
|
||||
- [ ] 남은 이슈를 `docs/todo.md`에 기록
|
||||
19
Dockerfile
Normal file
19
Dockerfile
Normal file
@@ -0,0 +1,19 @@
|
||||
FROM node:22-alpine AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package.json package-lock.json ./
|
||||
RUN npm ci
|
||||
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
|
||||
FROM nginx:1.27-alpine AS runtime
|
||||
|
||||
COPY docker/nginx.conf /etc/nginx/conf.d/default.conf
|
||||
COPY --from=builder /app/dist /usr/share/nginx/html
|
||||
|
||||
EXPOSE 80
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
|
||||
CMD wget --quiet --tries=1 --spider http://127.0.0.1/ || exit 1
|
||||
27
README.md
Normal file
27
README.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# Play Uniare
|
||||
|
||||
Union Arena 덱을 불러와 혼자 연습하거나 2인 세션으로 플레이하는 웹 카드
|
||||
시뮬레이터입니다.
|
||||
|
||||
## 기술 구성
|
||||
|
||||
- Vue 3
|
||||
- Vite
|
||||
- Tailwind CSS
|
||||
- 향후 Pinia, Vue Router, Socket.IO 도입 예정
|
||||
|
||||
## 빠른 실행
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
브라우저에서 터미널에 표시된 로컬 주소로 접속합니다.
|
||||
|
||||
상세한 개발 및 배포 방법은 다음 문서를 참고합니다.
|
||||
|
||||
- 개발: `docs/development.md`
|
||||
- NAS 배포: `docs/deploy.md`
|
||||
- 프로젝트 명세: `docs/spec.md`
|
||||
- AI 작업 규칙: `AGENTS.md`
|
||||
7
compose.nas.yaml
Normal file
7
compose.nas.yaml
Normal file
@@ -0,0 +1,7 @@
|
||||
services:
|
||||
playuniare-web:
|
||||
volumes:
|
||||
- type: bind
|
||||
source: /volume1/docker/projects/apps/uniare/config/www/images
|
||||
target: /srv/uniare-card-images
|
||||
read_only: true
|
||||
7
compose.test.nas.yaml
Normal file
7
compose.test.nas.yaml
Normal file
@@ -0,0 +1,7 @@
|
||||
services:
|
||||
playuniare-test-web:
|
||||
volumes:
|
||||
- type: bind
|
||||
source: /volume1/docker/projects/apps/uniare/config/www/images
|
||||
target: /srv/uniare-card-images
|
||||
read_only: true
|
||||
11
compose.test.yaml
Normal file
11
compose.test.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
name: playuniare-test
|
||||
|
||||
services:
|
||||
playuniare-test-web:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
container_name: playuniare-test-web
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- '18080:80'
|
||||
11
compose.yaml
Normal file
11
compose.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
name: playuniare-production
|
||||
|
||||
services:
|
||||
playuniare-web:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
container_name: playuniare-web
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- '18081:80'
|
||||
31
docker/nginx.conf
Normal file
31
docker/nginx.conf
Normal file
@@ -0,0 +1,31 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
location = /health {
|
||||
access_log off;
|
||||
add_header Content-Type text/plain;
|
||||
return 200 'ok';
|
||||
}
|
||||
|
||||
location ^~ /card-assets/ {
|
||||
alias /srv/uniare-card-images/;
|
||||
autoindex off;
|
||||
expires 1h;
|
||||
add_header Cache-Control "public, max-age=3600";
|
||||
add_header X-Content-Type-Options "nosniff";
|
||||
}
|
||||
|
||||
location ~* \.(?:css|js|jpg|jpeg|gif|png|svg|ico|webp|woff2?)$ {
|
||||
expires 7d;
|
||||
add_header Cache-Control "public, max-age=604800, immutable";
|
||||
try_files $uri =404;
|
||||
}
|
||||
}
|
||||
342
docs/deploy.md
Normal file
342
docs/deploy.md
Normal file
@@ -0,0 +1,342 @@
|
||||
# UGREEN NAS Docker 배포 안내
|
||||
|
||||
## 문서 목적
|
||||
|
||||
이 문서는 터미널과 Docker에 익숙하지 않은 사용자가 Play Uniare의 테스트
|
||||
버전과 운영 버전을 UGREEN NAS에서 실행하고 업데이트하는 절차를 설명한다.
|
||||
|
||||
현재 프로젝트는 Vue 정적 프런트엔드만 포함한다. 실시간 서버가 추가되면
|
||||
`compose.yaml`에 백엔드 서비스와 상태 저장소를 함께 추가한다.
|
||||
|
||||
## 배포 방식
|
||||
|
||||
```text
|
||||
사용자 브라우저
|
||||
↓
|
||||
NAS 포트 또는 리버스 프록시
|
||||
↓
|
||||
Play Uniare 컨테이너의 Nginx
|
||||
├── / → Vue 빌드 결과
|
||||
└── /card-assets/ → 기존 Uniare 카드 이미지
|
||||
```
|
||||
|
||||
Docker 이미지를 만들 때 Node.js로 Vue를 빌드하고, 실행할 때는 가벼운
|
||||
Nginx가 빌드 결과를 제공한다.
|
||||
|
||||
카드 이미지는 다음 기존 폴더를 복사하지 않고 읽기 전용으로 연결한다.
|
||||
|
||||
```text
|
||||
/volume1/docker/projects/apps/uniare/config/www/images
|
||||
```
|
||||
|
||||
## NAS에서 처음 한 번 준비하기
|
||||
|
||||
### 1. Docker 앱 설치
|
||||
|
||||
UGREEN NAS의 앱 센터에서 Docker 또는 Container Manager에 해당하는 앱을
|
||||
설치한다. 모델과 UGOS 버전에 따라 앱 이름이 다를 수 있다.
|
||||
|
||||
### 2. 프로젝트 저장 폴더 준비
|
||||
|
||||
NAS 공유 폴더에 다음과 같은 폴더를 만든다.
|
||||
|
||||
```text
|
||||
/volume1/docker/projects/apps/
|
||||
├── uniare/
|
||||
│ └── config/
|
||||
│ └── www/
|
||||
│ ├── index.html
|
||||
│ └── images/
|
||||
└── playuniare/
|
||||
```
|
||||
|
||||
이 프로젝트의 NAS 경로는 다음으로 고정한다.
|
||||
|
||||
```text
|
||||
/volume1/docker/projects/apps/playuniare
|
||||
```
|
||||
|
||||
### 3. SSH 사용 여부 결정
|
||||
|
||||
UGREEN 관리 화면에서 Docker Compose 프로젝트를 직접 등록할 수 있다면 GUI를
|
||||
우선 사용한다. GUI에서 Git 저장소를 직접 받을 수 없거나 업데이트가 어려운
|
||||
경우에만 SSH 터미널을 사용한다.
|
||||
|
||||
SSH를 활성화할 때는 관리자 화면에서 포트와 계정을 확인한다. NAS 비밀번호를
|
||||
문서, `.env`, Git 저장소에 기록하지 않는다.
|
||||
|
||||
## 테스트 버전 실행
|
||||
|
||||
테스트 버전은 외부 도메인에 연결하기 전에 NAS 내부에서 확인하는 용도다.
|
||||
|
||||
### 1. NAS에 프로젝트 받기
|
||||
|
||||
NAS 터미널에서 프로젝트 저장 폴더로 이동한다.
|
||||
|
||||
```bash
|
||||
cd /volume1/docker/projects/apps
|
||||
```
|
||||
|
||||
처음 한 번만 저장소를 복제한다.
|
||||
|
||||
```bash
|
||||
git clone https://git.sori.studio/zenn/playuniare.sori.studio.git playuniare
|
||||
cd playuniare
|
||||
```
|
||||
|
||||
비공개 저장소가 인증을 요구하면 비밀번호 대신 Git 서버에서 발급한 접근
|
||||
토큰을 사용하는 것을 권장한다. 토큰을 명령문이나 문서에 저장하지 않는다.
|
||||
|
||||
### 2. 테스트 컨테이너 빌드 및 실행
|
||||
|
||||
```bash
|
||||
docker compose \
|
||||
-f compose.test.yaml \
|
||||
-f compose.test.nas.yaml \
|
||||
up -d --build
|
||||
```
|
||||
|
||||
이 명령은 다음 작업을 한 번에 수행한다.
|
||||
|
||||
- 최신 소스 코드로 Docker 이미지 생성
|
||||
- 테스트 컨테이너 실행
|
||||
- NAS의 18080 포트와 컨테이너 연결
|
||||
|
||||
### 3. 브라우저에서 확인
|
||||
|
||||
NAS의 내부 IP가 `192.168.0.20`이라면 다음 주소로 접속한다.
|
||||
|
||||
```text
|
||||
http://192.168.0.20:18080
|
||||
```
|
||||
|
||||
실제 NAS IP는 공유기 또는 UGREEN 관리 화면에서 확인한다.
|
||||
|
||||
### 4. 테스트 로그 확인
|
||||
|
||||
화면이 열리지 않으면 다음을 실행한다.
|
||||
|
||||
```bash
|
||||
docker compose \
|
||||
-f compose.test.yaml \
|
||||
-f compose.test.nas.yaml \
|
||||
ps
|
||||
|
||||
docker compose \
|
||||
-f compose.test.yaml \
|
||||
-f compose.test.nas.yaml \
|
||||
logs --tail=100
|
||||
```
|
||||
|
||||
`Up` 또는 `running` 상태인지 확인한다.
|
||||
|
||||
### 5. 테스트 버전 중지
|
||||
|
||||
```bash
|
||||
docker compose \
|
||||
-f compose.test.yaml \
|
||||
-f compose.test.nas.yaml \
|
||||
down
|
||||
```
|
||||
|
||||
컨테이너는 제거되지만 소스 코드와 Docker 이미지는 남아 있다.
|
||||
|
||||
## 운영 버전 최초 배포
|
||||
|
||||
### 1. 배포 전 로컬 검증
|
||||
|
||||
개발 컴퓨터에서 다음 명령이 성공해야 한다.
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm run build
|
||||
```
|
||||
|
||||
그 후 변경 사항을 커밋하고 원격 저장소에 푸시한다.
|
||||
|
||||
### 2. NAS에서 운영 컨테이너 실행
|
||||
|
||||
NAS 프로젝트 폴더에서 실행한다.
|
||||
|
||||
```bash
|
||||
cd /volume1/docker/projects/apps/playuniare
|
||||
git pull
|
||||
docker compose \
|
||||
-f compose.yaml \
|
||||
-f compose.nas.yaml \
|
||||
up -d --build
|
||||
```
|
||||
|
||||
운영 컨테이너는 NAS의 18081 포트를 사용한다.
|
||||
|
||||
```text
|
||||
http://NAS_IP:18081
|
||||
```
|
||||
|
||||
### 3. 도메인 연결
|
||||
|
||||
UGREEN NAS 또는 사용 중인 리버스 프록시에서 다음과 같이 연결한다.
|
||||
|
||||
```text
|
||||
외부 도메인: https://playuniare.sori.studio
|
||||
내부 대상: http://127.0.0.1:18081
|
||||
```
|
||||
|
||||
- 외부 접속은 HTTPS를 사용한다.
|
||||
- 공유기에서 컨테이너 포트를 그대로 외부에 노출하기보다 리버스 프록시를
|
||||
사용한다.
|
||||
- 인증서 자동 갱신이 설정되어 있는지 확인한다.
|
||||
|
||||
### 4. 카드 이미지 연결 확인
|
||||
|
||||
브라우저에서 실제 카드 파일 하나를 직접 연다.
|
||||
|
||||
```text
|
||||
http://NAS_IP:18081/card-assets/ua03/EX01BT_HTR-2-001.png
|
||||
```
|
||||
|
||||
이미지가 보이면 읽기 전용 마운트와 Nginx 경로가 정상이다. 파일명이 실제로
|
||||
존재하는 카드인지 먼저 확인한다.
|
||||
|
||||
이미지가 보이지 않으면 NAS 터미널에서 원본 경로를 확인한다.
|
||||
|
||||
```bash
|
||||
ls /volume1/docker/projects/apps/uniare/config/www/images
|
||||
```
|
||||
|
||||
시리즈 폴더가 보여야 한다. 다음 명령으로 컨테이너 내부 연결도 확인한다.
|
||||
|
||||
```bash
|
||||
docker exec playuniare-web ls /srv/uniare-card-images
|
||||
```
|
||||
|
||||
이 경로 역시 같은 시리즈 폴더를 보여야 한다.
|
||||
|
||||
## 새 버전 업데이트
|
||||
|
||||
### 안전한 권장 순서
|
||||
|
||||
1. 개발 컴퓨터에서 `npm run build`를 실행한다.
|
||||
2. 변경 내용을 Git에 커밋하고 푸시한다.
|
||||
3. NAS에서 테스트 버전을 먼저 업데이트한다.
|
||||
4. 테스트 주소에서 주요 기능을 확인한다.
|
||||
5. 문제가 없으면 운영 버전을 업데이트한다.
|
||||
|
||||
### 테스트 버전 업데이트
|
||||
|
||||
```bash
|
||||
cd /volume1/docker/projects/apps/playuniare
|
||||
git pull
|
||||
docker compose \
|
||||
-f compose.test.yaml \
|
||||
-f compose.test.nas.yaml \
|
||||
up -d --build
|
||||
```
|
||||
|
||||
브라우저에서 `http://NAS_IP:18080`을 확인한다.
|
||||
|
||||
### 운영 버전 업데이트
|
||||
|
||||
```bash
|
||||
cd /volume1/docker/projects/apps/playuniare
|
||||
git pull
|
||||
docker compose \
|
||||
-f compose.yaml \
|
||||
-f compose.nas.yaml \
|
||||
up -d --build
|
||||
```
|
||||
|
||||
브라우저에서 실제 도메인을 확인한다.
|
||||
|
||||
### 사용하지 않는 Docker 이미지 정리
|
||||
|
||||
업데이트를 여러 번 하면 이전 이미지가 NAS 용량을 차지할 수 있다.
|
||||
|
||||
```bash
|
||||
docker image prune
|
||||
```
|
||||
|
||||
삭제 대상 확인 질문이 나오면 내용을 읽고 `y`를 입력한다. `-a` 옵션이나
|
||||
볼륨 삭제 명령은 데이터 유실 가능성이 있으므로 이 문서에서는 사용하지 않는다.
|
||||
|
||||
## 이전 버전으로 되돌리기
|
||||
|
||||
문제가 생기면 먼저 현재 커밋을 확인한다.
|
||||
|
||||
```bash
|
||||
git log --oneline -10
|
||||
```
|
||||
|
||||
정상 동작했던 커밋 식별자를 확인한 뒤 다음처럼 전환한다.
|
||||
|
||||
```bash
|
||||
git checkout 정상커밋식별자
|
||||
docker compose \
|
||||
-f compose.yaml \
|
||||
-f compose.nas.yaml \
|
||||
up -d --build
|
||||
```
|
||||
|
||||
복구가 끝난 뒤 개발 컴퓨터에서 문제를 수정하고 정상 브랜치에 새 커밋을
|
||||
만든다. NAS에서 임의로 소스 파일을 직접 수정하지 않는다.
|
||||
|
||||
다시 최신 `main`으로 돌아갈 때:
|
||||
|
||||
```bash
|
||||
git checkout main
|
||||
git pull
|
||||
docker compose \
|
||||
-f compose.yaml \
|
||||
-f compose.nas.yaml \
|
||||
up -d --build
|
||||
```
|
||||
|
||||
## 운영 점검 명령
|
||||
|
||||
```bash
|
||||
docker compose -f compose.yaml -f compose.nas.yaml ps
|
||||
docker compose -f compose.yaml -f compose.nas.yaml logs --tail=100
|
||||
docker compose -f compose.yaml -f compose.nas.yaml restart
|
||||
```
|
||||
|
||||
- `ps`: 실행 상태 확인
|
||||
- `logs`: 최근 오류 확인
|
||||
- `restart`: 같은 이미지로 컨테이너 재시작
|
||||
|
||||
## 배포 체크리스트
|
||||
|
||||
- [ ] 로컬 `npm run build` 성공
|
||||
- [ ] 관련 문서 업데이트
|
||||
- [ ] Git 커밋과 푸시 완료
|
||||
- [ ] 테스트 컨테이너 재빌드
|
||||
- [ ] 테스트 URL 주요 기능 확인
|
||||
- [ ] 운영 컨테이너 재빌드
|
||||
- [ ] 실제 도메인 HTTPS 접속 확인
|
||||
- [ ] `/card-assets/<series>/<fileName>` 이미지 접속 확인
|
||||
- [ ] 카드 이미지 마운트가 읽기 전용인지 확인
|
||||
- [ ] 브라우저 새로고침과 직접 URL 접근 확인
|
||||
- [ ] 오류 로그 확인
|
||||
- [ ] `.env`, 토큰, 비밀번호가 Git에 없는지 확인
|
||||
|
||||
## 향후 실시간 서버 추가 시
|
||||
|
||||
프런트엔드만 있을 때는 Nginx 컨테이너 하나면 충분하다. Socket.IO 서버가
|
||||
추가되면 다음 항목이 필요하다.
|
||||
|
||||
- Node.js 서버 컨테이너
|
||||
- WebSocket을 지원하는 리버스 프록시 설정
|
||||
- 프런트엔드와 서버의 환경변수
|
||||
- 방 상태 백업 또는 Redis 도입 여부
|
||||
- 컨테이너 재시작 시 방이 종료된다는 안내
|
||||
|
||||
이 구조가 변경되면 반드시 이 문서와 `docs/history.md`를 함께 수정한다.
|
||||
|
||||
## NAS 전용 Compose 파일을 분리한 이유
|
||||
|
||||
- `compose.yaml`: 어느 컴퓨터에서도 검사하고 빌드할 수 있는 공통 운영 설정
|
||||
- `compose.nas.yaml`: NAS에만 존재하는 카드 이미지 절대 경로 연결
|
||||
- `compose.test.yaml`: 공통 테스트 설정
|
||||
- `compose.test.nas.yaml`: 테스트 컨테이너의 NAS 카드 이미지 연결
|
||||
|
||||
개발 Mac에는 `/volume1/...` 경로가 없으므로 NAS 전용 파일을 사용하지 않는다.
|
||||
NAS에서는 반드시 공통 파일과 NAS 전용 파일을 함께 지정한다.
|
||||
132
docs/development.md
Normal file
132
docs/development.md
Normal file
@@ -0,0 +1,132 @@
|
||||
# 로컬 개발과 테스트 실행 안내
|
||||
|
||||
## 처음 한 번만 준비하기
|
||||
|
||||
### 1. Node.js 확인
|
||||
|
||||
터미널에서 실행한다.
|
||||
|
||||
```bash
|
||||
node --version
|
||||
npm --version
|
||||
```
|
||||
|
||||
두 명령 모두 버전 번호가 출력되어야 한다. 명령을 찾을 수 없다는 메시지가
|
||||
나오면 Node.js LTS 버전을 먼저 설치한다.
|
||||
|
||||
### 2. 프로젝트 폴더로 이동
|
||||
|
||||
```bash
|
||||
cd /Users/bicute/Desktop/dxp6800pro/uniare-simulator
|
||||
```
|
||||
|
||||
### 3. 패키지 설치
|
||||
|
||||
프로젝트를 처음 받았거나 `package.json`이 변경되었을 때 실행한다.
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
`node_modules`는 자동 생성되며 Git에 올리지 않는다.
|
||||
|
||||
## 개발 서버 실행
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
터미널에 다음과 비슷한 주소가 표시된다.
|
||||
|
||||
```text
|
||||
Local: http://localhost:5173/
|
||||
```
|
||||
|
||||
해당 주소를 브라우저에서 연다. 개발 중 코드를 저장하면 화면이 자동으로
|
||||
갱신된다.
|
||||
|
||||
개발 서버를 종료하려면 실행 중인 터미널에서 `Control + C`를 누른다.
|
||||
|
||||
## 같은 네트워크의 다른 기기에서 테스트
|
||||
|
||||
```bash
|
||||
npm run dev -- --host
|
||||
```
|
||||
|
||||
터미널의 `Network` 주소를 같은 공유기에 연결된 휴대전화나 태블릿에서 연다.
|
||||
macOS 방화벽이 연결 허용 여부를 물으면 개발 중에만 허용한다.
|
||||
|
||||
## 배포용 빌드 확인
|
||||
|
||||
코드 변경을 완료하기 전에 반드시 실행한다.
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
성공하면 `dist/` 폴더가 생성된다. 오류가 발생하면 배포하지 말고 오류를 먼저
|
||||
수정한다.
|
||||
|
||||
빌드 결과를 로컬에서 확인하려면 다음을 실행한다.
|
||||
|
||||
```bash
|
||||
npm run preview
|
||||
```
|
||||
|
||||
개발 서버와 마찬가지로 터미널에 표시된 주소로 접속한다.
|
||||
|
||||
## 환경변수 사용
|
||||
|
||||
로컬 전용 값은 프로젝트 루트의 `.env.local`에 작성한다.
|
||||
|
||||
```text
|
||||
VITE_CARD_ASSET_BASE_URL=http://127.0.0.1:5500/images
|
||||
VITE_SOCKET_SERVER_URL=http://localhost:3000
|
||||
```
|
||||
|
||||
- `VITE_`로 시작하는 값은 브라우저에 노출될 수 있다.
|
||||
- 비밀번호, 개인키, NAS 관리자 계정은 넣지 않는다.
|
||||
- `.env.local`은 Git에 커밋하지 않는다.
|
||||
|
||||
## 일반적인 작업 순서
|
||||
|
||||
```bash
|
||||
git pull
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
작업이 끝난 뒤:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
git status
|
||||
```
|
||||
|
||||
변경 문서를 확인한 후 커밋하고 푸시한다.
|
||||
|
||||
## 자주 발생하는 문제
|
||||
|
||||
### 포트가 이미 사용 중이라고 나오는 경우
|
||||
|
||||
Vite가 다른 포트를 자동으로 제시할 수 있다. 터미널에 새로 표시된 주소로
|
||||
접속한다. 반드시 5173 포트를 고정할 필요는 없다.
|
||||
|
||||
### 패키지 오류가 발생하는 경우
|
||||
|
||||
먼저 다음을 다시 실행한다.
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm run build
|
||||
```
|
||||
|
||||
문제가 지속되면 오류 전문과 Node.js 버전을 기록하고 임의로 대규모 패키지
|
||||
업데이트를 하지 않는다.
|
||||
|
||||
### 카드 이미지가 보이지 않는 경우
|
||||
|
||||
- 덱의 `series` 값이 올바른지 확인한다.
|
||||
- 이미지 파일명이 실제 파일과 같은지 확인한다.
|
||||
- `VITE_CARD_ASSET_BASE_URL` 값 끝에 불필요한 경로가 없는지 확인한다.
|
||||
- 브라우저 개발자 도구의 Network 탭에서 404 또는 CORS 오류를 확인한다.
|
||||
38
docs/history.md
Normal file
38
docs/history.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# 구조 및 의사결정 이력
|
||||
|
||||
## 2026-06-19: 독립 Vue 프로젝트로 시작
|
||||
|
||||
- 기존 UA Deck Builder와 별도 Git 저장소로 분리했다.
|
||||
- 덱 빌더는 카드 선택과 덱 데이터 제공을 담당한다.
|
||||
- Play Uniare는 카드 조작과 실시간 세션을 담당한다.
|
||||
- 카드 이미지의 대규모 복제를 피하기 위해 기존 카드 자산 도메인을 참조한다.
|
||||
|
||||
## 2026-06-19: Vue 3, Vite, Tailwind CSS 채택
|
||||
|
||||
- 사용자가 직접 유지보수할 수 있는 Vue 3를 선택했다.
|
||||
- 정적 빌드와 빠른 개발 서버를 위해 Vite를 사용한다.
|
||||
- 일관된 UI 작성을 위해 Tailwind CSS를 기본 스타일 방식으로 정했다.
|
||||
|
||||
## 2026-06-19: 카드 소유자만 조작하는 권한 모델
|
||||
|
||||
- 플레이어는 자신이 소유한 카드만 조작한다.
|
||||
- 전투나 효과로 내 카드가 제거되어도 내 카드 소유자가 직접 정리한다.
|
||||
- 이 원칙으로 동시에 같은 카드를 수정하는 충돌을 줄인다.
|
||||
- 온라인 모드에서는 서버가 소유권을 최종 검증한다.
|
||||
|
||||
## 2026-06-19: Docker 기반 NAS 배포
|
||||
|
||||
- UGREEN NAS에서 재현 가능한 배포를 위해 Docker Compose를 사용한다.
|
||||
- Vue 빌드에는 Node.js를 사용하고 실행 이미지는 Nginx를 사용한다.
|
||||
- 테스트와 운영 컨테이너를 서로 다른 포트로 분리한다.
|
||||
|
||||
## 2026-06-19: 기존 카드 이미지 읽기 전용 공유
|
||||
|
||||
- 기존 Uniare의 실제 웹 루트는
|
||||
`/volume1/docker/projects/apps/uniare/config/www`로 확인했다.
|
||||
- 카드 원본은 해당 웹 루트의 `images` 폴더를 사용한다.
|
||||
- 새 프로젝트의 NAS 위치는
|
||||
`/volume1/docker/projects/apps/playuniare`로 정했다.
|
||||
- 이미지 중복 저장을 막기 위해 Docker bind mount를 사용한다.
|
||||
- 원본 보호를 위해 컨테이너에는 읽기 전용으로 연결한다.
|
||||
- 브라우저에는 같은 출처의 `/card-assets` 경로로 제공한다.
|
||||
53
docs/map.md
Normal file
53
docs/map.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# 화면 및 파일 구조
|
||||
|
||||
## 현재 화면
|
||||
|
||||
### `/`
|
||||
|
||||
- 파일: `src/App.vue`
|
||||
- 역할: 프로젝트 준비 상태를 보여주는 초기 랜딩 화면
|
||||
- 현재 기능: 제품 목표 안내
|
||||
- 다음 변경: 로비 화면과 게임 화면을 Vue Router로 분리
|
||||
- 문서 제목과 기본 언어: `index.html`의 `Play Uniare`, 한국어
|
||||
|
||||
## 예정 화면
|
||||
|
||||
### `/`
|
||||
|
||||
- 로컬 연습 시작
|
||||
- 방 만들기
|
||||
- 방 참가
|
||||
- 덱 쿼리 유효성 안내
|
||||
|
||||
### `/room/:roomCode`
|
||||
|
||||
- 상대 필드
|
||||
- 공용 게임 보드
|
||||
- 내 필드와 손패
|
||||
- 덱·무덤·공개 영역
|
||||
- 카드 조작 메뉴
|
||||
- 연결 상태와 재접속 안내
|
||||
|
||||
## 예정 디렉터리 구조
|
||||
|
||||
```text
|
||||
src/
|
||||
├── components/
|
||||
│ ├── cards/
|
||||
│ ├── game-board/
|
||||
│ └── room/
|
||||
├── composables/
|
||||
├── services/
|
||||
├── stores/
|
||||
├── types/
|
||||
├── utils/
|
||||
└── views/
|
||||
```
|
||||
|
||||
- `components`: 화면에서 재사용하는 UI
|
||||
- `composables`: Vue 반응형 조합 로직
|
||||
- `services`: 쿼리 파싱, API, Socket.IO 연결
|
||||
- `stores`: Pinia 기반 게임·방·사용자 상태
|
||||
- `types`: JSDoc 타입 정의
|
||||
- `utils`: Vue에 의존하지 않는 순수 함수
|
||||
- `views`: Router 단위 화면
|
||||
106
docs/spec.md
Normal file
106
docs/spec.md
Normal file
@@ -0,0 +1,106 @@
|
||||
# 프로젝트 기술 명세
|
||||
|
||||
## 프로젝트 개요
|
||||
|
||||
- 프로젝트명: Play Uniare
|
||||
- 목적: Union Arena 덱을 불러와 카드 조작 중심의 로컬·온라인 플레이 제공
|
||||
- 현재 단계: 프로젝트 기반 구성
|
||||
- 프런트엔드: Vue 3, Vite, Tailwind CSS
|
||||
- 배포 대상: UGREEN NAS의 Docker 환경
|
||||
|
||||
## 제품 범위
|
||||
|
||||
### 포함
|
||||
|
||||
- 기존 덱 빌더 쿼리에서 덱 불러오기
|
||||
- 카드 수량만큼 독립 인스턴스 생성
|
||||
- 덱 셔플과 드로우
|
||||
- 카드 드래그 이동
|
||||
- 카드 앞면·뒷면 전환
|
||||
- 카드 탭·언탭
|
||||
- 손패, 필드, 덱, 무덤, 공개 영역
|
||||
- 덱 위·아래 또는 무덤으로 카드 이동
|
||||
- 덱 위에서 여러 장을 공개한 후 목적지 지정
|
||||
- 혼자 연습하는 로컬 모드
|
||||
- 방 코드 기반 2인 세션
|
||||
- 각 플레이어가 자신의 카드만 조작하는 권한 모델
|
||||
|
||||
### 제외
|
||||
|
||||
- 카드 효과와 게임 규칙 자동 판정
|
||||
- 음성·텍스트 채팅
|
||||
- 랭킹, 매칭, 대회 운영
|
||||
- 초기 단계의 회원가입
|
||||
|
||||
## 덱 입력
|
||||
|
||||
초기에는 기존 덱 빌더 형식을 호환한다.
|
||||
|
||||
```text
|
||||
?series=ua03&seriesName=HUNTER%C3%97HUNTER&cards=<encoded-json>
|
||||
```
|
||||
|
||||
수신 후 다음 절차를 적용한다.
|
||||
|
||||
1. 쿼리 길이와 필수 필드를 검사한다.
|
||||
2. JSON 디코딩 오류를 처리한다.
|
||||
3. `series`, `imgSrc`, `Number`, `count`를 검증한다.
|
||||
4. 카드 수량만큼 고유한 게임 카드 인스턴스를 만든다.
|
||||
5. 이미지 주소는 환경변수 기반 카드 자산 원본과 조합한다.
|
||||
|
||||
장기적으로는 축약된 덱 코드 또는 서버의 `deckId` 방식으로 전환한다.
|
||||
|
||||
## 권한 모델
|
||||
|
||||
- 플레이어는 자기 카드만 이동·뒤집기·탭·정리할 수 있다.
|
||||
- 상대 효과로 내 카드가 제거될 때에도 내가 직접 내 카드를 이동한다.
|
||||
- 서버는 모든 카드 조작에서 요청 사용자와 카드 소유자를 비교한다.
|
||||
- 상대 손패에는 장수만 공개하고 카드 식별 정보는 공개하지 않는다.
|
||||
|
||||
## 화면 좌표
|
||||
|
||||
- 배경 이미지는 카드 배치 가이드로 사용한다.
|
||||
- 실제 영역은 배경 위의 투명한 드롭 영역으로 정의한다.
|
||||
- 자유 배치 좌표는 보드 너비·높이 기준 0부터 1 사이 값으로 저장한다.
|
||||
- 창 크기가 변해도 같은 상대 위치를 유지해야 한다.
|
||||
|
||||
## 상태 저장
|
||||
|
||||
- 로컬 모드: 브라우저 메모리, 추후 복구가 필요하면 localStorage 검토
|
||||
- 온라인 모드: 서버가 방 상태의 최종 권위를 가진다.
|
||||
- 초기 서버: Node.js와 Socket.IO, 메모리 기반 방 상태
|
||||
- 운영 안정화 단계: Redis 또는 데이터베이스 저장 검토
|
||||
|
||||
## 환경변수 예정
|
||||
|
||||
```text
|
||||
VITE_CARD_ASSET_BASE_URL=https://uniare.sori.studio/images
|
||||
VITE_SOCKET_SERVER_URL=https://playuniare.sori.studio
|
||||
```
|
||||
|
||||
실제 운영 값은 `.env`에 두고 Git에 커밋하지 않는다.
|
||||
|
||||
## NAS 카드 이미지 공유
|
||||
|
||||
운영 NAS에서는 카드 이미지를 새 프로젝트에 복사하지 않는다. 기존 Uniare
|
||||
컨테이너가 사용하는 웹 루트의 이미지 폴더를 읽기 전용으로 공유한다.
|
||||
|
||||
```text
|
||||
기존 Uniare 웹 루트:
|
||||
/volume1/docker/projects/apps/uniare/config/www
|
||||
|
||||
카드 이미지 원본:
|
||||
/volume1/docker/projects/apps/uniare/config/www/images
|
||||
|
||||
Play Uniare 컨테이너 내부:
|
||||
/srv/uniare-card-images
|
||||
|
||||
브라우저 공개 경로:
|
||||
/card-assets
|
||||
```
|
||||
|
||||
- `config/www`는 기존 `index.html`이 존재하는 실제 웹 루트다.
|
||||
- Docker bind mount는 `images` 폴더만 읽기 전용으로 연결한다.
|
||||
- Play Uniare는 기존 카드 이미지를 읽을 수 있지만 수정하거나 삭제할 수 없다.
|
||||
- 카드 이미지 URL은 `/card-assets/<series>/<fileName>` 형식을 사용한다.
|
||||
- 같은 Play Uniare 도메인으로 제공되므로 별도의 CORS 설정이 필요하지 않다.
|
||||
58
docs/todo.md
Normal file
58
docs/todo.md
Normal file
@@ -0,0 +1,58 @@
|
||||
# 개발 할 일
|
||||
|
||||
## 기반 작업
|
||||
|
||||
- [x] Vue 3와 Vite 프로젝트 생성
|
||||
- [x] Tailwind CSS 설정
|
||||
- [x] Docker 정적 배포 기반 작성
|
||||
- [x] 개발·배포·AI 작업 문서 작성
|
||||
- [ ] ESLint와 Prettier 규칙 확정
|
||||
- [ ] Vue Router 설치와 화면 구조 생성
|
||||
- [ ] Pinia 설치와 상태 구조 생성
|
||||
- [ ] 테스트 도구 도입 범위 결정
|
||||
|
||||
## 1단계: 덱 불러오기와 로컬 플레이
|
||||
|
||||
- [ ] 기존 덱 쿼리 파서 작성
|
||||
- [ ] 외부 입력 검증 스키마 작성
|
||||
- [x] NAS 카드 이미지 읽기 전용 공유 경로 구성
|
||||
- [ ] 카드 이미지 원본 환경변수를 프런트엔드 코드에 연결
|
||||
- [ ] 카드 인스턴스 생성
|
||||
- [ ] 게임 보드 배경과 영역 정의
|
||||
- [ ] 덱 셔플과 드로우
|
||||
- [ ] 카드 드래그 이동
|
||||
- [ ] 앞면·뒷면 전환
|
||||
- [ ] 탭·언탭
|
||||
- [ ] 덱·손패·필드·무덤 이동
|
||||
- [ ] 덱 위 N장 공개와 정리
|
||||
- [ ] 로컬 상태 초기화
|
||||
|
||||
## 2단계: 온라인 2인 세션
|
||||
|
||||
- [ ] Node.js 실시간 서버 구성
|
||||
- [ ] Socket.IO 연결
|
||||
- [ ] 방 만들기와 방 코드 참가
|
||||
- [ ] 좌석과 플레이어 소유권
|
||||
- [ ] 카드 조작 권한 서버 검증
|
||||
- [ ] 상대 손패 비공개 전송
|
||||
- [ ] 상태 스냅샷과 재접속
|
||||
- [ ] 연결 종료 처리
|
||||
- [ ] NAS WebSocket 프록시 배포
|
||||
|
||||
## 3단계: 안정화
|
||||
|
||||
- [ ] 모바일·태블릿 Pointer Events 검증
|
||||
- [ ] 카드 겹침과 z-index 조작 UX
|
||||
- [ ] 잘못된 덱 입력 오류 화면
|
||||
- [ ] 이미지 로딩 실패 폴백
|
||||
- [ ] 접근성 키보드 조작 범위 검토
|
||||
- [ ] 서버 재시작 시 방 종료 안내
|
||||
- [ ] Redis 또는 상태 저장 필요성 검토
|
||||
|
||||
## 확인이 필요한 정책
|
||||
|
||||
- [ ] 카드 뒷면 이미지와 플레이매트 자산 확정
|
||||
- [ ] 한 방의 최대 플레이어와 관전자 정책
|
||||
- [ ] 방 유지 시간
|
||||
- [ ] 사용하지 않는 방 자동 삭제 시간
|
||||
- [ ] 덱 빌더와 시뮬레이터 사이의 최종 전달 형식
|
||||
13
docs/update.md
Normal file
13
docs/update.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# 버그 수정 및 작업 기록
|
||||
|
||||
## 2026-06-19
|
||||
|
||||
- Vue 3와 Vite 기본 프로젝트를 생성했다.
|
||||
- Tailwind CSS Vite 플러그인을 적용했다.
|
||||
- 기본 샘플 화면을 프로젝트 안내 화면으로 교체했다.
|
||||
- UGREEN NAS Docker 테스트·운영 배포 문서를 작성했다.
|
||||
- AI 작업 지침과 초기 명세, 화면 구조, 할 일, 의사결정 문서를 추가했다.
|
||||
- Vue 프로덕션 빌드, Docker Compose 설정, Docker 이미지 빌드와 Nginx
|
||||
헬스체크를 검증했다.
|
||||
- NAS의 기존 `uniare/config/www/images`를 읽기 전용으로 공유하는 Compose
|
||||
설정과 `/card-assets` Nginx 경로를 추가했다.
|
||||
17
index.html
Normal file
17
index.html
Normal file
@@ -0,0 +1,17 @@
|
||||
<!doctype html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Union Arena 덱을 불러와 연습하고 함께 플레이하는 웹 카드 시뮬레이터"
|
||||
/>
|
||||
<title>Play Uniare</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
1464
package-lock.json
generated
Normal file
1464
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
20
package.json
Normal file
20
package.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "uniare-simulator",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"vue": "^3.5.34"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/vite": "^4.3.1",
|
||||
"@vitejs/plugin-vue": "^6.0.6",
|
||||
"tailwindcss": "^4.3.1",
|
||||
"vite": "^8.0.12"
|
||||
}
|
||||
}
|
||||
1
public/favicon.svg
Normal file
1
public/favicon.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 9.3 KiB |
24
public/icons.svg
Normal file
24
public/icons.svg
Normal file
@@ -0,0 +1,24 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg">
|
||||
<symbol id="bluesky-icon" viewBox="0 0 16 17">
|
||||
<g clip-path="url(#bluesky-clip)"><path fill="#08060d" d="M7.75 7.735c-.693-1.348-2.58-3.86-4.334-5.097-1.68-1.187-2.32-.981-2.74-.79C.188 2.065.1 2.812.1 3.251s.241 3.602.398 4.13c.52 1.744 2.367 2.333 4.07 2.145-2.495.37-4.71 1.278-1.805 4.512 3.196 3.309 4.38-.71 4.987-2.746.608 2.036 1.307 5.91 4.93 2.746 2.72-2.746.747-4.143-1.747-4.512 1.702.189 3.55-.4 4.07-2.145.156-.528.397-3.691.397-4.13s-.088-1.186-.575-1.406c-.42-.19-1.06-.395-2.741.79-1.755 1.24-3.64 3.752-4.334 5.099"/></g>
|
||||
<defs><clipPath id="bluesky-clip"><path fill="#fff" d="M.1.85h15.3v15.3H.1z"/></clipPath></defs>
|
||||
</symbol>
|
||||
<symbol id="discord-icon" viewBox="0 0 20 19">
|
||||
<path fill="#08060d" d="M16.224 3.768a14.5 14.5 0 0 0-3.67-1.153c-.158.286-.343.67-.47.976a13.5 13.5 0 0 0-4.067 0c-.128-.306-.317-.69-.476-.976A14.4 14.4 0 0 0 3.868 3.77C1.546 7.28.916 10.703 1.231 14.077a14.7 14.7 0 0 0 4.5 2.306q.545-.748.965-1.587a9.5 9.5 0 0 1-1.518-.74q.191-.14.372-.293c2.927 1.369 6.107 1.369 8.999 0q.183.152.372.294-.723.437-1.52.74.418.838.963 1.588a14.6 14.6 0 0 0 4.504-2.308c.37-3.911-.63-7.302-2.644-10.309m-9.13 8.234c-.878 0-1.599-.82-1.599-1.82 0-.998.705-1.82 1.6-1.82.894 0 1.614.82 1.599 1.82.001 1-.705 1.82-1.6 1.82m5.91 0c-.878 0-1.599-.82-1.599-1.82 0-.998.705-1.82 1.6-1.82.893 0 1.614.82 1.599 1.82 0 1-.706 1.82-1.6 1.82"/>
|
||||
</symbol>
|
||||
<symbol id="documentation-icon" viewBox="0 0 21 20">
|
||||
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="m15.5 13.333 1.533 1.322c.645.555.967.833.967 1.178s-.322.623-.967 1.179L15.5 18.333m-3.333-5-1.534 1.322c-.644.555-.966.833-.966 1.178s.322.623.966 1.179l1.534 1.321"/>
|
||||
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M17.167 10.836v-4.32c0-1.41 0-2.117-.224-2.68-.359-.906-1.118-1.621-2.08-1.96-.599-.21-1.349-.21-2.848-.21-2.623 0-3.935 0-4.983.369-1.684.591-3.013 1.842-3.641 3.428C3 6.449 3 7.684 3 10.154v2.122c0 2.558 0 3.838.706 4.726q.306.383.713.671c.76.536 1.79.64 3.581.66"/>
|
||||
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M3 10a2.78 2.78 0 0 1 2.778-2.778c.555 0 1.209.097 1.748-.047.48-.129.854-.503.982-.982.145-.54.048-1.194.048-1.749a2.78 2.78 0 0 1 2.777-2.777"/>
|
||||
</symbol>
|
||||
<symbol id="github-icon" viewBox="0 0 19 19">
|
||||
<path fill="#08060d" fill-rule="evenodd" d="M9.356 1.85C5.05 1.85 1.57 5.356 1.57 9.694a7.84 7.84 0 0 0 5.324 7.44c.387.079.528-.168.528-.376 0-.182-.013-.805-.013-1.454-2.165.467-2.616-.935-2.616-.935-.349-.91-.864-1.143-.864-1.143-.71-.48.051-.48.051-.48.787.051 1.2.805 1.2.805.695 1.194 1.817.857 2.268.649.064-.507.27-.857.49-1.052-1.728-.182-3.545-.857-3.545-3.87 0-.857.31-1.558.8-2.104-.078-.195-.349-1 .077-2.078 0 0 .657-.208 2.14.805a7.5 7.5 0 0 1 1.946-.26c.657 0 1.328.092 1.946.26 1.483-1.013 2.14-.805 2.14-.805.426 1.078.155 1.883.078 2.078.502.546.799 1.247.799 2.104 0 3.013-1.818 3.675-3.558 3.87.284.247.528.714.528 1.454 0 1.052-.012 1.896-.012 2.156 0 .208.142.455.528.377a7.84 7.84 0 0 0 5.324-7.441c.013-4.338-3.48-7.844-7.773-7.844" clip-rule="evenodd"/>
|
||||
</symbol>
|
||||
<symbol id="social-icon" viewBox="0 0 20 20">
|
||||
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M12.5 6.667a4.167 4.167 0 1 0-8.334 0 4.167 4.167 0 0 0 8.334 0"/>
|
||||
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M2.5 16.667a5.833 5.833 0 0 1 8.75-5.053m3.837.474.513 1.035c.07.144.257.282.414.309l.93.155c.596.1.736.536.307.965l-.723.73a.64.64 0 0 0-.152.531l.207.903c.164.715-.213.991-.84.618l-.872-.52a.63.63 0 0 0-.577 0l-.872.52c-.624.373-1.003.094-.84-.618l.207-.903a.64.64 0 0 0-.152-.532l-.723-.729c-.426-.43-.289-.864.306-.964l.93-.156a.64.64 0 0 0 .412-.31l.513-1.034c.28-.562.735-.562 1.012 0"/>
|
||||
</symbol>
|
||||
<symbol id="x-icon" viewBox="0 0 19 19">
|
||||
<path fill="#08060d" fill-rule="evenodd" d="M1.893 1.98c.052.072 1.245 1.769 2.653 3.77l2.892 4.114c.183.261.333.48.333.486s-.068.089-.152.183l-.522.593-.765.867-3.597 4.087c-.375.426-.734.834-.798.905a1 1 0 0 0-.118.148c0 .01.236.017.664.017h.663l.729-.83c.4-.457.796-.906.879-.999a692 692 0 0 0 1.794-2.038c.034-.037.301-.34.594-.675l.551-.624.345-.392a7 7 0 0 1 .34-.374c.006 0 .93 1.306 2.052 2.903l2.084 2.965.045.063h2.275c1.87 0 2.273-.003 2.266-.021-.008-.02-1.098-1.572-3.894-5.547-2.013-2.862-2.28-3.246-2.273-3.266.008-.019.282-.332 2.085-2.38l2-2.274 1.567-1.782c.022-.028-.016-.03-.65-.03h-.674l-.3.342a871 871 0 0 1-1.782 2.025c-.067.075-.405.458-.75.852a100 100 0 0 1-.803.91c-.148.172-.299.344-.99 1.127-.304.343-.32.358-.345.327-.015-.019-.904-1.282-1.976-2.808L6.365 1.85H1.8zm1.782.91 8.078 11.294c.772 1.08 1.413 1.973 1.425 1.984.016.017.241.02 1.05.017l1.03-.004-2.694-3.766L7.796 5.75 5.722 2.852l-1.039-.004-1.039-.004z" clip-rule="evenodd"/>
|
||||
</symbol>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.9 KiB |
40
src/App.vue
Normal file
40
src/App.vue
Normal file
@@ -0,0 +1,40 @@
|
||||
<template>
|
||||
<main class="min-h-screen bg-slate-950 px-6 py-16 text-slate-100">
|
||||
<section class="mx-auto max-w-4xl">
|
||||
<p class="mb-4 text-sm font-semibold tracking-[0.24em] text-cyan-400">
|
||||
PLAY UNIARE
|
||||
</p>
|
||||
<h1 class="text-4xl font-black tracking-tight sm:text-6xl">
|
||||
Union Arena Simulator
|
||||
</h1>
|
||||
<p class="mt-6 max-w-2xl text-base leading-7 text-slate-400 sm:text-lg">
|
||||
덱 빌더에서 전달받은 카드로 혼자 연습하거나, 방을 만들어 상대와
|
||||
실시간으로 플레이하는 시뮬레이터를 준비하고 있습니다.
|
||||
</p>
|
||||
|
||||
<div class="mt-10 grid gap-4 sm:grid-cols-3">
|
||||
<article class="rounded-2xl border border-slate-800 bg-slate-900 p-5">
|
||||
<p class="text-sm font-bold text-cyan-300">01</p>
|
||||
<h2 class="mt-3 text-lg font-bold">덱 불러오기</h2>
|
||||
<p class="mt-2 text-sm leading-6 text-slate-400">
|
||||
기존 덱 빌더의 쿼리 데이터를 안전하게 변환합니다.
|
||||
</p>
|
||||
</article>
|
||||
<article class="rounded-2xl border border-slate-800 bg-slate-900 p-5">
|
||||
<p class="text-sm font-bold text-cyan-300">02</p>
|
||||
<h2 class="mt-3 text-lg font-bold">카드 조작</h2>
|
||||
<p class="mt-2 text-sm leading-6 text-slate-400">
|
||||
이동, 뒤집기, 탭과 덱·손패·무덤 이동을 제공합니다.
|
||||
</p>
|
||||
</article>
|
||||
<article class="rounded-2xl border border-slate-800 bg-slate-900 p-5">
|
||||
<p class="text-sm font-bold text-cyan-300">03</p>
|
||||
<h2 class="mt-3 text-lg font-bold">2인 세션</h2>
|
||||
<p class="mt-2 text-sm leading-6 text-slate-400">
|
||||
각 플레이어가 자신의 카드만 조작하도록 동기화합니다.
|
||||
</p>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</template>
|
||||
5
src/main.js
Normal file
5
src/main.js
Normal file
@@ -0,0 +1,5 @@
|
||||
import { createApp } from 'vue'
|
||||
import './style.css'
|
||||
import App from './App.vue'
|
||||
|
||||
createApp(App).mount('#app')
|
||||
34
src/style.css
Normal file
34
src/style.css
Normal file
@@ -0,0 +1,34 @@
|
||||
@import 'tailwindcss';
|
||||
|
||||
:root {
|
||||
font-family:
|
||||
Inter, Pretendard, 'Noto Sans KR', system-ui, -apple-system, BlinkMacSystemFont,
|
||||
'Segoe UI', sans-serif;
|
||||
color: #e2e8f0;
|
||||
background: #020617;
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html,
|
||||
body,
|
||||
#app {
|
||||
min-width: 320px;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
background: #020617;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
min-height: 100vh;
|
||||
}
|
||||
7
vite.config.js
Normal file
7
vite.config.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import tailwindcss from '@tailwindcss/vite'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [vue(), tailwindcss()],
|
||||
})
|
||||
Reference in New Issue
Block a user