๐ŸŽฒ ์ฃผ์‚ฌ์œ„ ๊ฒŒ์ž„ ์ปฌ๋ ‰์…˜ - ๋‹ค์–‘ํ•œ ํ™•๋ฅ  ๊ฒŒ์ž„ ํ”Œ๋ ˆ์ด๊ทธ๋ผ์šด๋“œ - ๋ฐ”์ด๋ธŒ์ฝ”๋”ฉ ๋ ˆ์‹œํ”ผ
๊ณ ๊ธ‰ PHP g5se AI: Claude

๐ŸŽฒ ์ฃผ์‚ฌ์œ„ ๊ฒŒ์ž„ ์ปฌ๋ ‰์…˜ - ๋‹ค์–‘ํ•œ ํ™•๋ฅ  ๊ฒŒ์ž„ ํ”Œ๋ ˆ์ด๊ทธ๋ผ์šด๋“œ

๋ฆฌํฌ์ง€ํ† ๋ฆฌ๋ช… dice-game-collection
์กฐํšŒ์ˆ˜ 306 0๊ฑด ์ œ์ถœ

๋ฌธ์ œ ์„ค๋ช…

๐ŸŽฏ ํ”„๋กœ์ ํŠธ ๊ฐœ์š”

๊ทธ๋ˆ„๋ณด๋“œ5 SE ์œ„์—์„œ ๋™์ž‘ํ•˜๋Š” ์ฃผ์‚ฌ์œ„ ๊ฒŒ์ž„ ์ปฌ๋ ‰์…˜ ํ”Œ๋žซํผ์„ ๋งŒ๋“ญ๋‹ˆ๋‹ค. ์—ฌ๋Ÿฌ ์ข…๋ฅ˜์˜ ์ฃผ์‚ฌ์œ„ ๊ฒŒ์ž„(์ผ๋ฐ˜ ์ฃผ์‚ฌ์œ„, ํŠน์ˆ˜ ์ฃผ์‚ฌ์œ„, ๋‹ค๋ฉด์ฒด ์ฃผ์‚ฌ์œ„ ๋“ฑ)์„ ํ•œ ๊ณณ์—์„œ ์ฆ๊ธฐ๊ณ , ๊ฒŒ์ž„ ๊ฒฐ๊ณผ๋ฅผ ๊ธฐ๋กํ•˜๋ฉฐ, ์ „์ฒด ํ”Œ๋ ˆ์ด์–ด ํ†ต๊ณ„๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋Š” ์ธํ„ฐ๋ž™ํ‹ฐ๋ธŒ ์›น ์„œ๋น„์Šค์ž…๋‹ˆ๋‹ค.

ํ•„์ˆ˜ ๊ธฐ๋Šฅ

  1. ๊ฒŒ์ž„ ์ปฌ๋ ‰์…˜ ๋ฉ”์ธ ํŽ˜์ด์ง€

    • 6์ข…๋ฅ˜ ์ด์ƒ์˜ ๋‹ค์–‘ํ•œ ์ฃผ์‚ฌ์œ„ ๊ฒŒ์ž„ ์นด๋“œ ๊ทธ๋ฆฌ๋“œ ๋ ˆ์ด์•„์›ƒ
    • ๊ฐ ๊ฒŒ์ž„๋ณ„ ์ธ๋„ค์ผ, ์„ค๋ช…, ํ”Œ๋ ˆ์ด ํšŸ์ˆ˜ ํ‘œ์‹œ
    • ํ˜ธ๋ฒ„ ์‹œ 3D ์นด๋“œ ํ‹ธํŠธ ํšจ๊ณผ
    • ๋‹คํฌ๋ชจ๋“œ ์ž๋™ ๋Œ€์‘ (modern shell ๋””์ž์ธ ํ† ํฐ ํ™œ์šฉ)
  2. ๊ฐœ๋ณ„ ๊ฒŒ์ž„ ํŽ˜์ด์ง€ (ํด๋” ๊ธฐ๋ฐ˜ ๋ผ์šฐํŒ…)

    • /games/classic - ์ผ๋ฐ˜ ์ฃผ์‚ฌ์œ„ (1~6)
    • /games/lucky7 - ๋Ÿญํ‚ค์„ธ๋ธ (ํ•ฉ์ด 7์ด๋ฉด ๋Œ€๋ฐ•)
    • /games/polyhedral - ๋‹ค๋ฉด์ฒด ์ฃผ์‚ฌ์œ„ (d4, d6, d8, d10, d12, d20)
    • /games/custom - ์ปค์Šคํ…€ ์ฃผ์‚ฌ์œ„ (์‚ฌ์šฉ์ž ์ •์˜ ๋ฉด ๊ฐœ์ˆ˜)
    • /games/battle - ์ฃผ์‚ฌ์œ„ ๋ฐฐํ‹€ (2์ธ ๋Œ€๊ฒฐ)
    • /games/accumulate - ๋ˆ„์  ์ ์ˆ˜ ๊ฒŒ์ž„ (๋ชฉํ‘œ ์ ์ˆ˜ ๋„๋‹ฌ)
    • ๊ฐ ๊ฒŒ์ž„๋ณ„ ๊ณ ์œ ํ•œ ๋ฃฐ๊ณผ UI/UX
    • ์• ๋‹ˆ๋ฉ”์ด์…˜์œผ๋กœ ์ฃผ์‚ฌ์œ„ ๊ตด๋ฆฌ๊ธฐ ํšจ๊ณผ (CSS 3D transform)
    • ๊ฒฐ๊ณผ ํžˆ์Šคํ† ๋ฆฌ ์‹ค์‹œ๊ฐ„ ํ‘œ์‹œ
  3. ๊ฒŒ์ž„ ๊ธฐ๋ก ์‹œ์Šคํ…œ (SQLite)

    • ๊ฐ ๊ฒŒ์ž„๋ณ„ ํ”Œ๋ ˆ์ด ๊ธฐ๋ก ์ €์žฅ (๊ฒŒ์ž„ ํƒ€์ž…, ๊ฒฐ๊ณผ, ํƒ€์ž„์Šคํƒฌํ”„)
    • ํšŒ์› ์—ฐ๋™: ๋กœ๊ทธ์ธํ•œ ์‚ฌ์šฉ์ž๋Š” ์ž์‹ ์˜ ๊ธฐ๋ก ์ถ”์ , ๋น„ํšŒ์›์€ ์„ธ์…˜ ๊ธฐ๋ฐ˜ ์ž„์‹œ ๊ธฐ๋ก
    • ๊ฒŒ์ž„๋ณ„ ์ตœ๊ณ  ๊ธฐ๋ก, ํ‰๊ท  ์ ์ˆ˜, ์ด ํ”Œ๋ ˆ์ด ํšŸ์ˆ˜ ํ†ต๊ณ„
  4. ์ „์ฒด ํ†ต๊ณ„ ๋Œ€์‹œ๋ณด๋“œ (/games/stats)

    • ์ „์ฒด ํ”Œ๋ ˆ์ด์–ด์˜ ๊ฒŒ์ž„๋ณ„ ํ†ต๊ณ„ (์ด ํ”Œ๋ ˆ์ด ํšŸ์ˆ˜, ์ธ๊ธฐ ๊ฒŒ์ž„ ์ˆœ์œ„)
    • ์ตœ๊ทผ 100๊ฐœ ํ”Œ๋ ˆ์ด ๊ธฐ๋ก ํƒ€์ž„๋ผ์ธ
    • Chart.js๋ฅผ ํ™œ์šฉํ•œ ๊ฒŒ์ž„๋ณ„ ํ”Œ๋ ˆ์ด ๋ถ„ํฌ ํŒŒ์ด ์ฐจํŠธ
    • ์ฃผ์‚ฌ์œ„ ๋ˆˆ ๋ถ„ํฌ ํžˆ์Šคํ† ๊ทธ๋žจ (ํ™•๋ฅ  ๊ฒ€์ฆ)
  5. ๊ฐœ์ธ ๊ธฐ๋ก ํŽ˜์ด์ง€ (/games/myrecords)

    • ๋กœ๊ทธ์ธ ์‚ฌ์šฉ์ž ์ „์šฉ
    • ๋‚ด๊ฐ€ ํ”Œ๋ ˆ์ดํ•œ ๊ฒŒ์ž„๋ณ„ ์ƒ์„ธ ๊ธฐ๋ก
    • ๊ฒŒ์ž„๋ณ„ ์Šน๋ฅ , ์ตœ๊ณ  ์ ์ˆ˜, ํ”Œ๋ ˆ์ด ํƒ€์ž„ ์ดํ•ฉ
    • ๊ธฐ๋ก ์‚ญ์ œ ๊ธฐ๋Šฅ (AJAX)
  6. UX ์ธํ„ฐ๋ž™์…˜

    • ์ฃผ์‚ฌ์œ„ ๊ตด๋ฆฌ๊ธฐ ๋ฒ„ํŠผ ํด๋ฆญ ์‹œ 3D ํšŒ์ „ ์• ๋‹ˆ๋ฉ”์ด์…˜ + ์‚ฌ์šด๋“œ ํšจ๊ณผ (optional)
    • ๊ฒฐ๊ณผ ๋ชจ๋‹ฌ ํŒ์—… (์Šน๋ฆฌ/ํŒจ๋ฐฐ ๋ฉ”์‹œ์ง€)
    • ๋ฌดํ•œ ์Šคํฌ๋กค๋กœ ๊ธฐ๋ก ํƒ€์ž„๋ผ์ธ ๋กœ๋”ฉ
    • ๋“œ๋ž˜๊ทธ ์•ค ๋“œ๋กญ์œผ๋กœ ๋‹ค๋ฉด์ฒด ์ฃผ์‚ฌ์œ„ ๊ฐœ์ˆ˜ ์กฐ์ • (์Šฌ๋ผ์ด๋”)

