๐ฎ ๋ฏธ๋ ๊ฒ์ ์์ผ์ด๋ - ๋ธ๋ผ์ฐ์ ์บ์ฃผ์ผ ๊ฒ์ ๋ชจ์
mini-game-arcade
๋ฌธ์ ์ค๋ช
๐ฏ ํ๋ก์ ํธ ๊ฐ์
๋ธ๋ผ์ฐ์ ์์ ์ฆ๊ธฐ๋ ๊ฐ๋จํ ์บ์ฃผ์ผ ๊ฒ์ 3์ข ์ ํ ํ์ด์ง์ ๋ชจ์ ๋ฏธ๋ ์์ผ์ด๋์ ๋๋ค. ์ฌ์ฉ์๋ ๊ฒ์์ ์ ํํ๊ณ , ์ ์๋ฅผ ๊ธฐ๋กํ๋ฉฐ, ์์ ์ ์ต๊ณ ๊ธฐ๋ก๊ณผ ์ ์ฒด ๋ญํน์ ํ์ธํ ์ ์์ต๋๋ค.
โจ ํ์ ๊ธฐ๋ฅ
1. ๊ฒ์ ์ ํ ํ๋ฉด
- 3๊ฐ์ง ๊ฒ์ ์นด๋ ๊ทธ๋ฆฌ๋ ๋ ์ด์์ (์์ ์นด๋ ๊ธฐ์ตํ๊ธฐ, ์ซ์ ์์ ๋ง์ถ๊ธฐ, ๋ฐ์์๋ ํ ์คํธ)
- ๊ฐ ๊ฒ์ ์นด๋์ ์์ด์ฝ, ์ ๋ชฉ, ๊ฐ๋จํ ์ค๋ช , ์ต๊ณ ๊ธฐ๋ก ํ์
- ์นด๋ ํธ๋ฒ ์ ์ ๋๋ฉ์ด์ ํจ๊ณผ
- ๋คํฌ๋ชจ๋ ์๋ ๋์ (modern shell ๋์์ธ ํ ํฐ ํ์ฉ)
2. ๊ฒ์ #1: ์์ ์นด๋ ๊ธฐ์ตํ๊ธฐ
- 3x3 ๊ทธ๋ฆฌ๋์ 9๊ฐ์ ์์ ์นด๋๊ฐ 2์ด๊ฐ ํ์๋จ
- ์นด๋๊ฐ ๋ค์งํ ํ ์ฌ์ฉ์๊ฐ ์์๋๋ก ํด๋ฆญ
- ์ ๋ต ๊ฐ์๋ฅผ ์ ์๋ก ๊ธฐ๋ก
- ๊ฒ์ ์ข ๋ฃ ํ ๊ฒฐ๊ณผ ๋ชจ๋ฌ ํ์
3. ๊ฒ์ #2: ์ซ์ ์์ ๋ง์ถ๊ธฐ
- 1๋ถํฐ ์์ํ๋ ์ซ์ ๋ฒํผ์ด ๋๋ค ์์น์ ๋ฐฐ์น
- ์ฌ์ฉ์๊ฐ ์์๋๋ก ํด๋ฆญํด์ผ ํจ (1โ2โ3...)
- ์ ํ ์๊ฐ 30์ด ๋ด์ ์ต๋ํ ๋ง์ ์ซ์ ํด๋ฆญ
- ํด๋ฆญํ ์ซ์ ๊ฐ์๊ฐ ์ ์
4. ๊ฒ์ #3: ๋ฐ์์๋ ํ ์คํธ
- ํ๋ฉด์ด ๋นจ๊ฐ์์์ ์ด๋ก์์ผ๋ก ๋ณํ๋ ์๊ฐ ํด๋ฆญ
- ๋ฐ์ ์๊ฐ(ms)์ ์ธก์ ํ์ฌ ์ ์๋ก ๊ธฐ๋ก (๋ฎ์์๋ก ์ข์)
- 3ํ ์ธก์ ํ ํ๊ท ๊ฐ ๊ณ์ฐ
- ๋๋ฌด ๋นจ๋ฆฌ ํด๋ฆญํ๋ฉด "๋ถ์ ์ถ๋ฐ" ๊ฒฝ๊ณ
5. ์ ์ ๊ธฐ๋ก ์์คํ
- ๊ฒ์ ์ข ๋ฃ ์ ๋๋ค์ ์ ๋ ฅ ํผ
- SQLite DB์ ๊ฒ์๋ณ ์ ์ ์ ์ฅ (๋๋ค์, ์ ์, ํ๋ ์ด ์ผ์)
- ๊ฐ์ธ ์ต๊ณ ๊ธฐ๋ก localStorage์ ์บ์ฑ
- ๊ฒ์ ์ ํ ํ๋ฉด์ ๋ด ์ต๊ณ ๊ธฐ๋ก ํ์
6. ๋ญํน ๋ณด๋
- ๊ฐ ๊ฒ์๋ณ Top 10 ๋ญํน ํ์
- ํ ์ด๋ธ ํ์์ผ๋ก ์์, ๋๋ค์, ์ ์, ๋ ์ง ํ์
- ๋ด ๊ธฐ๋ก์ ํ์ด๋ผ์ดํธ ์ฒ๋ฆฌ
- ๊ฒ์ ์ ํ ํ๋ฉด ํ๋จ์ ํญ์ผ๋ก 3๊ฐ ๊ฒ์ ๋ญํน ์ ํ
๐ ์ ํ ์ถ๊ฐ ๊ธฐ๋ฅ (๋ณด๋์ค)
- ๊ฒ์ ํ๋ ์ด ์ค ๋ฐฐ๊ฒฝ์์ /ํจ๊ณผ์ (Web Audio API)
- ๋์ด๋ ์ ํ (์ฌ์/๋ณดํต/์ด๋ ค์)
- ์ผ์ผ ์ฑ๋ฆฐ์ง ๋ชจ๋ (ํ๋ฃจ 1ํ๋ง ์ ์ ๊ธฐ๋ก ๊ฐ๋ฅ, ๋ ๋์ ๋ณด์)
- ์ ์ ์์คํ (์ฒซ ํ๋ ์ด, 10ํ ํ๋ ์ด, 1๋ฑ ๋ฌ์ฑ ๋ฑ)
- ์น๊ตฌ ์ด๋ ๋งํฌ ์์ฑ (์ ์ ๋๊ฒฐ ๋ชจ๋)
- ๊ฒ์ ํ๋ ์ด ํต๊ณ ์ฐจํธ (Chart.js๋ก ์๊ฐ๋๋ณ ์ ์ ์ถ์ด)
๐จ ๋์์ธ ์๊ตฌ์ฌํญ
- modern shell ํตํฉ (nav, footer, ๋คํฌ๋ชจ๋ ํ ํฐ ์ฌ์ฉ)
- ๊ฒ์ ์นด๋๋
var(--m-surface)๋ฐฐ๊ฒฝ์var(--m-shadow)๊ทธ๋ฆผ์ - ๊ฒ์๋ณ ๋ธ๋๋ ์ปฌ๋ฌ (์์ ๊ธฐ์ต: #FF6B6B, ์ซ์ ์์: #4ECDC4, ๋ฐ์์๋: #FFE66D)
- ๋ฒํผ ํธ๋ฒ/ํด๋ฆญ ์ ์ค์ผ์ผ ์ ๋๋ฉ์ด์
- ์ ์ ์นด์ดํฐ๋ ์ ๋๋ฉ์ด์ ํจ๊ณผ์ ํจ๊ป ์ฆ๊ฐ
- ๋ชจ๋ฐ์ผ ๋ฐ์ํ (๊ฒ์ ๊ทธ๋ฆฌ๋ ํฌ๊ธฐ ์๋ ์กฐ์ )
๐ ๊ธฐ์ ์คํ
- PHP 8.x + ๊ทธ๋๋ณด๋5 SE (modern shell)
- SQLite (๊ฒ์๋ณ ์ ์ ํ ์ด๋ธ)
- Vanilla JavaScript (๊ฒ์ ๋ก์ง, ํ์ด๋จธ, ์ ๋๋ฉ์ด์ )
- CSS3 (Grid, Flexbox, Transitions)
- localStorage (๊ฐ์ธ ์ต๊ณ ๊ธฐ๋ก ์บ์ฑ)
์ ์ฝ ์กฐ๊ฑด
- โข ๊ทธ๋๋ณด๋5 SE ์ค์น๋ณธ ์์์ ์์ (app/modules/mini-game-arcade/index.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/mini-game-arcade.sqlite) โ g5se ๋ฉ์ธ DB ๊ฑด๋๋ฆฌ์ง ๋ง ๊ฒ
- โข ํด๋ ๊ธฐ๋ฐ ์๋ ๋ผ์ฐํ ํ์ฉ (app/router.php ์์ ๋ถํ์)
- โข prepared statement๋ก SQL ์ธ์ ์ ๋ฐฉ์ง
- โข ์ฌ์ฉ์ ์ ๋ ฅ์ htmlspecialchars()๋ก ์ด์ค์ผ์ดํ
- โข ์ธ๋ถ ๋น๋ ๋๊ตฌ(npm/vite) ์ฌ์ฉ ๊ธ์ง โ ์์ HTML/CSS/JS
- โข CDN ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ต์ํ (Chart.js ๋ฑ ๋ณด๋์ค ๊ธฐ๋ฅ์๋ง ํ์ฉ)
- โข ๋ชจ๋ฐ์ผ ๋ฐ์ํ ๋์์ธ (๋ฏธ๋์ด ์ฟผ๋ฆฌ ํ์ฉ)
ํ๋กฌํํธ ๋ ์ํผ
์๋ ํ๋กฌํํธ๋ฅผ ์์๋๋ก AI์๊ฒ ๋ณด๋ด๋ฉด ๋ฉ๋๋ค. ๋ณต์ฌ ๋ฒํผ์ ๋๋ฌ ๋ฐ๋ก ์ฌ์ฉํ์ธ์.
g5se๊ฐ ์ค์น๋ ๋๋ ํฐ๋ฆฌ(๋ฃจํธ์ `index.php`ยท`app/`์ด ์๋ ๊ณณ)์์ ์์
์ ์์ํฉ๋๋ค. (g5se ์ค์น๋ https://github.com/gnuboard/g5se ์ฐธ๊ณ )
**Step 1: ํ๋ก์ ํธ ๊ธฐ๋ณธ ๊ตฌ์กฐ ๋ฐ ๊ฒ์ ์ ํ ํ๋ฉด ๊ตฌํ**
1. `app/modules/mini-game-arcade/index.php` ํ์ผ์ ์์ฑํ๊ณ modern shell ๊ณจ๊ฒฉ์ ์ ์ฉํ์ธ์.
2. ํ์ด์ง ์๋จ์ "๐ฎ ๋ฏธ๋ ๊ฒ์ ์์ผ์ด๋" ์ ๋ชฉ์ ํ์ํ์ธ์.
3. 3๊ฐ์ ๊ฒ์ ์นด๋๋ฅผ ๊ทธ๋ฆฌ๋ ๋ ์ด์์(3์ด, ๋ชจ๋ฐ์ผ์์๋ 1์ด)์ผ๋ก ๋ฐฐ์นํ์ธ์:
- ๊ฒ์ #1: ์์ ์นด๋ ๊ธฐ์ตํ๊ธฐ (์์ด์ฝ: ๐จ, ๋ธ๋๋ ์ปฌ๋ฌ: #FF6B6B)
- ๊ฒ์ #2: ์ซ์ ์์ ๋ง์ถ๊ธฐ (์์ด์ฝ: ๐ข, ๋ธ๋๋ ์ปฌ๋ฌ: #4ECDC4)
- ๊ฒ์ #3: ๋ฐ์์๋ ํ
์คํธ (์์ด์ฝ: โก, ๋ธ๋๋ ์ปฌ๋ฌ: #FFE66D)
4. ๊ฐ ์นด๋์๋ ์์ด์ฝ, ์ ๋ชฉ, ๊ฐ๋จํ ์ค๋ช
(2์ค), "ํ๋ ์ด" ๋ฒํผ, "๋ด ์ต๊ณ ๊ธฐ๋ก: -" ํ
์คํธ๋ฅผ ํฌํจํ์ธ์.
5. ์นด๋๋ `var(--m-surface)` ๋ฐฐ๊ฒฝ, `var(--m-shadow)` ๊ทธ๋ฆผ์, `var(--m-radius-lg)` ๋ผ์ด๋ ์ฒ๋ฆฌํ์ธ์.
6. ์นด๋ ํธ๋ฒ ์ `transform: translateY(-4px)` ์ ๋๋ฉ์ด์
๊ณผ ๊ทธ๋ฆผ์ ๊ฐํ ํจ๊ณผ๋ฅผ ์ถ๊ฐํ์ธ์.
7. "ํ๋ ์ด" ๋ฒํผ ํด๋ฆญ ์ ํด๋น ๊ฒ์ ์์ญ์ด ํ์๋๋๋ก JavaScript๋ก ํ๋ฉด ์ ํ ๋ก์ง์ ์ค๋นํ์ธ์ (์์ง ๊ฒ์ ๋ก์ง์ ๊ตฌํํ์ง ์์, `showGame(gameId)` ํจ์๋ง ์ ์).
8. modern shell์ ๋์์ธ ํ ํฐ์ ํ์ฉํ์ฌ ๋คํฌ๋ชจ๋์์๋ ์์ฐ์ค๋ฝ๊ฒ ๋ณด์ด๋๋ก ํ์ธ์.
9. ๋ชจ๋ฐ์ผ ๋ฐ์ํ์ ์ํด ๋ฏธ๋์ด ์ฟผ๋ฆฌ๋ฅผ ์ถ๊ฐํ์ธ์ (`@media (max-width: 768px)`).
modern shell ๊ณจ๊ฒฉ:
```php
<?php
include_once('./_common.php');
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;">
<!-- ํ์ด์ง ๋ณธ๋ฌธ -->
</main>
<?php require G5_THEME_PATH.'/modern/_footer.inc.php'; ?>
</div>
<?php include_once('./tail.sub.php'); ?>
```
์ด ๋จ๊ณ์์๋ ๊ฒ์ ์ ํ ํ๋ฉด๋ง ์์ฑํ๊ณ , ๊ฒ์ ๋ก์ง์ ๋ค์ ๋จ๊ณ์์ ๊ตฌํํฉ๋๋ค.
๊ธฐ๋ ๊ฒฐ๊ณผ: ๊ฒ์ ์ ํ ํ๋ฉด์ด ํ์๋๋ฉฐ, 3๊ฐ์ ๊ฒ์ ์นด๋๊ฐ ๊ทธ๋ฆฌ๋๋ก ๋ฐฐ์น๋ฉ๋๋ค. ์นด๋ ํธ๋ฒ ์ ์ ๋๋ฉ์ด์ ์ด ์๋ํ๊ณ , "ํ๋ ์ด" ๋ฒํผ ํด๋ฆญ ์ ์ฝ์์ ๊ฒ์ ID๊ฐ ์ถ๋ ฅ๋ฉ๋๋ค. ๋คํฌ๋ชจ๋ ์ ํ ์ ๋์์ธ ํ ํฐ์ด ์ ์ฉ๋์ด ์์ฐ์ค๋ฝ๊ฒ ์์์ด ๋ณ๊ฒฝ๋ฉ๋๋ค.
**Step 2: ๊ฒ์ #1 "์์ ์นด๋ ๊ธฐ์ตํ๊ธฐ" ๊ตฌํ** 1. ๊ฒ์ ์ ํ ํ๋ฉด ์๋์ ๊ฒ์ ํ๋ ์ด ์์ญ์ ์ถ๊ฐํ์ธ์ (๊ธฐ๋ณธ์ ์ผ๋ก `display: none`). 2. ๊ฒ์ #1 ์์ญ์๋ ๋ค์์ ํฌํจํ์ธ์: - ๊ฒ์ ์ ๋ชฉ "๐จ ์์ ์นด๋ ๊ธฐ์ตํ๊ธฐ" - ์ค๋ช ํ ์คํธ: "2์ด ๋์ ํ์๋๋ ์์์ ๊ธฐ์ตํ๊ณ ์์๋๋ก ํด๋ฆญํ์ธ์!" - 3x3 ๊ทธ๋ฆฌ๋ (9๊ฐ์ ์นด๋) - "์์" ๋ฒํผ - "๋ค๋ก ๊ฐ๊ธฐ" ๋ฒํผ (๊ฒ์ ์ ํ ํ๋ฉด์ผ๋ก ๋ณต๊ท) 3. JavaScript๋ก ๊ฒ์ ๋ก์ง์ ๊ตฌํํ์ธ์: - "์์" ๋ฒํผ ํด๋ฆญ ์ 9๊ฐ์ ๋๋ค ์์์ ์์ฑํ์ฌ ์นด๋์ ํ์ (2์ด๊ฐ) - 2์ด ํ ๋ชจ๋ ์นด๋๋ฅผ ํ์(#CCCCCC)์ผ๋ก ๋ค์ง๊ธฐ - ์ฌ์ฉ์๊ฐ ์นด๋๋ฅผ ํด๋ฆญํ๋ฉด ์๋ ์์์ด ๋ํ๋๊ณ , ์ ๋ต ์ฌ๋ถ๋ฅผ ์ฒดํฌ - ์ ๋ต์ด๋ฉด ์นด๋๋ฅผ ๊ณ ์ , ์ค๋ต์ด๋ฉด ๋ค์ ํ์์ผ๋ก (์ง๋ ์ ๋๋ฉ์ด์ ) - 9๊ฐ ๋ชจ๋ ๋ง์ถ๋ฉด ๊ฒฐ๊ณผ ๋ชจ๋ฌ ํ์ (์ ์ = ์ ๋ต ๊ฐ์) 4. ์์ ๋ฐฐ์ด์ `['#FF6B6B', '#4ECDC4', '#FFE66D', '#95E1D3', '#F38181', '#AA96DA', '#FCBAD3', '#A8D8EA', '#FFAAA5']` ์ค ๋๋ค ์ ํํ์ธ์. 5. ์นด๋ ํด๋ฆญ ์ `transform: scale(0.95)` ์ ๋๋ฉ์ด์ ์ ์ถ๊ฐํ์ธ์. 6. ๊ฒฐ๊ณผ ๋ชจ๋ฌ์๋ "์ ์: X/9", "๋๋ค์ ์ ๋ ฅ" ํผ, "์ ์ฅ" ๋ฒํผ์ ํฌํจํ์ธ์ (์์ง DB ์ ์ฅ์ ๊ตฌํํ์ง ์์). 7. ๊ฒ์ ํ๋ ์ด ์ค์๋ "์์" ๋ฒํผ์ ๋นํ์ฑํํ์ธ์. ๊ฒ์ ๋ก์ง ํ๋ฆ: 1. ์ฌ์ฉ์๊ฐ "์์" ํด๋ฆญ 2. 9๊ฐ ๋๋ค ์์ ์์ฑ ๋ฐ 2์ด๊ฐ ํ์ 3. ์นด๋ ๋ค์ง๊ธฐ (ํ์) 4. ์ฌ์ฉ์ ํด๋ฆญ ๋๊ธฐ โ ์ ๋ต ์ฒดํฌ โ 9๊ฐ ์๋ฃ ์ ๊ฒฐ๊ณผ ๋ชจ๋ฌ ์ด ๋จ๊ณ์์๋ ๊ฒ์ #1๋ง ์์ฑํ๊ณ , ์ ์ ์ ์ฅ ๊ธฐ๋ฅ์ ๋ค์ ๋จ๊ณ์์ ์ถ๊ฐํฉ๋๋ค.
๊ธฐ๋ ๊ฒฐ๊ณผ: ๊ฒ์ #1์ ํ๋ ์ดํ ์ ์์ต๋๋ค. "์์" ๋ฒํผ ํด๋ฆญ ์ ์์ ์นด๋๊ฐ 2์ด๊ฐ ํ์๋๊ณ , ๋ค์งํ ํ ์ฌ์ฉ์๊ฐ ์์๋๋ก ํด๋ฆญํ ์ ์์ต๋๋ค. ์ ๋ต/์ค๋ต์ ๋ฐ๋ผ ์นด๋๊ฐ ๊ณ ์ ๋๊ฑฐ๋ ๋ค์ ๋ค์งํ๋ฉฐ, ๋ชจ๋ ์นด๋๋ฅผ ๋ง์ถ๋ฉด ๊ฒฐ๊ณผ ๋ชจ๋ฌ์ด ํ์๋ฉ๋๋ค.
**Step 3: ๊ฒ์ #2 "์ซ์ ์์ ๋ง์ถ๊ธฐ" ๋ฐ ๊ฒ์ #3 "๋ฐ์์๋ ํ ์คํธ" ๊ตฌํ** ### ๊ฒ์ #2: ์ซ์ ์์ ๋ง์ถ๊ธฐ 1. ๊ฒ์ #2 ์์ญ์ ์ถ๊ฐํ์ธ์ (๊ฒ์ #1๊ณผ ๋์ผํ ๊ตฌ์กฐ). 2. ๊ฒ์ ์ ๋ชฉ "๐ข ์ซ์ ์์ ๋ง์ถ๊ธฐ", ์ค๋ช : "30์ด ์์ 1๋ถํฐ ์์๋๋ก ์ต๋ํ ๋ง์ ์ซ์๋ฅผ ํด๋ฆญํ์ธ์!" 3. ๊ฒ์ ํ๋ ์ด ์์ญ: - ํ์ด๋จธ ํ์ (๋จ์ ์๊ฐ: 30์ด) - ์ซ์ ๋ฒํผ 10๊ฐ๊ฐ ๋๋ค ์์น์ ๋ฐฐ์น (absolute position) - "์์" ๋ฒํผ, "๋ค๋ก ๊ฐ๊ธฐ" ๋ฒํผ 4. JavaScript ๋ก์ง: - "์์" ํด๋ฆญ ์ 1~10 ์ซ์ ๋ฒํผ์ด ๋๋ค ์์น์ ์์ฑ - ์ฌ์ฉ์๋ 1๋ถํฐ ์์๋๋ก ํด๋ฆญํด์ผ ํจ (์๋ชป๋ ์์ ํด๋ฆญ ์ ๋ฌด์) - ์ ๋ต ํด๋ฆญ ์ ๋ฒํผ์ด ์ฌ๋ผ์ง๊ณ ๋ค์ ์ซ์๊ฐ ํ์ฑํ - 30์ด ํ์ด๋จธ๊ฐ ๋๋๋ฉด ๊ฒ์ ์ข ๋ฃ, ์ ์ = ํด๋ฆญํ ์ซ์ ๊ฐ์ - ๊ฒฐ๊ณผ ๋ชจ๋ฌ ํ์ (๋๋ค์ ์ ๋ ฅ ํผ ํฌํจ) 5. ํ์ด๋จธ๋ `setInterval`๋ก 1์ด๋ง๋ค ๊ฐฑ์ ํ๊ณ , 0์ด๊ฐ ๋๋ฉด ๊ฒ์ ์ข ๋ฃ 6. ์ซ์ ๋ฒํผ์ ๊ฒ์ ์์ญ(500px x 500px) ๋ด ๋๋ค ์์น์ ๋ฐฐ์น (๊ฒน์น์ง ์๋๋ก ์ต์ ๊ฐ๊ฒฉ ์ ์ง) ### ๊ฒ์ #3: ๋ฐ์์๋ ํ ์คํธ 1. ๊ฒ์ #3 ์์ญ์ ์ถ๊ฐํ์ธ์. 2. ๊ฒ์ ์ ๋ชฉ "โก ๋ฐ์์๋ ํ ์คํธ", ์ค๋ช : "ํ๋ฉด์ด ์ด๋ก์์ผ๋ก ๋ณํ๋ฉด ์ต๋ํ ๋นจ๋ฆฌ ํด๋ฆญํ์ธ์!" 3. ๊ฒ์ ํ๋ ์ด ์์ญ: - ํฐ ํด๋ฆญ ์์ญ (600px x 400px, ์ด๊ธฐ ๋ฐฐ๊ฒฝ: #FF6B6B ๋นจ๊ฐ์) - ์๋ด ํ ์คํธ: "ํด๋ฆญํด์ ์์ํ์ธ์" - 3ํ ์ธก์ ํ ํ๊ท ๋ฐ์ ์๊ฐ ํ์ - "์์" ๋ฒํผ, "๋ค๋ก ๊ฐ๊ธฐ" ๋ฒํผ 4. JavaScript ๋ก์ง: - ํด๋ฆญ ์์ญ ํด๋ฆญ ์ ๋๋ค ์๊ฐ(2~5์ด) ํ ๋ฐฐ๊ฒฝ์ด ์ด๋ก์(#4ECDC4)์ผ๋ก ๋ณ๊ฒฝ - ์ด๋ก์์ผ๋ก ๋ณํ๋ ์๊ฐ๋ถํฐ ํด๋ฆญ๊น์ง์ ์๊ฐ์ ๋ฐ๋ฆฌ์ด(ms)๋ก ์ธก์ - ๋นจ๊ฐ์์ผ ๋ ํด๋ฆญํ๋ฉด "๋ถ์ ์ถ๋ฐ! ์ด๋ก์์ด ๋ ๋๊น์ง ๊ธฐ๋ค๋ฆฌ์ธ์" ๊ฒฝ๊ณ - 3ํ ์ธก์ ํ ํ๊ท ๊ฐ ๊ณ์ฐ, ๊ฒฐ๊ณผ ๋ชจ๋ฌ ํ์ (์ ์ = ํ๊ท ms) 5. ๊ฐ ์ธก์ ํ "๋ค์ ๋ผ์ด๋" ๋ฒํผ ํ์, ํด๋ฆญ ์ ๋ค์ ์ธก์ ์์ ์ด ๋จ๊ณ์์๋ ๊ฒ์ #2, #3๋ฅผ ์์ฑํ๊ณ , ๋ชจ๋ ๊ฒ์์ ๊ฒฐ๊ณผ ๋ชจ๋ฌ์ ๋๋ค์ ์ ๋ ฅ ํผ์ ํฌํจํ์ธ์ (์์ง DB ์ ์ฅ์ ๊ตฌํํ์ง ์์).
๊ธฐ๋ ๊ฒฐ๊ณผ: ๊ฒ์ #2์ #3๋ฅผ ํ๋ ์ดํ ์ ์์ต๋๋ค. ๊ฒ์ #2๋ 30์ด ํ์ด๋จธ์ ํจ๊ป ์ซ์๋ฅผ ์์๋๋ก ํด๋ฆญํ๋ฉฐ, ๊ฒ์ #3๋ ๋นจ๊ฐ์์์ ์ด๋ก์์ผ๋ก ๋ณํ๋ ์๊ฐ์ ๋ฐ์์๋๋ฅผ ์ธก์ ํฉ๋๋ค. ๊ฐ ๊ฒ์ ์ข ๋ฃ ์ ๊ฒฐ๊ณผ ๋ชจ๋ฌ์ด ํ์๋ฉ๋๋ค.
**Step 4: SQLite DB ์ฐ๋ ๋ฐ ์ ์ ์ ์ฅ ๊ธฐ๋ฅ ๊ตฌํ**
1. ํ์ด์ง ์๋จ์ SQLite DB ์ฐ๊ฒฐ ์ฝ๋๋ฅผ ์ถ๊ฐํ์ธ์:
- DB ๊ฒฝ๋ก: `G5_DATA_PATH.'/modules/mini-game-arcade.sqlite'`
- ๋๋ ํฐ๋ฆฌ๊ฐ ์์ผ๋ฉด `@mkdir(G5_DATA_PATH.'/modules', 0707, true);`๋ก ์์ฑ
- `new PDO('sqlite:'.G5_DATA_PATH.'/modules/mini-game-arcade.sqlite')`๋ก ์ฐ๊ฒฐ
2. ์ฒซ ์ ๊ทผ ์ 3๊ฐ์ ํ
์ด๋ธ์ ์๋ ์์ฑํ์ธ์ (๋ง์ด๊ทธ๋ ์ด์
๋ถํ์):
```sql
CREATE TABLE IF NOT EXISTS scores_color (
id INTEGER PRIMARY KEY AUTOINCREMENT,
nickname TEXT NOT NULL,
score INTEGER NOT NULL,
played_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS scores_number (
id INTEGER PRIMARY KEY AUTOINCREMENT,
nickname TEXT NOT NULL,
score INTEGER NOT NULL,
played_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS scores_reaction (
id INTEGER PRIMARY KEY AUTOINCREMENT,
nickname TEXT NOT NULL,
score INTEGER NOT NULL,
played_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
```
3. AJAX ์๋ํฌ์ธํธ๋ฅผ ์ถ๊ฐํ์ธ์ (๊ฐ์ ํ์ผ ๋ด ์๋จ์์ `$_POST` ์ฒดํฌ):
- `action=save_score`์ผ ๋:
- `game_id` (color/number/reaction), `nickname`, `score`๋ฅผ ๋ฐ์
- ๋๋ค์์ htmlspecialchars()๋ก ์ด์ค์ผ์ดํ
- prepared statement๋ก ํด๋น ํ
์ด๋ธ์ INSERT
- ์ฑ๊ณต ์ `{"success": true, "message": "์ ์๊ฐ ์ ์ฅ๋์์ต๋๋ค!"}`๋ฅผ JSON์ผ๋ก ๋ฐํ
- ์คํจ ์ `{"success": false, "message": "์ ์ฅ ์คํจ"}`
4. JavaScript์์ ๊ฒฐ๊ณผ ๋ชจ๋ฌ์ "์ ์ฅ" ๋ฒํผ ํด๋ฆญ ์:
- `fetch('/mini-game-arcade', { method: 'POST', body: formData })`๋ก ์ ์ ์ ์ก
- ์ฑ๊ณต ์ localStorage์ ๊ฐ์ธ ์ต๊ณ ๊ธฐ๋ก ์ ์ฅ (`localStorage.setItem('best_score_color', score)`)
- ์ฑ๊ณต ๋ฉ์์ง ํ์ ํ ๊ฒ์ ์ ํ ํ๋ฉด์ผ๋ก ๋ณต๊ท
5. ํ์ด์ง ๋ก๋ ์ localStorage์์ ๊ฐ์ธ ์ต๊ณ ๊ธฐ๋ก์ ์ฝ์ด์ ๊ฒ์ ์นด๋์ ํ์ํ์ธ์:
```javascript
const bestScoreColor = localStorage.getItem('best_score_color') || '-';
document.querySelector('#card-color .best-score').textContent = `๋ด ์ต๊ณ ๊ธฐ๋ก: ${bestScoreColor}`;
```
์ด ๋จ๊ณ์์๋ ์ ์ ์ ์ฅ ๊ธฐ๋ฅ์ ์์ฑํ๊ณ , ๊ฐ์ธ ์ต๊ณ ๊ธฐ๋ก์ localStorage์ ์บ์ฑํ์ฌ ๊ฒ์ ์ ํ ํ๋ฉด์ ํ์ํฉ๋๋ค.
๊ธฐ๋ ๊ฒฐ๊ณผ: ๊ฒ์ ์ข ๋ฃ ํ ๋๋ค์์ ์ ๋ ฅํ๊ณ "์ ์ฅ" ๋ฒํผ์ ํด๋ฆญํ๋ฉด ์ ์๊ฐ SQLite DB์ ์ ์ฅ๋ฉ๋๋ค. ์ ์ฅ ์ฑ๊ณต ์ localStorage์ ๊ฐ์ธ ์ต๊ณ ๊ธฐ๋ก์ด ๊ฐฑ์ ๋๊ณ , ๊ฒ์ ์ ํ ํ๋ฉด์ ์นด๋์ "๋ด ์ต๊ณ ๊ธฐ๋ก: X"๊ฐ ํ์๋ฉ๋๋ค.
**Step 5: ๋ญํน ๋ณด๋ ๊ตฌํ ๋ฐ UI ์์ฑ**
1. ๊ฒ์ ์ ํ ํ๋ฉด ํ๋จ์ ๋ญํน ๋ณด๋ ์น์
์ ์ถ๊ฐํ์ธ์:
- ์ ๋ชฉ: "๐ ๋ญํน ๋ณด๋"
- 3๊ฐ์ ํญ ๋ฒํผ (์์ ๊ธฐ์ต, ์ซ์ ์์, ๋ฐ์์๋)
- ํญ ํด๋ฆญ ์ ํด๋น ๊ฒ์์ Top 10 ๋ญํน ํ
์ด๋ธ ํ์
2. ๋ญํน ํ
์ด๋ธ:
- ์ปฌ๋ผ: ์์, ๋๋ค์, ์ ์, ๋ ์ง
- ๋ด ๊ธฐ๋ก์ ๋ฐฐ๊ฒฝ์ ํ์ด๋ผ์ดํธ (`background: rgba(255, 107, 107, 0.1)` ๋ฑ ๊ฒ์๋ณ ๋ธ๋๋ ์ปฌ๋ฌ)
- 1์๋ ๊ธ๋ฉ๋ฌ ์์ด์ฝ ๐ฅ, 2์๋ ์๋ฉ๋ฌ ๐ฅ, 3์๋ ๋๋ฉ๋ฌ ๐ฅ
3. PHP์์ ๋ญํน ๋ฐ์ดํฐ๋ฅผ ์กฐํํ์ธ์:
```php
$stmt = $pdo->prepare("SELECT nickname, score, played_at FROM scores_color ORDER BY score DESC LIMIT 10");
$stmt->execute();
$rankingsColor = $stmt->fetchAll(PDO::FETCH_ASSOC);
```
- 3๊ฐ ๊ฒ์ ๋ชจ๋ ๋์ผํ๊ฒ ์กฐํ
- ๋ฐ์์๋๋ `ORDER BY score ASC` (๋ฎ์์๋ก ์ข์)
4. JavaScript๋ก ๋ญํน ๋ฐ์ดํฐ๋ฅผ JSON์ผ๋ก ๋ฐ์ ํ
์ด๋ธ์ ๋์ ์์ฑํ์ธ์:
- ํญ ํด๋ฆญ ์ ํด๋น ๊ฒ์์ ๋ญํน๋ง ํ์
- localStorage์ ๋๋ค์๊ณผ ์ผ์นํ๋ ํ์ ํ์ด๋ผ์ดํธ
5. ๊ฒ์ ์นด๋์ "์ต๊ทผ 1์" ์ ๋ณด๋ฅผ ์ถ๊ฐํ์ธ์ (๊ฐ ๊ฒ์๋ณ 1์ ๋๋ค์๊ณผ ์ ์):
```php
$stmt = $pdo->prepare("SELECT nickname, score FROM scores_color ORDER BY score DESC LIMIT 1");
$stmt->execute();
$topPlayer = $stmt->fetch(PDO::FETCH_ASSOC);
```
6. ๋ชจ๋ฐ์ผ ๋ฐ์ํ ์ต์ข
์ ๊ฒ:
- ๋ญํน ํ
์ด๋ธ์ด ๋ชจ๋ฐ์ผ์์ ๊ฐ๋ก ์คํฌ๋กค ๊ฐ๋ฅํ๋๋ก `overflow-x: auto` ์ ์ฉ
- ๊ฒ์ ํ๋ ์ด ์์ญ์ด ์์ ํ๋ฉด์์๋ ์ ๋ณด์ด๋๋ก ํฌ๊ธฐ ์กฐ์
์ด ๋จ๊ณ์์๋ ๋ญํน ๋ณด๋๋ฅผ ์์ฑํ๊ณ , ์ ์ฒด UI๋ฅผ ๋ค๋ฌ์ด ํ๋ก์ ํธ๋ฅผ ๋ง๋ฌด๋ฆฌํฉ๋๋ค.
๊ธฐ๋ ๊ฒฐ๊ณผ: ๊ฒ์ ์ ํ ํ๋ฉด ํ๋จ์ ๋ญํน ๋ณด๋๊ฐ ํ์๋๋ฉฐ, ํญ ํด๋ฆญ ์ ๊ฐ ๊ฒ์์ Top 10 ๋ญํน์ด ํ ์ด๋ธ๋ก ๋ํ๋ฉ๋๋ค. ๋ด ๊ธฐ๋ก์ ํ์ด๋ผ์ดํธ๋๊ณ , 1~3์๋ ๋ฉ๋ฌ ์์ด์ฝ์ด ํ์๋ฉ๋๋ค. ๊ฒ์ ์นด๋์ "์ต๊ทผ 1์: ๋๋ค์ (์ ์)" ์ ๋ณด๊ฐ ํ์๋ฉ๋๋ค.
**Step 6: ๋ณด๋์ค ๊ธฐ๋ฅ ์ถ๊ฐ ๋ฐ ์ต์ข
์์ฑ**
### ์ ํ ๊ธฐ๋ฅ 1: ๊ฒ์ ํ๋ ์ด ํต๊ณ ์ฐจํธ
1. ๋ญํน ๋ณด๋ ์์ "๐ ๋ด ํต๊ณ" ๋ฒํผ์ ์ถ๊ฐํ์ธ์.
2. ๋ฒํผ ํด๋ฆญ ์ ๋ชจ๋ฌ์ ์ด๊ณ , ์ฌ์ฉ์๊ฐ ํ๋ ์ดํ ๊ฒ์๋ณ ์ ์ ์ถ์ด๋ฅผ Chart.js ๋ผ์ธ ์ฐจํธ๋ก ํ์ํ์ธ์:
- CDN: `<script src="https://cdn.jsdelivr.net/npm/chart.js@4"></script>`
- X์ถ: ํ๋ ์ด ์ผ์, Y์ถ: ์ ์
- 3๊ฐ ๊ฒ์์ ๊ฐ๊ฐ ๋ค๋ฅธ ์์ ๋ผ์ธ์ผ๋ก ํ์
3. PHP์์ ์ฌ์ฉ์์ ์ต๊ทผ 10ํ ํ๋ ์ด ๊ธฐ๋ก์ ์กฐํํ์ธ์ (๋๋ค์ ๊ธฐ์ค):
```php
$stmt = $pdo->prepare("SELECT score, played_at FROM scores_color WHERE nickname = ? ORDER BY played_at DESC LIMIT 10");
$stmt->execute([$nickname]);
```
4. JavaScript๋ก Chart.js ์ฐจํธ๋ฅผ ์์ฑํ์ธ์:
```javascript
const ctx = document.getElementById('statsChart').getContext('2d');
new Chart(ctx, {
type: 'line',
data: { labels: dates, datasets: [{ label: '์์ ๊ธฐ์ต', data: scoresColor, borderColor: '#FF6B6B' }] }
});
```
### ์ ํ ๊ธฐ๋ฅ 2: ๋์ด๋ ์ ํ
1. ๊ฒ์ ์์ ์ ๋์ด๋ ์ ํ ๋ฒํผ ์ถ๊ฐ (์ฌ์/๋ณดํต/์ด๋ ค์):
- ์ฌ์: ์์ ์นด๋ 2x2 (4๊ฐ), ์ซ์ ์์ 40์ด, ๋ฐ์์๋ 3~6์ด ๋๊ธฐ
- ๋ณดํต: ๊ธฐ๋ณธ ์ค์
- ์ด๋ ค์: ์์ ์นด๋ 4x4 (16๊ฐ), ์ซ์ ์์ 20์ด, ๋ฐ์์๋ 1~3์ด ๋๊ธฐ
2. JavaScript์์ ๋์ด๋์ ๋ฐ๋ผ ๊ฒ์ ์ค์ ์ ๋์ ์ผ๋ก ๋ณ๊ฒฝํ์ธ์.
### ์ ํ ๊ธฐ๋ฅ 3: ํจ๊ณผ์ ์ถ๊ฐ
1. Web Audio API๋ก ๊ฐ๋จํ ํจ๊ณผ์์ ์ถ๊ฐํ์ธ์:
- ์ ๋ต ํด๋ฆญ: ์ ์๋ฆฌ (์ฃผํ์ 800Hz, 100ms)
- ์ค๋ต ํด๋ฆญ: ๋ถ์ ์๋ฆฌ (์ฃผํ์ 200Hz, 200ms)
- ๊ฒ์ ์ข
๋ฃ: ํกํ๋ (์ฌ๋ฌ ์๊ณ ์กฐํฉ)
2. ํจ๊ณผ์ on/off ํ ๊ธ ๋ฒํผ์ ์ถ๊ฐํ์ธ์ (localStorage์ ์ค์ ์ ์ฅ).
### ์ต์ข
์ ๊ฒ
1. ๋ชจ๋ ๊ฒ์์ด ์ ์ ์๋ํ๋์ง ํ
์คํธํ์ธ์.
2. ๋คํฌ๋ชจ๋ ์ ํ ์ ๋ชจ๋ ์์๊ฐ ์์ฐ์ค๋ฝ๊ฒ ๋ณํ๋์ง ํ์ธํ์ธ์.
3. ๋ชจ๋ฐ์ผ ํ๋ฉด์์ ๊ฒ์ ํ๋ ์ด๊ฐ ์ํํ์ง ํ์ธํ์ธ์.
4. SQLite DB์ ์ ์๊ฐ ์ ์์ ์ผ๋ก ์ ์ฅ๋๊ณ , ๋ญํน ๋ณด๋์ ํ์๋๋์ง ํ์ธํ์ธ์.
5. ์ฝ๋ ์ฃผ์์ ์ถ๊ฐํ์ฌ ์ ์ง๋ณด์๋ฅผ ์ฝ๊ฒ ํ์ธ์.
์ด ๋จ๊ณ์์๋ ๋ณด๋์ค ๊ธฐ๋ฅ์ ์ถ๊ฐํ๊ณ , ์ ์ฒด ํ๋ก์ ํธ๋ฅผ ์ต์ข
์์ฑํฉ๋๋ค.
๊ธฐ๋ ๊ฒฐ๊ณผ: ๋ณด๋์ค ๊ธฐ๋ฅ์ด ์ถ๊ฐ๋์ด ์ฌ์ฉ์๋ ์์ ์ ํ๋ ์ด ํต๊ณ๋ฅผ ์ฐจํธ๋ก ํ์ธํ๊ณ , ๋์ด๋๋ฅผ ์ ํํ์ฌ ๊ฒ์์ ์ฆ๊ธฐ๋ฉฐ, ํจ๊ณผ์๊ณผ ํจ๊ป ๋์ฑ ๋ชฐ์ ๊ฐ ์๋ ๊ฒฝํ์ ํ ์ ์์ต๋๋ค. ๋ชจ๋ ๊ธฐ๋ฅ์ด ์ ์ ์๋ํ๋ฉฐ, ๋คํฌ๋ชจ๋์ ๋ชจ๋ฐ์ผ ๋ฐ์ํ์ด ์๋ฒฝํ๊ฒ ์ ์ฉ๋ฉ๋๋ค.
์ ์ถ๋ ์ํ (0)
๋ก๊ทธ์ธ ํ ์ ์ถ์์ง ์ ์ถ๋ ์ํ์ด ์์ต๋๋ค. ์ฒซ ๋ฒ์งธ ๋์ ์๊ฐ ๋์ด๋ณด์ธ์!