add action
CI / Type-check & Build (push) Successful in 12m2s

This commit is contained in:
ysj7705 committed 2026-06-22 16:18:44 +09:00
1 parent 8bf1d1b928
commit b693cae7c3
6 files changed
+240 -30

No files matched your search

+32
View File
@@ -0,0 +1,32 @@
name: CI
on:
push:
branches:
- main
- master
pull_request:
branches:
- main
- master
jobs:
build:
name: Type-check & Build
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
cache: npm
- name: Install dependencies
run: npm ci
- name: Type-check and build
run: npm run build
+201 -23
View File
@@ -1,42 +1,220 @@
# tabletop-web # Tabletop Web
This template should help get you started developing with Vue 3 in Vite. 여러 대의 기기(태블릿·스마트폰)로 함께 즐기는 **보드게임 시뮬레이터**입니다.
현재 **원카드(One Card)** 를 지원하며, 보드판 1대 + 플레이어 2~4대가 P2P로 연결됩니다.
## Recommended IDE Setup ## 사용 기술
[VS Code](https://code.visualstudio.com/) + [Vue (Official)](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur). | 구분 | 기술 |
|------|------|
| 프레임워크 | [Vue 3](https://vuejs.org/) (Composition API, `<script setup>`) |
| 빌드 | [Vite 8](https://vite.dev/) |
| 언어 | TypeScript |
| 라우팅 | Vue Router 5 |
| 상태 관리 | Pinia |
| P2P 통신 | [PeerJS](https://peerjs.com/) (WebRTC DataChannel) |
| ICE | STUN + TURN (`openrelay.metered.ca` 기본, 자체 TURN 설정 가능) |
## Recommended Browser Setup ## 구성
- Chromium-based browsers (Chrome, Edge, Brave, etc.): ```mermaid
- [Vue.js devtools](https://chromewebstore.google.com/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd) flowchart TB
- [Turn on Custom Object Formatter in Chrome DevTools](http://bit.ly/object-formatters) subgraph devices["기기"]
- Firefox: Board["보드판 (태블릿/PC)"]
- [Vue.js devtools](https://addons.mozilla.org/en-US/firefox/addon/vue-js-devtools/) P1["플레이어 1"]
- [Turn on Custom Object Formatter in Firefox DevTools](https://fxdx.dev/firefox-devtools-custom-object-formatters/) P2["플레이어 2"]
P3["플레이어 3~4"]
end
## Type Support for `.vue` Imports in TS subgraph app["tabletop-web"]
Home["홈"]
Lobby["로비"]
BoardView["보드 화면"]
PlayerView["플레이어 화면"]
Engine["게임 엔진 (순수 TS)"]
end
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types. subgraph network["네트워크"]
PeerCloud["PeerJS Cloud (0.peerjs.com)"]
STUN["STUN / TURN"]
end
## Customize configuration Home --> Lobby
Lobby --> BoardView
Lobby --> PlayerView
BoardView --> Engine
PlayerView --> Engine
See [Vite Configuration Reference](https://vite.dev/config/). Board <-->|"호스트 (방 ID = Peer ID)"| PeerCloud
P1 & P2 & P3 <-->|"DataConnection"| Board
Board & P1 & P2 & P3 --- STUN
```
## Project Setup - **보드판**: 방 생성·자리 배치·게임 시작·룰 검증·덱/버린 더미·상대 패 뒷면·카드 뽑기 버튼
- **플레이어**: 본인 패 조작(탭으로 확대/내기), 원카드 선언, 턴 표시
- **게임 로직**: `src/games/onecard/` 에 순수 함수로 분리 (UI·네트워크와 무관)
## 화면 흐름
```mermaid
flowchart LR
A["/"] --> B["/onecard/lobby"]
B -->|"방 만들기"| C["/onecard/board"]
B -->|"방 참가"| D["/onecard/player"]
```
| 경로 | 역할 | 설명 |
|------|------|------|
| `/` | 공통 | 게임 선택 (현재: 원카드) |
| `/onecard/lobby` | 공통 | 방 만들기 / 참가, 방 코드 표시 |
| `/onecard/board` | 보드판 전용 | 4모서리 테이블, 자리 배치, 게임 진행 |
| `/onecard/player` | 플레이어 전용 | 패 UI, 턴 표시 |
## 게임 진행 흐름
```mermaid
sequenceDiagram
participant B as 보드판
participant P as 플레이어
B->>B: 방 생성 (Peer ID = 방 코드)
P->>B: join (닉네임, playerId)
B->>P: joined / players-update
B->>B: 자리 배치 (드래그앤드롭)
B->>B: createGame() — 덱 셔플, 7장 배분
B->>P: game-start, hand-update, state-update
loop 턴 진행
P->>B: play-card / request-draw / declare-onecard
B->>B: playCard() / playerDrawCards() 검증
B->>P: hand-update (본인만)
B->>P: state-update (전체 공개 상태)
P->>B: hand-preview (선택 카드 미리보기)
end
Note over B: 패를 모두 낸 플레이어 → round_won
B->>B: 이어하기 / 다시 하기 선택
```
### 게임 단계 (`GamePhase`)
| 단계 | 설명 |
|------|------|
| `waiting` | 게임 시작 전 |
| `playing` | 진행 중 |
| `round_won` | 한 명이 패를 모두 냄 → 이어하기 / 다시 하기 선택 |
| `finished` | 최종 승자 확정 |
| `paused` | 플레이어 연결 끊김 등 |
### 자리 배치
보드판 기준 **시계 방향**: 아래 → 왼쪽 → 위 → 오른쪽
턴 순서는 자리 배치와 동일합니다. 자리 배치 UI는 중앙 미배치 풀 + 모서리 슬롯 **드래그앤드롭**이며, occupied 자리에 놓으면 **서로 교체**됩니다.
## P2P 메시지
보드판이 **호스트(권한 서버)** 역할을 하며, 플레이어는 카드 내기·드로우 요청만 보냅니다.
| 메시지 | 방향 | 용도 |
|--------|------|------|
| `join` | P → B | 방 참가 |
| `joined` / `players-update` | B → P | 접속 확인·목록 갱신 |
| `play-card` | P → B | 카드 내기 |
| `request-draw` | P → B | 턴 중 드로우 |
| `declare-onecard` | P → B | 원카드 선언 |
| `hand-preview` | P → B | 선택 카드 미리보기 (보드 뒷면 강조) |
| `hand-update` | B → P | 본인 패 동기화 |
| `state-update` | B → * | 공개 게임 상태 (덱 수, 턴, 버린 더미 등) |
| `play-rejected` | B → P | 불가 행동 사유 |
## 프로젝트 구조
```
src/
├── views/
│ ├── HomeView.vue # 홈
│ └── onecard/
│ ├── OneCardLobbyView.vue # 로비
│ ├── OneCardBoardView.vue # 보드판
│ └── OneCardPlayerView.vue # 플레이어
├── components/ # 카드·패·덱·자리배치 UI
├── composables/
│ ├── usePeerRoom.ts # PeerJS 방·게임 동기화
│ └── messages.ts # P2P 메시지 타입
├── games/onecard/
│ ├── game.ts # 게임 상태·턴·승리 처리
│ ├── rules.ts # 카드 낼 수 있는지 판정
│ ├── deck.ts # 덱 생성·셔플
│ └── history.ts # 턴 되돌리기
├── stores/roomStore.ts # 연결·공개 상태·내 패
├── utils/
│ ├── boardLayout.ts # 4모서리 자리·턴 순서
│ ├── session.ts # sessionStorage / localStorage
│ └── clipboard.ts # 방 코드 복사
└── config/peer.ts # PeerJS·ICE 설정
```
## 시작하기
### 요구 사항
- Node.js `^22.18.0` 또는 `>=24.12.0`
### 설치 및 실행
```sh ```sh
npm install npm install
```
### Compile and Hot-Reload for Development
```sh
npm run dev npm run dev
``` ```
### Type-Check, Compile and Minify for Production 같은 Wi-Fi에 연결된 기기에서 `http://<개발 PC IP>:5173` 으로 접속합니다.
`npm run dev`는 `--host`로 실행되어 LAN 접속이 가능합니다.
### 빌드
```sh ```sh
npm run build npm run build # 타입 체크 + 프로덕션 빌드
npm run preview # 빌드 결과 미리보기
``` ```
## 환경 변수 (선택)
자체 TURN 서버를 쓰려면 프로젝트 루트에 `.env` 를 추가합니다.
```env
VITE_TURN_URLS=turn:your.server:3478
VITE_TURN_USERNAME=your-username
VITE_TURN_CREDENTIAL=your-credential
```
미설정 시 `openrelay.metered.ca` 공개 TURN을 사용합니다.
## 브라우저
- **권장**: 최신 Chrome, Safari, Edge (모바일·태블릿 포함)
- **P2P**: WebRTC 지원 브라우저 필요. 방화벽·NAT 환경에서는 TURN 품질에 따라 연결이 달라질 수 있습니다.
- **구형 Safari**: 터치 진입·스크롤 이슈를 일부 완화했으나, iOS 12 이하 등 ES 모듈 미지원 환경은 빌드 타겟 추가(`@vitejs/plugin-legacy`)가 필요할 수 있습니다.
## CI (Gitea Actions)
`main` / `master` 브랜치에 push·PR 시 Gitea Runner(`ubuntu-latest`)에서 빌드를 실행합니다.
워크플로: [`.gitea/workflows/ci.yml`](.gitea/workflows/ci.yml)
```sh
npm ci
npm run build # vue-tsc 타입 체크 + vite build
```
### Runner 요구 사항
1. Gitea 서버에서 **Actions** 활성화
2. `main-runner` 등 `ubuntu-latest` 레이블 러너 등록·유휴 상태
3. (선택) Actions 마켓플레이스 URL — `actions/checkout`, `actions/setup-node` 사용
자체 호스팅 Gitea는 `app.ini`에 `DEFAULT_ACTIONS_URL` 설정이 필요할 수 있습니다.
푸시 후 저장소 **Actions** 탭에서 `CI` 워크플로 실행 여부를 확인하세요.
## 라이선스
Private project.
+4 -4
View File
@@ -123,7 +123,7 @@ const dimensionStyle = computed(() => {
--card-h: 176px; --card-h: 176px;
} }
.card.2xl { .card.xxl {
--card-w: 140px; --card-w: 140px;
--card-h: 210px; --card-h: 210px;
border-radius: 12px; border-radius: 12px;
@@ -247,12 +247,12 @@ const dimensionStyle = computed(() => {
font-size: 2.6rem; font-size: 2.6rem;
} }
.card.2xl .top-left, .card.xxl .top-left,
.card.2xl .bottom-right { .card.xxl .bottom-right {
font-size: 1.35rem; font-size: 1.35rem;
} }
.card.2xl .center-value { .card.xxl .center-value {
font-size: 3rem; font-size: 3rem;
} }
+1 -1
View File
@@ -84,7 +84,7 @@ function layerStyle(layerIndex: number) {
<CardView <CardView
:card="deckBackCard" :card="deckBackCard"
face-down face-down
:size="large ? '2xl' : 'md'" :size="large ? 'xxl' : 'md'"
:dimensions="cardDimensions" :dimensions="cardDimensions"
/> />
</div> </div>
+1 -1
View File
@@ -21,7 +21,7 @@ const isHovered = ref(false)
const visibleCount = computed(() => Math.min(Math.max(props.count, 0), 14)) const visibleCount = computed(() => Math.min(Math.max(props.count, 0), 14))
const cardSize = computed((): CardViewSize => (props.large ? '2xl' : 'md')) const cardSize = computed((): CardViewSize => (props.large ? 'xxl' : 'md'))
const cardHalf = computed(() => (props.large ? 70 : 36)) const cardHalf = computed(() => (props.large ? 70 : 36))
const cardHeight = computed(() => (props.large ? 210 : 120)) const cardHeight = computed(() => (props.large ? 210 : 120))
const cardSpacing = computed(() => (props.large ? 28 : 18)) const cardSpacing = computed(() => (props.large ? 28 : 18))
+1 -1
View File
@@ -1,4 +1,4 @@
export type CardViewSize = 'sm' | 'md' | 'lg' | 'xl' | '2xl' export type CardViewSize = 'sm' | 'md' | 'lg' | 'xl' | 'xxl'
/** 보드판 중앙 덱·버린 더미 공통 카드 크기 */ /** 보드판 중앙 덱·버린 더미 공통 카드 크기 */
export const PILE_CARD = { export const PILE_CARD = {