์„ ํƒ ์ถ”๊ฐ€ ๊ธฐ๋Šฅ (๋ณด๋„ˆ์Šค)

  • ์ฃผ์‚ฌ์œ„ ์Šคํ‚จ ์ปค์Šคํ„ฐ๋งˆ์ด์ง• (์ƒ‰์ƒ, ๋ฌด๋Šฌ ๋ณ€๊ฒฝ)
  • ์‹ค์‹œ๊ฐ„ ์ˆœ์œ„ํ‘œ (์˜ค๋Š˜์˜ ์ตœ๊ณ  ์ ์ˆ˜)
  • ์นœ๊ตฌ ์ดˆ๋Œ€ ๋งํฌ ์ƒ์„ฑ (๋ฐฐํ‹€ ๊ฒŒ์ž„ ๊ณต์œ )
  • ๊ฒŒ์ž„ ๋ฆฌํ”Œ๋ ˆ์ด ๊ธฐ๋Šฅ (๊ณผ๊ฑฐ ๊ธฐ๋ก ์žฌ์ƒ)
  • ๋„์ „ ๊ณผ์ œ ์‹œ์Šคํ…œ (๋ฑƒ์ง€ ํš๋“)

๊ธฐ์ˆ  ์Šคํƒ

  • ๋ฐฑ์—”๋“œ: ๊ทธ๋ˆ„๋ณด๋“œ5 SE (PHP 8.x), SQLite (๋ชจ๋“ˆ ์ „์šฉ DB)
  • ํ”„๋ก ํŠธ์—”๋“œ: ์ˆœ์ˆ˜ HTML/CSS/JS (Vanilla), modern shell ๋””์ž์ธ ํ† ํฐ
  • ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ: Chart.js (CDN, ํ†ต๊ณ„ ์ฐจํŠธ์šฉ)
  • ๋ผ์šฐํŒ…: ํด๋” ๊ธฐ๋ฐ˜ ์ž๋™ ๋ผ์šฐํŒ… (app/modules/games/*/index.php)

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์Šคํ‚ค๋งˆ

games.sqlite (G5_DATA_PATH.'/modules/games.sqlite')

CREATE TABLE IF NOT EXISTS play_records (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    game_type TEXT NOT NULL,        -- 'classic', 'lucky7', 'polyhedral', ...
    mb_id TEXT,                      -- ํšŒ์› ID (๋น„ํšŒ์›์€ NULL)
    session_id TEXT,                 -- ๋น„ํšŒ์› ์„ธ์…˜ ID
    result_data TEXT NOT NULL,       -- JSON ํ˜•์‹ ๊ฒŒ์ž„ ๊ฒฐ๊ณผ
    score INTEGER DEFAULT 0,         -- ์ ์ˆ˜
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

CREATE INDEX IF NOT EXISTS idx_game_type ON play_records(game_type);
CREATE INDEX IF NOT EXISTS idx_mb_id ON play_records(mb_id);
CREATE INDEX IF NOT EXISTS idx_created_at ON play_records(created_at DESC);

ํด๋” ๊ตฌ์กฐ

app/modules/games/
โ”œโ”€โ”€ index.php              (๋ฉ”์ธ ๊ฒŒ์ž„ ์ปฌ๋ ‰์…˜ ํŽ˜์ด์ง€)
โ”œโ”€โ”€ classic/
โ”‚   โ””โ”€โ”€ index.php          (์ผ๋ฐ˜ ์ฃผ์‚ฌ์œ„ ๊ฒŒ์ž„)
โ”œโ”€โ”€ lucky7/
โ”‚   โ””โ”€โ”€ index.php          (๋Ÿญํ‚ค์„ธ๋ธ ๊ฒŒ์ž„)
โ”œโ”€โ”€ polyhedral/
โ”‚   โ””โ”€โ”€ index.php          (๋‹ค๋ฉด์ฒด ์ฃผ์‚ฌ์œ„)
โ”œโ”€โ”€ custom/
โ”‚   โ””โ”€โ”€ index.php          (์ปค์Šคํ…€ ์ฃผ์‚ฌ์œ„)
โ”œโ”€โ”€ battle/
โ”‚   โ””โ”€โ”€ index.php          (์ฃผ์‚ฌ์œ„ ๋ฐฐํ‹€)
โ”œโ”€โ”€ accumulate/
โ”‚   โ””โ”€โ”€ index.php          (๋ˆ„์  ์ ์ˆ˜ ๊ฒŒ์ž„)
โ”œโ”€โ”€ stats/
โ”‚   โ””โ”€โ”€ index.php          (์ „์ฒด ํ†ต๊ณ„ ๋Œ€์‹œ๋ณด๋“œ)
โ””โ”€โ”€ myrecords/
    โ””โ”€โ”€ index.php          (๊ฐœ์ธ ๊ธฐ๋ก ํŽ˜์ด์ง€)

์ด ํ”„๋กœ์ ํŠธ๋Š” ํด๋” ๊ธฐ๋ฐ˜ ๋ผ์šฐํŒ…์˜ ๊ฐ•๋ ฅํ•จ์„ ์ฒดํ—˜ํ•˜๊ณ , SQLite ๋ชจ๋“ˆ DB ํŒจํ„ด์„ ์ตํžˆ๋ฉฐ, modern shell ๋””์ž์ธ ํ† ํฐ์œผ๋กœ ์ผ๊ด€๋œ UX๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ์‹ค์ „ ์—ฐ์Šต์ž…๋‹ˆ๋‹ค.

์ œ์•ฝ ์กฐ๊ฑด

  • โ€ข ๊ทธ๋ˆ„๋ณด๋“œ5 SE ์„ค์น˜๋ณธ ์œ„์—์„œ ์ž‘์—… (https://github.com/gnuboard/g5se)
  • โ€ข ์ˆœ์ˆ˜ PHP + g5se ์ปจ๋ฒค์…˜๋งŒ ์‚ฌ์šฉ (Laravel/Composer/Vite/React/Vue ๊ธˆ์ง€)
  • โ€ข ํด๋” ๊ธฐ๋ฐ˜ ์ž๋™ ๋ผ์šฐํŒ… ํ™œ์šฉ (app/router.php ๋ผ์šฐํŠธ ๋“ฑ๋ก ๋ถˆํ•„์š”)
  • โ€ข modern shell (_head.inc.php, m-shell, _nav.inc.php, m-container, _footer.inc.php) ํ•„์ˆ˜ ์ ์šฉ
  • โ€ข ๋””์ž์ธ ํ† ํฐ (--m-surface, --m-text, --m-shadow ๋“ฑ) ์‚ฌ์šฉํ•˜์—ฌ ๋‹คํฌ๋ชจ๋“œ ์ž๋™ ๋Œ€์‘
  • โ€ข SQLite ๋ชจ๋“ˆ DB๋Š” G5_DATA_PATH.'/modules/games.sqlite'์— ์ƒ์„ฑ
  • โ€ข prepared statement๋กœ SQL injection ๋ฐฉ์ง€
  • โ€ข htmlspecialchars()๋กœ XSS ๋ฐฉ์ง€
  • โ€ข ์™ธ๋ถ€ ๋นŒ๋“œ ๋„๊ตฌ(npm/webpack) ์‚ฌ์šฉ ๊ธˆ์ง€
  • โ€ข Chart.js๋Š” CDN์—์„œ ๋กœ๋“œ (<script src='https://cdn.jsdelivr.net/npm/chart.js@4'></script>)
  • โ€ข ํšŒ์› ์—ฐ๋™: $is_member, $member['mb_id'] ํ™œ์šฉ
  • โ€ข ๋น„ํšŒ์›์€ session_id()๋กœ ์ž„์‹œ ๊ธฐ๋ก ์ถ”์ 
  • โ€ข AJAX ์š”์ฒญ์€ Content-Type: application/json ์‘๋‹ต
  • โ€ข ์ฃผ์‚ฌ์œ„ ์• ๋‹ˆ๋ฉ”์ด์…˜์€ CSS 3D transform + @keyframes ์‚ฌ์šฉ

ํ”„๋กฌํ”„ํŠธ ๋ ˆ์‹œํ”ผ

์•„๋ž˜ ํ”„๋กฌํ”„ํŠธ๋ฅผ ์ˆœ์„œ๋Œ€๋กœ AI์—๊ฒŒ ๋ณด๋‚ด๋ฉด ๋ฉ๋‹ˆ๋‹ค. ๋ณต์‚ฌ ๋ฒ„ํŠผ์„ ๋ˆŒ๋Ÿฌ ๋ฐ”๋กœ ์‚ฌ์šฉํ•˜์„ธ์š”.

1
Step 1
g5se๊ฐ€ ์„ค์น˜๋œ ๋””๋ ‰ํ„ฐ๋ฆฌ(๋ฃจํŠธ์— `index.php`ยท`app/`์ด ์žˆ๋Š” ๊ณณ)์—์„œ ์ž‘์—…์„ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค. (g5se ์„ค์น˜๋Š” https://github.com/gnuboard/g5se ์ฐธ๊ณ )

**Step 1: ํ”„๋กœ์ ํŠธ ํด๋” ๊ตฌ์กฐ ์ƒ์„ฑ & ๋ฉ”์ธ ํŽ˜์ด์ง€ ๊ณจ๊ฒฉ**

์•„๋ž˜ ํด๋” ๊ตฌ์กฐ๋ฅผ ์ƒ์„ฑํ•˜์„ธ์š”:

```
app/modules/games/
โ”œโ”€โ”€ index.php              (๋ฉ”์ธ ๊ฒŒ์ž„ ์ปฌ๋ ‰์…˜)
โ”œโ”€โ”€ classic/
โ”‚   โ””โ”€โ”€ index.php
โ”œโ”€โ”€ lucky7/
โ”‚   โ””โ”€โ”€ index.php
โ”œโ”€โ”€ polyhedral/
โ”‚   โ””โ”€โ”€ index.php
โ”œโ”€โ”€ custom/
โ”‚   โ””โ”€โ”€ index.php
โ”œโ”€โ”€ battle/
โ”‚   โ””โ”€โ”€ index.php
โ”œโ”€โ”€ accumulate/
โ”‚   โ””โ”€โ”€ index.php
โ”œโ”€โ”€ stats/
โ”‚   โ””โ”€โ”€ index.php
โ””โ”€โ”€ myrecords/
    โ””โ”€โ”€ index.php
```

**app/modules/games/index.php** ํŒŒ์ผ์„ ์ƒ์„ฑํ•˜๊ณ  ๋‹ค์Œ ๋‚ด์šฉ์„ ์ž‘์„ฑํ•˜์„ธ์š”:

```php
<?php
include_once('./_common.php');

// SQLite DB ์ดˆ๊ธฐํ™”
@mkdir(G5_DATA_PATH.'/modules', 0707, true);
$dbPath = G5_DATA_PATH.'/modules/games.sqlite';
$pdo = new PDO('sqlite:'.$dbPath);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

// ํ…Œ์ด๋ธ” ์ƒ์„ฑ
$pdo->exec("
CREATE TABLE IF NOT EXISTS play_records (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    game_type TEXT NOT NULL,
    mb_id TEXT,
    session_id TEXT,
    result_data TEXT NOT NULL,
    score INTEGER DEFAULT 0,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)
");
$pdo->exec("CREATE INDEX IF NOT EXISTS idx_game_type ON play_records(game_type)");
$pdo->exec("CREATE INDEX IF NOT EXISTS idx_mb_id ON play_records(mb_id)");
$pdo->exec("CREATE INDEX IF NOT EXISTS idx_created_at ON play_records(created_at DESC)");

// ์ „์ฒด ๊ฒŒ์ž„ ํ†ต๊ณ„ ์กฐํšŒ
$stmt = $pdo->query("
    SELECT game_type, COUNT(*) as play_count
    FROM play_records
    GROUP BY game_type
    ORDER BY play_count DESC
");
$gameStats = $stmt->fetchAll(PDO::FETCH_ASSOC);
$statsMap = [];
foreach ($gameStats as $row) {
    $statsMap[$row['game_type']] = $row['play_count'];
}

// ๊ฒŒ์ž„ ๋ชฉ๋ก ์ •์˜
$games = [
    ['id' => 'classic', 'name' => 'ํด๋ž˜์‹ ์ฃผ์‚ฌ์œ„', 'desc' => '1~6 ์ˆซ์ž๊ฐ€ ๋‚˜์˜ค๋Š” ๊ธฐ๋ณธ ์ฃผ์‚ฌ์œ„', 'icon' => '๐ŸŽฒ'],
    ['id' => 'lucky7', 'name' => '๋Ÿญํ‚ค์„ธ๋ธ', 'desc' => '๋‘ ์ฃผ์‚ฌ์œ„ ํ•ฉ์ด 7์ด๋ฉด ๋Œ€๋ฐ•!', 'icon' => '๐Ÿ€'],
    ['id' => 'polyhedral', 'name' => '๋‹ค๋ฉด์ฒด ์ฃผ์‚ฌ์œ„', 'desc' => 'd4, d6, d8, d10, d12, d20 ์„ ํƒ', 'icon' => '๐Ÿ”ท'],
    ['id' => 'custom', 'name' => '์ปค์Šคํ…€ ์ฃผ์‚ฌ์œ„', 'desc' => '์›ํ•˜๋Š” ๋ฉด ๊ฐœ์ˆ˜ ์„ค์ •', 'icon' => 'โš™๏ธ'],
    ['id' => 'battle', 'name' => '์ฃผ์‚ฌ์œ„ ๋ฐฐํ‹€', 'desc' => '2์ธ ๋Œ€๊ฒฐ ๋ชจ๋“œ', 'icon' => 'โš”๏ธ'],
    ['id' => 'accumulate', 'name' => '๋ˆ„์  ์ ์ˆ˜', 'desc' => '๋ชฉํ‘œ ์ ์ˆ˜ ๋จผ์ € ๋„๋‹ฌํ•˜๊ธฐ', 'icon' => '๐ŸŽฏ'],
];

require_once(G5_THEME_PATH.'/modern/_head.inc.php');
include_once('./head.sub.php');
?>
<div class="m-shell">
    <?php require G5_THEME_PATH.'/modern/_nav.inc.php'; ?>
    <main class="m-container" style="padding: 32px 20px 48px;">
        <h1 style="font-size: 2rem; font-weight: 700; margin-bottom: 12px; color: var(--m-text);">๐ŸŽฒ ์ฃผ์‚ฌ์œ„ ๊ฒŒ์ž„ ์ปฌ๋ ‰์…˜</h1>
        <p style="color: var(--m-text-muted); margin-bottom: 32px;">๋‹ค์–‘ํ•œ ์ฃผ์‚ฌ์œ„ ๊ฒŒ์ž„์„ ์ฆ๊ธฐ๊ณ  ๊ธฐ๋ก์„ ๋‚จ๊ฒจ๋ณด์„ธ์š”!</p>
        
        <div class="game-grid" style="display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 20px;">
            <?php foreach ($games as $game): ?>
            <a href="/games/<?php echo htmlspecialchars($game['id']); ?>" class="game-card" style="text-decoration: none;">
                <div style="background: var(--m-surface); border-radius: var(--m-radius-lg); box-shadow: var(--m-shadow); padding: 24px; transition: transform 0.2s, box-shadow 0.2s;">
                    <div style="font-size: 3rem; margin-bottom: 12px;"><?php echo $game['icon']; ?></div>
                    <h3 style="font-size: 1.25rem; font-weight: 600; color: var(--m-text); margin-bottom: 8px;"><?php echo htmlspecialchars($game['name']); ?></h3>
                    <p style="color: var(--m-text-muted); font-size: 0.9rem; margin-bottom: 12px;"><?php echo htmlspecialchars($game['desc']); ?></p>
                    <div style="display: flex; align-items: center; gap: 8px; color: var(--m-text-muted); font-size: 0.85rem;">
                        <span>โ–ถ๏ธ ํ”Œ๋ ˆ์ด ํšŸ์ˆ˜:</span>
                        <strong style="color: var(--m-primary);"><?php echo isset($statsMap[$game['id']]) ? number_format($statsMap[$game['id']]) : 0; ?></strong>
                    </div>
                </div>
            </a>
            <?php endforeach; ?>
        </div>
        
        <div style="margin-top: 40px; text-align: center;">
            <a href="/games/stats" style="display: inline-block; background: var(--m-primary); color: white; padding: 12px 24px; border-radius: var(--m-radius-lg); text-decoration: none; font-weight: 600;">๐Ÿ“Š ์ „์ฒด ํ†ต๊ณ„ ๋ณด๊ธฐ</a>
            <?php if ($is_member): ?>
            <a href="/games/myrecords" style="display: inline-block; background: var(--m-surface); color: var(--m-text); padding: 12px 24px; border-radius: var(--m-radius-lg); text-decoration: none; font-weight: 600; margin-left: 12px; box-shadow: var(--m-shadow);">๐Ÿ“ ๋‚ด ๊ธฐ๋ก ๋ณด๊ธฐ</a>
            <?php endif; ?>
        </div>
    </main>
    <?php require G5_THEME_PATH.'/modern/_footer.inc.php'; ?>
</div>
<style>
.game-card:hover > div {
    transform: translateY(-4px);
    box-shadow: 0 8px 20px rgba(0,0,0,0.15);
}
</style>
<?php include_once('./tail.sub.php'); ?>
```

์ด์ œ `/games` URL๋กœ ์ ‘์†ํ•˜๋ฉด ๊ฒŒ์ž„ ์ปฌ๋ ‰์…˜ ๋ฉ”์ธ ํŽ˜์ด์ง€๊ฐ€ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: ๋ฉ”์ธ ๊ฒŒ์ž„ ์ปฌ๋ ‰์…˜ ํŽ˜์ด์ง€๊ฐ€ ์ƒ์„ฑ๋˜๊ณ , 6๊ฐœ ๊ฒŒ์ž„ ์นด๋“œ๊ฐ€ ๊ทธ๋ฆฌ๋“œ๋กœ ๋ฐฐ์น˜๋ฉ๋‹ˆ๋‹ค. ๊ฐ ์นด๋“œ์—๋Š” ์•„์ด์ฝ˜, ์ด๋ฆ„, ์„ค๋ช…, ํ”Œ๋ ˆ์ด ํšŸ์ˆ˜๊ฐ€ ํ‘œ์‹œ๋˜๋ฉฐ, ํ˜ธ๋ฒ„ ์‹œ ์นด๋“œ๊ฐ€ ์‚ด์ง ์˜ฌ๋ผ๊ฐ€๋Š” ์• ๋‹ˆ๋ฉ”์ด์…˜์ด ์ ์šฉ๋ฉ๋‹ˆ๋‹ค. SQLite DB๊ฐ€ ์ž๋™์œผ๋กœ ์ƒ์„ฑ๋˜๊ณ  play_records ํ…Œ์ด๋ธ”์ด ์ดˆ๊ธฐํ™”๋ฉ๋‹ˆ๋‹ค. ๋‹คํฌ๋ชจ๋“œ ์ „ํ™˜ ์‹œ ์ƒ‰์ƒ์ด ์ž๋™์œผ๋กœ ๋Œ€์‘๋ฉ๋‹ˆ๋‹ค.

2
Step 2
**Step 2: ํด๋ž˜์‹ ์ฃผ์‚ฌ์œ„ ๊ฒŒ์ž„ ํŽ˜์ด์ง€ ๊ตฌํ˜„**

**app/modules/games/classic/index.php** ํŒŒ์ผ์„ ์ƒ์„ฑํ•˜๊ณ  ๋‹ค์Œ ๋‚ด์šฉ์„ ์ž‘์„ฑํ•˜์„ธ์š”:

```php
<?php
include_once('./_common.php');

// SQLite ์—ฐ๊ฒฐ
$dbPath = G5_DATA_PATH.'/modules/games.sqlite';
$pdo = new PDO('sqlite:'.$dbPath);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

// AJAX ์š”์ฒญ ์ฒ˜๋ฆฌ (์ฃผ์‚ฌ์œ„ ๊ตด๋ฆฌ๊ธฐ)
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'roll') {
    header('Content-Type: application/json');
    
    $result = rand(1, 6);
    $mbId = $is_member ? $member['mb_id'] : null;
    $sessionId = session_id();
    
    $stmt = $pdo->prepare("
        INSERT INTO play_records (game_type, mb_id, session_id, result_data, score)
        VALUES ('classic', ?, ?, ?, ?)
    ");
    $stmt->execute([$mbId, $sessionId, json_encode(['dice' => $result]), $result]);
    
    echo json_encode(['success' => true, 'result' => $result, 'id' => $pdo->lastInsertId()]);
    exit;
}

// ์ตœ๊ทผ ๊ธฐ๋ก 10๊ฐœ ์กฐํšŒ
$stmt = $pdo->prepare("
    SELECT result_data, created_at
    FROM play_records
    WHERE game_type = 'classic'
    ORDER BY created_at DESC
    LIMIT 10
");
$stmt->execute();
$recentRecords = $stmt->fetchAll(PDO::FETCH_ASSOC);

require_once(G5_THEME_PATH.'/modern/_head.inc.php');
include_once('./head.sub.php');
?>
<div class="m-shell">
    <?php require G5_THEME_PATH.'/modern/_nav.inc.php'; ?>
    <main class="m-container" style="padding: 32px 20px 48px;">
        <a href="/games" style="color: var(--m-text-muted); text-decoration: none; margin-bottom: 20px; display: inline-block;">โ† ๊ฒŒ์ž„ ๋ชฉ๋ก์œผ๋กœ</a>
        
        <h1 style="font-size: 2rem; font-weight: 700; margin-bottom: 12px; color: var(--m-text);">๐ŸŽฒ ํด๋ž˜์‹ ์ฃผ์‚ฌ์œ„</h1>
        <p style="color: var(--m-text-muted); margin-bottom: 32px;">1๋ถ€ํ„ฐ 6๊นŒ์ง€ ๋‚˜์˜ค๋Š” ์ผ๋ฐ˜ ์ฃผ์‚ฌ์œ„์ž…๋‹ˆ๋‹ค.</p>
        
        <div style="max-width: 600px; margin: 0 auto;">
            <div id="diceContainer" style="text-align: center; margin-bottom: 32px;">
                <div id="dice" style="width: 120px; height: 120px; margin: 0 auto 24px; background: white; border-radius: 16px; box-shadow: 0 4px 12px rgba(0,0,0,0.2); display: flex; align-items: center; justify-content: center; font-size: 4rem; font-weight: 700; color: #333;">
                    ?
                </div>
                <button id="rollBtn" style="background: var(--m-primary); color: white; padding: 14px 32px; border: none; border-radius: var(--m-radius-lg); font-size: 1.1rem; font-weight: 600; cursor: pointer; transition: transform 0.1s;">
                    ์ฃผ์‚ฌ์œ„ ๊ตด๋ฆฌ๊ธฐ
                </button>
            </div>
            
            <div style="background: var(--m-surface); border-radius: var(--m-radius-lg); box-shadow: var(--m-shadow); padding: 20px;">
                <h3 style="font-size: 1.1rem; font-weight: 600; color: var(--m-text); margin-bottom: 16px;">๐Ÿ“œ ์ตœ๊ทผ ๊ธฐ๋ก</h3>
                <div id="recordList">
                    <?php if (empty($recentRecords)): ?>
                    <p style="color: var(--m-text-muted); text-align: center;">์•„์ง ๊ธฐ๋ก์ด ์—†์Šต๋‹ˆ๋‹ค.</p>
                    <?php else: ?>
                    <?php foreach ($recentRecords as $record): ?>
                    <?php $data = json_decode($record['result_data'], true); ?>
                    <div style="padding: 12px; border-bottom: 1px solid rgba(0,0,0,0.1); display: flex; justify-content: space-between; align-items: center;">
                        <span style="font-size: 1.5rem;">๐ŸŽฒ <?php echo htmlspecialchars($data['dice']); ?></span>
                        <span style="color: var(--m-text-muted); font-size: 0.85rem;"><?php echo date('m/d H:i', strtotime($record['created_at'])); ?></span>
                    </div>
                    <?php endforeach; ?>
                    <?php endif; ?>
                </div>
            </div>
        </div>
    </main>
    <?php require G5_THEME_PATH.'/modern/_footer.inc.php'; ?>
</div>
<style>
@keyframes roll {
    0% { transform: rotate(0deg); }
    100% { transform: rotate(720deg); }
}
.rolling {
    animation: roll 0.6s ease-out;
}
#rollBtn:hover {
    transform: scale(1.05);
}
#rollBtn:active {
    transform: scale(0.95);
}
</style>
<script>
const dice = document.getElementById('dice');
const rollBtn = document.getElementById('rollBtn');
const recordList = document.getElementById('recordList');

rollBtn.addEventListener('click', async () => {
    rollBtn.disabled = true;
    dice.textContent = '?';
    dice.classList.add('rolling');
    
    const formData = new FormData();
    formData.append('action', 'roll');
    
    try {
        const res = await fetch('/games/classic', {
            method: 'POST',
            body: formData
        });
        const data = await res.json();
        
        setTimeout(() => {
            dice.classList.remove('rolling');
            dice.textContent = data.result;
            
            // ๊ธฐ๋ก ์ถ”๊ฐ€
            const now = new Date();
            const timeStr = `${String(now.getMonth()+1).padStart(2,'0')}/${String(now.getDate()).padStart(2,'0')} ${String(now.getHours()).padStart(2,'0')}:${String(now.getMinutes()).padStart(2,'0')}`;
            const newRecord = `
                <div style="padding: 12px; border-bottom: 1px solid rgba(0,0,0,0.1); display: flex; justify-content: space-between; align-items: center;">
                    <span style="font-size: 1.5rem;">๐ŸŽฒ ${data.result}</span>
                    <span style="color: var(--m-text-muted); font-size: 0.85rem;">${timeStr}</span>
                </div>
            `;
            recordList.insertAdjacentHTML('afterbegin', newRecord);
            
            // 10๊ฐœ ๋„˜์œผ๋ฉด ๋งˆ์ง€๋ง‰ ์ œ๊ฑฐ
            const records = recordList.querySelectorAll('div');
            if (records.length > 10) {
                records[records.length - 1].remove();
            }
            
            rollBtn.disabled = false;
        }, 600);
    } catch (err) {
        console.error(err);
        alert('์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.');
        rollBtn.disabled = false;
        dice.classList.remove('rolling');
    }
});
</script>
<?php include_once('./tail.sub.php'); ?>
```

์ด์ œ `/games/classic` URL๋กœ ์ ‘์†ํ•˜๋ฉด ํด๋ž˜์‹ ์ฃผ์‚ฌ์œ„ ๊ฒŒ์ž„์„ ํ”Œ๋ ˆ์ดํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: ํด๋ž˜์‹ ์ฃผ์‚ฌ์œ„ ๊ฒŒ์ž„ ํŽ˜์ด์ง€๊ฐ€ ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค. ์ฃผ์‚ฌ์œ„ ๊ตด๋ฆฌ๊ธฐ ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜๋ฉด ์ฃผ์‚ฌ์œ„๊ฐ€ ํšŒ์ „ ์• ๋‹ˆ๋ฉ”์ด์…˜๊ณผ ํ•จ๊ป˜ ๊ตด๋Ÿฌ๊ฐ€๊ณ , 0.6์ดˆ ํ›„ ๊ฒฐ๊ณผ๊ฐ€ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค. ๊ฒฐ๊ณผ๋Š” SQLite DB์— ์ €์žฅ๋˜๊ณ , ์ตœ๊ทผ 10๊ฐœ ๊ธฐ๋ก์ด ์‹ค์‹œ๊ฐ„์œผ๋กœ ์—…๋ฐ์ดํŠธ๋ฉ๋‹ˆ๋‹ค. AJAX๋กœ ํŽ˜์ด์ง€ ์ƒˆ๋กœ๊ณ ์นจ ์—†์ด ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค.

3
Step 3
**Step 3: ๋Ÿญํ‚ค์„ธ๋ธ ๊ฒŒ์ž„ & ๋‹ค๋ฉด์ฒด ์ฃผ์‚ฌ์œ„ ๊ฒŒ์ž„ ๊ตฌํ˜„**

**app/modules/games/lucky7/index.php** ํŒŒ์ผ์„ ์ƒ์„ฑํ•˜์„ธ์š”:

```php
<?php
include_once('./_common.php');

$dbPath = G5_DATA_PATH.'/modules/games.sqlite';
$pdo = new PDO('sqlite:'.$dbPath);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'roll') {
    header('Content-Type: application/json');
    
    $dice1 = rand(1, 6);
    $dice2 = rand(1, 6);
    $sum = $dice1 + $dice2;
    $isLucky = ($sum === 7);
    
    $mbId = $is_member ? $member['mb_id'] : null;
    $sessionId = session_id();
    
    $stmt = $pdo->prepare("
        INSERT INTO play_records (game_type, mb_id, session_id, result_data, score)
        VALUES ('lucky7', ?, ?, ?, ?)
    ");
    $stmt->execute([$mbId, $sessionId, json_encode(['dice1' => $dice1, 'dice2' => $dice2, 'sum' => $sum, 'lucky' => $isLucky]), $isLucky ? 100 : 0]);
    
    echo json_encode(['success' => true, 'dice1' => $dice1, 'dice2' => $dice2, 'sum' => $sum, 'lucky' => $isLucky]);
    exit;
}

$stmt = $pdo->query("SELECT result_data, created_at FROM play_records WHERE game_type = 'lucky7' ORDER BY created_at DESC LIMIT 10");
$recentRecords = $stmt->fetchAll(PDO::FETCH_ASSOC);

require_once(G5_THEME_PATH.'/modern/_head.inc.php');
include_once('./head.sub.php');
?>
<div class="m-shell">
    <?php require G5_THEME_PATH.'/modern/_nav.inc.php'; ?>
    <main class="m-container" style="padding: 32px 20px 48px;">
        <a href="/games" style="color: var(--m-text-muted); text-decoration: none; margin-bottom: 20px; display: inline-block;">โ† ๊ฒŒ์ž„ ๋ชฉ๋ก์œผ๋กœ</a>
        
        <h1 style="font-size: 2rem; font-weight: 700; margin-bottom: 12px; color: var(--m-text);">๐Ÿ€ ๋Ÿญํ‚ค์„ธ๋ธ</h1>
        <p style="color: var(--m-text-muted); margin-bottom: 32px;">๋‘ ์ฃผ์‚ฌ์œ„์˜ ํ•ฉ์ด 7์ด๋ฉด ๋Œ€๋ฐ•! ๐ŸŽฐ</p>
        
        <div style="max-width: 600px; margin: 0 auto;">
            <div style="text-align: center; margin-bottom: 32px;">
                <div style="display: flex; gap: 20px; justify-content: center; margin-bottom: 24px;">
                    <div id="dice1" class="dice-box">?</div>
                    <div id="dice2" class="dice-box">?</div>
                </div>
                <div id="sumDisplay" style="font-size: 2rem; font-weight: 700; color: var(--m-text); margin-bottom: 12px;">ํ•ฉ๊ณ„: -</div>
                <div id="resultMsg" style="font-size: 1.2rem; font-weight: 600; min-height: 30px;"></div>
                <button id="rollBtn" class="roll-button">์ฃผ์‚ฌ์œ„ ๊ตด๋ฆฌ๊ธฐ</button>
            </div>
            
            <div style="background: var(--m-surface); border-radius: var(--m-radius-lg); box-shadow: var(--m-shadow); padding: 20px;">
                <h3 style="font-size: 1.1rem; font-weight: 600; color: var(--m-text); margin-bottom: 16px;">๐Ÿ“œ ์ตœ๊ทผ ๊ธฐ๋ก</h3>
                <div id="recordList">
                    <?php if (empty($recentRecords)): ?>
                    <p style="color: var(--m-text-muted); text-align: center;">์•„์ง ๊ธฐ๋ก์ด ์—†์Šต๋‹ˆ๋‹ค.</p>
                    <?php else: ?>
                    <?php foreach ($recentRecords as $record): ?>
                    <?php $data = json_decode($record['result_data'], true); ?>
                    <div style="padding: 12px; border-bottom: 1px solid rgba(0,0,0,0.1); display: flex; justify-content: space-between; align-items: center;">
                        <span><?php echo $data['dice1']; ?> + <?php echo $data['dice2']; ?> = <?php echo $data['sum']; ?> <?php echo $data['lucky'] ? '๐Ÿ€ ๋Ÿญํ‚ค!' : ''; ?></span>
                        <span style="color: var(--m-text-muted); font-size: 0.85rem;"><?php echo date('m/d H:i', strtotime($record['created_at'])); ?></span>
                    </div>
                    <?php endforeach; ?>
                    <?php endif; ?>
                </div>
            </div>
        </div>
    </main>
    <?php require G5_THEME_PATH.'/modern/_footer.inc.php'; ?>
</div>
<style>
.dice-box {
    width: 100px;
    height: 100px;
    background: white;
    border-radius: 12px;
    box-shadow: 0 4px 12px rgba(0,0,0,0.2);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 3rem;
    font-weight: 700;
    color: #333;
}
.roll-button {
    background: var(--m-primary);
    color: white;
    padding: 14px 32px;
    border: none;
    border-radius: var(--m-radius-lg);
    font-size: 1.1rem;
    font-weight: 600;
    cursor: pointer;
    transition: transform 0.1s;
    margin-top: 20px;
}
.roll-button:hover { transform: scale(1.05); }
.roll-button:active { transform: scale(0.95); }
@keyframes roll { 0% { transform: rotate(0deg); } 100% { transform: rotate(720deg); } }
.rolling { animation: roll 0.6s ease-out; }
</style>
<script>
const dice1 = document.getElementById('dice1');
const dice2 = document.getElementById('dice2');
const sumDisplay = document.getElementById('sumDisplay');
const resultMsg = document.getElementById('resultMsg');
const rollBtn = document.getElementById('rollBtn');
const recordList = document.getElementById('recordList');

rollBtn.addEventListener('click', async () => {
    rollBtn.disabled = true;
    dice1.textContent = '?';
    dice2.textContent = '?';
    sumDisplay.textContent = 'ํ•ฉ๊ณ„: -';
    resultMsg.textContent = '';
    dice1.classList.add('rolling');
    dice2.classList.add('rolling');
    
    const formData = new FormData();
    formData.append('action', 'roll');
    
    try {
        const res = await fetch('/games/lucky7', { method: 'POST', body: formData });
        const data = await res.json();
        
        setTimeout(() => {
            dice1.classList.remove('rolling');
            dice2.classList.remove('rolling');
            dice1.textContent = data.dice1;
            dice2.textContent = data.dice2;
            sumDisplay.textContent = `ํ•ฉ๊ณ„: ${data.sum}`;
            
            if (data.lucky) {
                resultMsg.innerHTML = '๐ŸŽ‰ <span style="color: #FFD700;">๋Ÿญํ‚ค ์„ธ๋ธ! ๋Œ€๋ฐ•!</span>';
            } else {
                resultMsg.innerHTML = '<span style="color: var(--m-text-muted);">๋‹ค์Œ ๊ธฐํšŒ์—...</span>';
            }
            
            const now = new Date();
            const timeStr = `${String(now.getMonth()+1).padStart(2,'0')}/${String(now.getDate()).padStart(2,'0')} ${String(now.getHours()).padStart(2,'0')}:${String(now.getMinutes()).padStart(2,'0')}`;
            const newRecord = `
                <div style="padding: 12px; border-bottom: 1px solid rgba(0,0,0,0.1); display: flex; justify-content: space-between; align-items: center;">
                    <span>${data.dice1} + ${data.dice2} = ${data.sum} ${data.lucky ? '๐Ÿ€ ๋Ÿญํ‚ค!' : ''}</span>
                    <span style="color: var(--m-text-muted); font-size: 0.85rem;">${timeStr}</span>
                </div>
            `;
            recordList.insertAdjacentHTML('afterbegin', newRecord);
            const records = recordList.querySelectorAll('div');
            if (records.length > 10) records[records.length - 1].remove();
            
            rollBtn.disabled = false;
        }, 600);
    } catch (err) {
        console.error(err);
        alert('์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.');
        rollBtn.disabled = false;
    }
});
</script>
<?php include_once('./tail.sub.php'); ?>
```

**app/modules/games/polyhedral/index.php** ํŒŒ์ผ๋„ ์ƒ์„ฑํ•˜์„ธ์š” (๋‹ค๋ฉด์ฒด ์ฃผ์‚ฌ์œ„ - d4, d6, d8, d10, d12, d20 ์„ ํƒ ๊ฐ€๋Šฅ). ์ฝ”๋“œ๋Š” lucky7์™€ ์œ ์‚ฌํ•˜๋˜, ์ฃผ์‚ฌ์œ„ ์ข…๋ฅ˜ ์„ ํƒ `<select>` ์ถ”๊ฐ€ํ•˜๊ณ  `rand(1, $sides)` ๋ฐฉ์‹์œผ๋กœ ๊ตฌํ˜„ํ•˜์„ธ์š”.

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: ๋Ÿญํ‚ค์„ธ๋ธ ๊ฒŒ์ž„์ด ์™„์„ฑ๋ฉ๋‹ˆ๋‹ค. ๋‘ ์ฃผ์‚ฌ์œ„๊ฐ€ ๋™์‹œ์— ๊ตด๋Ÿฌ๊ฐ€๊ณ , ํ•ฉ์ด 7์ด๋ฉด '๋Ÿญํ‚ค ์„ธ๋ธ!' ๋ฉ”์‹œ์ง€๊ฐ€ ํ™ฉ๊ธˆ์ƒ‰์œผ๋กœ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค. ๋‹ค๋ฉด์ฒด ์ฃผ์‚ฌ์œ„ ๊ฒŒ์ž„๋„ ์…€๋ ‰ํŠธ ๋ฐ•์Šค๋กœ ์ฃผ์‚ฌ์œ„ ์ข…๋ฅ˜(d4~d20)๋ฅผ ์„ ํƒํ•˜๋ฉด ํ•ด๋‹น ๋ฒ”์œ„์˜ ๋‚œ์ˆ˜๊ฐ€ ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค.

4
Step 4
**Step 4: ์ปค์Šคํ…€ ์ฃผ์‚ฌ์œ„ & ์ฃผ์‚ฌ์œ„ ๋ฐฐํ‹€ ๊ฒŒ์ž„ ๊ตฌํ˜„**

**app/modules/games/custom/index.php** ํŒŒ์ผ์„ ์ƒ์„ฑํ•˜์„ธ์š”:

์‚ฌ์šฉ์ž๊ฐ€ ์Šฌ๋ผ์ด๋”๋กœ ์ฃผ์‚ฌ์œ„ ๋ฉด ๊ฐœ์ˆ˜(2~100)๋ฅผ ์„ ํƒํ•˜๊ณ , ๊ทธ ๋ฒ”์œ„ ๋‚ด์—์„œ ๋žœ๋ค ์ˆซ์ž๊ฐ€ ๋‚˜์˜ค๋„๋ก ๊ตฌํ˜„ํ•˜์„ธ์š”. UI๋Š” ์Šฌ๋ผ์ด๋” + ํ˜„์žฌ ๊ฐ’ ํ‘œ์‹œ + ์ฃผ์‚ฌ์œ„ ๊ตด๋ฆฌ๊ธฐ ๋ฒ„ํŠผ. ๊ฒฐ๊ณผ๋Š” ํฐ ์ˆซ์ž๋กœ ํ‘œ์‹œํ•˜๊ณ  ๊ธฐ๋ก์— "์ปค์Šคํ…€ d{sides}: {result}" ํ˜•ํƒœ๋กœ ์ €์žฅ.

**app/modules/games/battle/index.php** ํŒŒ์ผ์„ ์ƒ์„ฑํ•˜์„ธ์š”:

2์ธ ๋Œ€๊ฒฐ ๋ชจ๋“œ์ž…๋‹ˆ๋‹ค. ๊ฐ ํ”Œ๋ ˆ์ด์–ด(Player 1, Player 2)๊ฐ€ ์ฃผ์‚ฌ์œ„๋ฅผ ๊ตด๋ฆฌ๊ณ , ๋” ํฐ ์ˆซ์ž๊ฐ€ ๋‚˜์˜จ ์ชฝ์ด ์Šน๋ฆฌ. UI๋Š” ๋‘ ๊ฐœ์˜ ์ฃผ์‚ฌ์œ„ + "๋ฐฐํ‹€ ์‹œ์ž‘" ๋ฒ„ํŠผ. ๊ฒฐ๊ณผ๋Š” "Player 1: 5 vs Player 2: 3 โ†’ Player 1 ์Šน๋ฆฌ!" ํ˜•ํƒœ๋กœ ํ‘œ์‹œํ•˜๊ณ , ์Šน์ž ์ด๋ฆ„์„ ๊ฐ•์กฐ(์ƒ‰์ƒ ๋ณ€๊ฒฝ). ๊ธฐ๋ก์—๋Š” `result_data`์— `{"p1": 5, "p2": 3, "winner": 1}` JSON ์ €์žฅ.

๋‘ ํŽ˜์ด์ง€ ๋ชจ๋‘ modern shell + ๋””์ž์ธ ํ† ํฐ + AJAX ํŒจํ„ด ์œ ์ง€ํ•˜์„ธ์š”.

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: ์ปค์Šคํ…€ ์ฃผ์‚ฌ์œ„ ๊ฒŒ์ž„์—์„œ ์Šฌ๋ผ์ด๋”๋ฅผ ์›€์ง์ด๋ฉด "d{sides}" ํ‘œ์‹œ๊ฐ€ ์‹ค์‹œ๊ฐ„ ์—…๋ฐ์ดํŠธ๋˜๊ณ , ์ฃผ์‚ฌ์œ„๋ฅผ ๊ตด๋ฆฌ๋ฉด ์„ ํƒํ•œ ๋ฒ”์œ„ ๋‚ด์˜ ์ˆซ์ž๊ฐ€ ๋‚˜์˜ต๋‹ˆ๋‹ค. ์ฃผ์‚ฌ์œ„ ๋ฐฐํ‹€ ๊ฒŒ์ž„์—์„œ๋Š” ๋‘ ์ฃผ์‚ฌ์œ„๊ฐ€ ๋™์‹œ์— ๊ตด๋Ÿฌ๊ฐ€๊ณ , ์Šน์ž๊ฐ€ ๊ฒฐ์ •๋˜๋ฉด ์Šน์ž ์ด๋ฆ„์ด ํŒŒ๋ž€์ƒ‰(๋˜๋Š” ์ฃผ ์ƒ‰์ƒ)์œผ๋กœ ๊ฐ•์กฐ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค. ๋‘ ๊ฒŒ์ž„ ๋ชจ๋‘ SQLite์— ๊ธฐ๋ก์ด ์ €์žฅ๋˜๊ณ  ์ตœ๊ทผ 10๊ฐœ ๊ธฐ๋ก์ด ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.

5
Step 5
**Step 5: ๋ˆ„์  ์ ์ˆ˜ ๊ฒŒ์ž„ & ์ „์ฒด ํ†ต๊ณ„ ๋Œ€์‹œ๋ณด๋“œ ๊ตฌํ˜„**

**app/modules/games/accumulate/index.php** ํŒŒ์ผ์„ ์ƒ์„ฑํ•˜์„ธ์š”:

๋ชฉํ‘œ ์ ์ˆ˜(์˜ˆ: 50์ )๋ฅผ ๋จผ์ € ๋‹ฌ์„ฑํ•˜๋Š” ๊ฒŒ์ž„์ž…๋‹ˆ๋‹ค. ์ฃผ์‚ฌ์œ„๋ฅผ ๊ตด๋ฆด ๋•Œ๋งˆ๋‹ค ๋‚˜์˜จ ์ˆซ์ž๋งŒํผ ์ ์ˆ˜ ๋ˆ„์ . ํ˜„์žฌ ์ ์ˆ˜๋ฅผ ํ™”๋ฉด์— ํฐ ์ˆซ์ž๋กœ ํ‘œ์‹œํ•˜๊ณ , ๋ชฉํ‘œ ๋„๋‹ฌ ์‹œ "๐ŸŽ‰ ๋ชฉํ‘œ ๋‹ฌ์„ฑ! {roll_count}๋ฒˆ ๋งŒ์— ์„ฑ๊ณต!" ๋ฉ”์‹œ์ง€ ํ‘œ์‹œ. "๋ฆฌ์…‹" ๋ฒ„ํŠผ์œผ๋กœ ๋‹ค์‹œ ์‹œ์ž‘. ๊ธฐ๋ก์—๋Š” `{"rolls": 12, "final_score": 53}` ํ˜•ํƒœ๋กœ ์ €์žฅ.

**app/modules/games/stats/index.php** ํŒŒ์ผ์„ ์ƒ์„ฑํ•˜์„ธ์š”:

์ „์ฒด ํ†ต๊ณ„ ๋Œ€์‹œ๋ณด๋“œ์ž…๋‹ˆ๋‹ค:

1. **๊ฒŒ์ž„๋ณ„ ํ”Œ๋ ˆ์ด ํšŸ์ˆ˜ ํŒŒ์ด ์ฐจํŠธ** (Chart.js ์‚ฌ์šฉ)
   - `SELECT game_type, COUNT(*) FROM play_records GROUP BY game_type`
   - ์ฐจํŠธ๋Š” `<canvas id="gameChart"></canvas>`์— ๋ Œ๋”๋ง

2. **์ตœ๊ทผ 100๊ฐœ ํ”Œ๋ ˆ์ด ํƒ€์ž„๋ผ์ธ**
   - ๊ฒŒ์ž„ ํƒ€์ž…, ๊ฒฐ๊ณผ ์š”์•ฝ, ์‹œ๊ฐ„ ํ‘œ์‹œ
   - ๋ฌดํ•œ ์Šคํฌ๋กค (์„ ํƒ: 100๊ฐœ ์ดํ›„ ๋”๋ณด๊ธฐ ๋ฒ„ํŠผ)

3. **์ฃผ์‚ฌ์œ„ ๋ˆˆ ๋ถ„ํฌ ํžˆ์Šคํ† ๊ทธ๋žจ** (ํด๋ž˜์‹ ์ฃผ์‚ฌ์œ„ ํ•œ์ •)
   - `SELECT result_data FROM play_records WHERE game_type='classic'`
   - JSON ํŒŒ์‹ฑํ•˜์—ฌ 1~6 ๊ฐ ์ˆซ์ž ์ถœํ˜„ ํšŸ์ˆ˜ ์ง‘๊ณ„
   - ๋ง‰๋Œ€ ๊ทธ๋ž˜ํ”„๋กœ ํ‘œ์‹œ

Chart.js๋Š” CDN์œผ๋กœ ๋กœ๋“œ:
```html
<script src="https://cdn.jsdelivr.net/npm/chart.js@4"></script>
```

์ฐจํŠธ ์ƒ์„ฑ ์˜ˆ์‹œ:
```javascript
const ctx = document.getElementById('gameChart').getContext('2d');
new Chart(ctx, {
    type: 'pie',
    data: {
        labels: ['ํด๋ž˜์‹', '๋Ÿญํ‚ค์„ธ๋ธ', '๋‹ค๋ฉด์ฒด', '์ปค์Šคํ…€', '๋ฐฐํ‹€', '๋ˆ„์ '],
        datasets: [{
            data: [120, 85, 60, 45, 30, 20],
            backgroundColor: ['#FF6384', '#36A2EB', '#FFCE56', '#4BC0C0', '#9966FF', '#FF9F40']
        }]
    }
});
```

์‹ค์ œ ๋ฐ์ดํ„ฐ๋Š” PHP์—์„œ JSON์œผ๋กœ ์ถœ๋ ฅํ•˜๊ณ  JS์—์„œ ํŒŒ์‹ฑํ•˜์—ฌ ์‚ฌ์šฉํ•˜์„ธ์š”.

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: ๋ˆ„์  ์ ์ˆ˜ ๊ฒŒ์ž„์—์„œ ์ฃผ์‚ฌ์œ„๋ฅผ ๊ตด๋ฆด ๋•Œ๋งˆ๋‹ค ์ ์ˆ˜๊ฐ€ ๋ˆ„์ ๋˜๊ณ , ๋ชฉํ‘œ ์ ์ˆ˜ ๋„๋‹ฌ ์‹œ ์ถ•ํ•˜ ๋ฉ”์‹œ์ง€๊ฐ€ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค. ์ „์ฒด ํ†ต๊ณ„ ๋Œ€์‹œ๋ณด๋“œ์—๋Š” ๊ฒŒ์ž„๋ณ„ ํ”Œ๋ ˆ์ด ํšŸ์ˆ˜ ํŒŒ์ด ์ฐจํŠธ๊ฐ€ ํ‘œ์‹œ๋˜๊ณ , ์ตœ๊ทผ 100๊ฐœ ํ”Œ๋ ˆ์ด ๊ธฐ๋ก์ด ํƒ€์ž„๋ผ์ธ ํ˜•ํƒœ๋กœ ๋‚˜์—ด๋ฉ๋‹ˆ๋‹ค. ํด๋ž˜์‹ ์ฃผ์‚ฌ์œ„์˜ ๋ˆˆ ๋ถ„ํฌ ํžˆ์Šคํ† ๊ทธ๋žจ๋„ ๋ง‰๋Œ€ ๊ทธ๋ž˜ํ”„๋กœ ์‹œ๊ฐํ™”๋ฉ๋‹ˆ๋‹ค.

6
Step 6
**Step 6: ๊ฐœ์ธ ๊ธฐ๋ก ํŽ˜์ด์ง€ & UX ์ธํ„ฐ๋ž™์…˜ ๊ฐ•ํ™”**

**app/modules/games/myrecords/index.php** ํŒŒ์ผ์„ ์ƒ์„ฑํ•˜์„ธ์š”:

๋กœ๊ทธ์ธํ•œ ํšŒ์›๋งŒ ์ ‘๊ทผ ๊ฐ€๋Šฅ (`if (!$is_member) { alert_close('๋กœ๊ทธ์ธ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.'); }`). ์‚ฌ์šฉ์ž์˜ ๊ฒŒ์ž„๋ณ„ ํ†ต๊ณ„๋ฅผ ํ‘œ์‹œ:

1. **๊ฒŒ์ž„๋ณ„ ํ”Œ๋ ˆ์ด ํšŸ์ˆ˜ & ์ตœ๊ณ  ์ ์ˆ˜**
   - `SELECT game_type, COUNT(*) as play_count, MAX(score) as max_score FROM play_records WHERE mb_id = ? GROUP BY game_type`

2. **์ „์ฒด ํ”Œ๋ ˆ์ด ๊ธฐ๋ก ํ…Œ์ด๋ธ”**
   - ๊ฒŒ์ž„ ํƒ€์ž…, ๊ฒฐ๊ณผ, ์ ์ˆ˜, ๋‚ ์งœ ํ‘œ์‹œ
   - ๊ฐ ํ–‰์— "์‚ญ์ œ" ๋ฒ„ํŠผ (AJAX๋กœ DELETE ์š”์ฒญ)

3. **์‚ญ์ œ ๊ธฐ๋Šฅ ๊ตฌํ˜„**
   - POST action=delete, id={record_id}
   - `DELETE FROM play_records WHERE id = ? AND mb_id = ?` (๋ณธ์ธ ๊ธฐ๋ก๋งŒ ์‚ญ์ œ ๊ฐ€๋Šฅ)
   - ์‚ญ์ œ ํ›„ ํ•ด๋‹น ํ–‰ fade-out ์• ๋‹ˆ๋ฉ”์ด์…˜

**UX ์ธํ„ฐ๋ž™์…˜ ๊ฐ•ํ™”:**

1. **์ฃผ์‚ฌ์œ„ ๊ตด๋ฆฌ๊ธฐ ์‚ฌ์šด๋“œ ํšจ๊ณผ (์„ ํƒ ์‚ฌํ•ญ)**
   - Web Audio API๋กœ ๊ฐ„๋‹จํ•œ "ํƒ" ์†Œ๋ฆฌ ์žฌ์ƒ
   - ๋˜๋Š” `<audio>` ํƒœ๊ทธ + ๋ฌด๋ฃŒ ํšจ๊ณผ์Œ ํŒŒ์ผ

2. **๊ฒฐ๊ณผ ๋ชจ๋‹ฌ ํŒ์—…**
   - ์ฃผ์‚ฌ์œ„ ๊ฒฐ๊ณผ๊ฐ€ ๋‚˜์˜ค๋ฉด ํ™”๋ฉด ์ค‘์•™์— ๋ชจ๋‹ฌ ํ‘œ์‹œ
   - "๋‹ค์‹œ ํ•˜๊ธฐ" / "๊ธฐ๋ก ๋ณด๊ธฐ" ๋ฒ„ํŠผ
   - ๋ชจ๋‹ฌ ์™ธ๋ถ€ ํด๋ฆญ ์‹œ ๋‹ซ๊ธฐ

3. **ํ˜ธ๋ฒ„ ์‹œ ์นด๋“œ ํ‹ธํŠธ ํšจ๊ณผ**
   - ๋ฉ”์ธ ํŽ˜์ด์ง€ ๊ฒŒ์ž„ ์นด๋“œ์— ๋งˆ์šฐ์Šค ์˜ฌ๋ฆฌ๋ฉด 3D ํ‹ธํŠธ
   - vanilla-tilt.js ๋˜๋Š” ์ˆœ์ˆ˜ CSS transform ์‚ฌ์šฉ

4. **๋ฌดํ•œ ์Šคํฌ๋กค (ํ†ต๊ณ„ ํŽ˜์ด์ง€)**
   - Intersection Observer API๋กœ ์Šคํฌ๋กค ๋ ๊ฐ์ง€
   - ์ถ”๊ฐ€ ๋ฐ์ดํ„ฐ AJAX ๋กœ๋“œ

๋ชจ๋“  ํŽ˜์ด์ง€์— modern shell ์ ์šฉ ์œ ์ง€ํ•˜๊ณ , ๋‹คํฌ๋ชจ๋“œ์—์„œ๋„ ์ž˜ ๋ณด์ด๋„๋ก ์ƒ‰์ƒ ํ† ํฐ ์‚ฌ์šฉํ•˜์„ธ์š”.

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: ๊ฐœ์ธ ๊ธฐ๋ก ํŽ˜์ด์ง€๊ฐ€ ์™„์„ฑ๋˜์–ด ๋กœ๊ทธ์ธํ•œ ์‚ฌ์šฉ์ž๋Š” ์ž์‹ ์˜ ๊ฒŒ์ž„๋ณ„ ํ†ต๊ณ„์™€ ์ „์ฒด ํ”Œ๋ ˆ์ด ๊ธฐ๋ก์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ธฐ๋ก ์‚ญ์ œ ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜๋ฉด AJAX๋กœ ์‚ญ์ œ๋˜๊ณ  ํ•ด๋‹น ํ–‰์ด fade-out๋ฉ๋‹ˆ๋‹ค. ์ฃผ์‚ฌ์œ„ ๊ฒฐ๊ณผ๊ฐ€ ๋‚˜์˜ฌ ๋•Œ ๋ชจ๋‹ฌ ํŒ์—…์ด ํ‘œ์‹œ๋˜๊ณ , ๋ฉ”์ธ ํŽ˜์ด์ง€ ๊ฒŒ์ž„ ์นด๋“œ์— ๋งˆ์šฐ์Šค๋ฅผ ์˜ฌ๋ฆฌ๋ฉด 3D ํ‹ธํŠธ ํšจ๊ณผ๊ฐ€ ์ ์šฉ๋ฉ๋‹ˆ๋‹ค. ํ†ต๊ณ„ ํŽ˜์ด์ง€์—์„œ ์Šคํฌ๋กค์„ ๋‚ด๋ฆฌ๋ฉด ์ถ”๊ฐ€ ๊ธฐ๋ก์ด ์ž๋™์œผ๋กœ ๋กœ๋“œ๋ฉ๋‹ˆ๋‹ค.

7
Step 7
**Step 7: ์ตœ์ข… ๋งˆ๋ฌด๋ฆฌ & ์„ ํƒ ๊ธฐ๋Šฅ ๊ตฌํ˜„**

**์ตœ์ข… ์ ๊ฒ€ ์‚ฌํ•ญ:**

1. **๋ชจ๋“  ํŽ˜์ด์ง€ modern shell ์ ์šฉ ํ™•์ธ**
   - `_head.inc.php`, `m-shell`, `_nav.inc.php`, `m-container`, `_footer.inc.php` ํฌํ•จ ์—ฌ๋ถ€
   - ๋””์ž์ธ ํ† ํฐ (`var(--m-surface)` ๋“ฑ) ์‚ฌ์šฉ ์—ฌ๋ถ€

2. **SQLite prepared statement ํ™•์ธ**
   - ๋ชจ๋“  SQL ์ฟผ๋ฆฌ๊ฐ€ `->prepare()` + `->execute([])` ๋ฐฉ์‹ ์‚ฌ์šฉ
   - ์‚ฌ์šฉ์ž ์ž…๋ ฅ์ด ์ง์ ‘ ์ฟผ๋ฆฌ์— ๋“ค์–ด๊ฐ€์ง€ ์•Š๋„๋ก

3. **XSS ๋ฐฉ์ง€ ํ™•์ธ**
   - ๋ชจ๋“  ์‚ฌ์šฉ์ž ๋ฐ์ดํ„ฐ ์ถœ๋ ฅ ์‹œ `htmlspecialchars()` ์‚ฌ์šฉ

4. **๋‹คํฌ๋ชจ๋“œ ํ…Œ์ŠคํŠธ**
   - ์‚ฌ์ดํŠธ ์ƒ๋‹จ ๋‹คํฌ๋ชจ๋“œ ํ† ๊ธ€ ๋ฒ„ํŠผ ํด๋ฆญ ํ›„ ๋ชจ๋“  ๊ฒŒ์ž„ ํŽ˜์ด์ง€ ํ™•์ธ
   - ํ…์ŠคํŠธ/๋ฐฐ๊ฒฝ์ด ์ž˜ ๋ณด์ด๋Š”์ง€ ๊ฒ€์ฆ

**์„ ํƒ ๊ธฐ๋Šฅ ๊ตฌํ˜„ (๋ณด๋„ˆ์Šค):**

1. **์ฃผ์‚ฌ์œ„ ์Šคํ‚จ ์ปค์Šคํ„ฐ๋งˆ์ด์ง•**
   - ์„ค์ • ํŽ˜์ด์ง€ (`/games/settings`) ์ถ”๊ฐ€
   - ์ฃผ์‚ฌ์œ„ ์ƒ‰์ƒ(๋นจ๊ฐ•, ํŒŒ๋ž‘, ์ดˆ๋ก ๋“ฑ) ์„ ํƒ
   - localStorage์— ์ €์žฅํ•˜๊ณ  ๋ชจ๋“  ๊ฒŒ์ž„์— ์ ์šฉ

2. **์‹ค์‹œ๊ฐ„ ์ˆœ์œ„ํ‘œ**
   - `/games/leaderboard` ํŽ˜์ด์ง€ ์ถ”๊ฐ€
   - ์˜ค๋Š˜์˜ ์ตœ๊ณ  ์ ์ˆ˜ TOP 10 (๊ฒŒ์ž„๋ณ„)
   - ์ž๋™ ์ƒˆ๋กœ๊ณ ์นจ (30์ดˆ๋งˆ๋‹ค)

3. **์นœ๊ตฌ ์ดˆ๋Œ€ ๋งํฌ**
   - ๋ฐฐํ‹€ ๊ฒŒ์ž„์—์„œ "์นœ๊ตฌ ์ดˆ๋Œ€" ๋ฒ„ํŠผ
   - ๊ณ ์œ  ์„ธ์…˜ ID ์ƒ์„ฑํ•˜์—ฌ URL์— ํฌํ•จ (`/games/battle?session=abc123`)
   - ๊ฐ™์€ ์„ธ์…˜ ID๋กœ ์ ‘์†ํ•œ ์‚ฌ์šฉ์ž๋ผ๋ฆฌ ๊ฒฐ๊ณผ ๊ณต์œ 

4. **๋„์ „ ๊ณผ์ œ ์‹œ์Šคํ…œ**
   - "์ฒซ ๋Ÿญํ‚ค์„ธ๋ธ ๋‹ฌ์„ฑ", "๋ˆ„์  ๊ฒŒ์ž„ 100ํšŒ ํ”Œ๋ ˆ์ด" ๋“ฑ ๋ฑƒ์ง€
   - ๋‹ฌ์„ฑ ์‹œ ๋ชจ๋‹ฌ ํŒ์—…์œผ๋กœ ์ถ•ํ•˜
   - ๋‚ด ๊ธฐ๋ก ํŽ˜์ด์ง€์— ๋ฑƒ์ง€ ๋ชฉ๋ก ํ‘œ์‹œ

**๋ฐฐํฌ ์ „ ์ฒดํฌ๋ฆฌ์ŠคํŠธ:**

- [ ] ๋ชจ๋“  ๊ฒŒ์ž„ ํŽ˜์ด์ง€ ์ •์ƒ ์ž‘๋™
- [ ] AJAX ์š”์ฒญ ์—๋Ÿฌ ํ•ธ๋“ค๋ง
- [ ] ๋ชจ๋ฐ”์ผ ๋ฐ˜์‘ํ˜• ํ™•์ธ (๋ฏธ๋””์–ด ์ฟผ๋ฆฌ)
- [ ] ๋กœ๋”ฉ ์Šคํ”ผ๋„ˆ ์ถ”๊ฐ€ (AJAX ์š”์ฒญ ์ค‘)
- [ ] 404 ํŽ˜์ด์ง€ ์ฒ˜๋ฆฌ (์กด์žฌํ•˜์ง€ ์•Š๋Š” ๊ฒŒ์ž„ ๊ฒฝ๋กœ)
- [ ] ์„ฑ๋Šฅ ์ตœ์ ํ™” (์ฟผ๋ฆฌ ์ธ๋ฑ์Šค, ์ด๋ฏธ์ง€ ์••์ถ•)

์ถ•ํ•˜ํ•ฉ๋‹ˆ๋‹ค! ๐ŸŽ‰ ๊ทธ๋ˆ„๋ณด๋“œ5 SE ์œ„์—์„œ ๋™์ž‘ํ•˜๋Š” ์™„์ „ํ•œ ์ฃผ์‚ฌ์œ„ ๊ฒŒ์ž„ ์ปฌ๋ ‰์…˜ ํ”Œ๋žซํผ์ด ์™„์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: ๋ชจ๋“  ํŽ˜์ด์ง€๊ฐ€ modern shell๋กœ ํ†ต์ผ๋˜๊ณ , ๋‹คํฌ๋ชจ๋“œ๊ฐ€ ์ •์ƒ ์ž‘๋™ํ•˜๋ฉฐ, SQLite ๋ณด์•ˆ์ด ๊ฐ•ํ™”๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์„ ํƒ ๊ธฐ๋Šฅ ์ค‘ ์ผ๋ถ€๋ฅผ ๊ตฌํ˜„ํ•˜๋ฉด ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์ด ๋”์šฑ ํ’๋ถ€ํ•ด์ง‘๋‹ˆ๋‹ค. ์ตœ์ข…์ ์œผ๋กœ 6๊ฐœ ์ด์ƒ์˜ ์ฃผ์‚ฌ์œ„ ๊ฒŒ์ž„, ํ†ต๊ณ„ ๋Œ€์‹œ๋ณด๋“œ, ๊ฐœ์ธ ๊ธฐ๋ก ํŽ˜์ด์ง€๊ฐ€ ๋ชจ๋‘ ์—ฐ๋™๋˜์–ด ์ž‘๋™ํ•˜๋Š” ์™„์ „ํ•œ ํ”Œ๋žซํผ์ด ์™„์„ฑ๋ฉ๋‹ˆ๋‹ค.

์ œ์ถœ๋œ ์ž‘ํ’ˆ (0)

๋กœ๊ทธ์ธ ํ›„ ์ œ์ถœ

์•„์ง ์ œ์ถœ๋œ ์ž‘ํ’ˆ์ด ์—†์Šต๋‹ˆ๋‹ค. ์ฒซ ๋ฒˆ์งธ ๋„์ „์ž๊ฐ€ ๋˜์–ด๋ณด์„ธ์š”!

๋Œ“๊ธ€ (0)

๋กœ๊ทธ์ธ ํ›„ ๋Œ“๊ธ€์„ ๋‚จ๊ธธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
์•„์ง ๋Œ“๊ธ€์ด ์—†์Šต๋‹ˆ๋‹ค. ์ฒซ ๋ฒˆ์งธ ๋Œ“๊ธ€์„ ๋‚จ๊ฒจ๋ณด์„ธ์š”!