๐ฏ g5se ์จ๋ผ์ธ ๋น๊ณ ๊ฒ์ ๋ฉ์ด์ปค - ์ปค์คํ ๋น๊ณ ํ ์์ฑ & ์ค์๊ฐ ํ๋ ์ด
g5se-bingo-game-maker
๋ฌธ์ ์ค๋ช
ํ๋ก์ ํธ ๊ฐ์
์ฌ์ฉ์๊ฐ ์ง์ ๋น๊ณ ํ ์ฃผ์ ์ ํญ๋ชฉ์ ์ ๋ ฅํ์ฌ ์ปค์คํ ๋น๊ณ ๊ฒ์์ ๋ง๋ค๊ณ , ์น๊ตฌ๋ค๊ณผ ํจ๊ป ํ๋ ์ดํ ์ ์๋ ์น ๊ธฐ๋ฐ ๋น๊ณ ๊ฒ์ ํ๋ซํผ์ ๋๋ค. ์์ด์ค๋ธ๋ ์ดํน, ํ ๋น๋ฉ, ์จ๋ผ์ธ ๋ชจ์์์ ํ์ฉํ ์ ์๋ ์ฌ๋ฏธ์๋ ๋๊ตฌ์ ๋๋ค.
ํ์ ๊ธฐ๋ฅ
1. ๋น๊ณ ํ ์์ฑ ํ์ด์ง
- ๋น๊ณ ํ ์ ๋ชฉ ์ ๋ ฅ (์: "๊ฐ๋ฐ์ ๋น๊ณ ", "์ํ ๋งค๋์ ๋น๊ณ ")
- 5ร5 ๊ทธ๋ฆฌ๋์ ๋ค์ด๊ฐ 25๊ฐ ํญ๋ชฉ ์ ๋ ฅ ํผ
- ํญ๋ชฉ ์๋ ์ ํ ์ต์ (์ฒดํฌ๋ฐ์ค)
- ๋น๊ณ ํ ์์ฑ ํ ๊ณ ์ ID๋ก ์ ์ฅ (SQLite)
- ์์ฑ๋ ๋น๊ณ ํ ๊ณต์ URL ์๋ ์์ฑ (์:
/bingo-game-maker/play/abc123)
2. ๋น๊ณ ํ๋ ์ด ํ์ด์ง
- ๊ณต์ URL๋ก ์ ์ ์ ํด๋น ๋น๊ณ ํ ๋ก๋
- 5ร5 ๊ทธ๋ฆฌ๋ UI (๊ฐ ์นธ์ ํด๋ฆญ ๊ฐ๋ฅํ ์นด๋ ํํ)
- ์นธ ํด๋ฆญ ์ "์ฒดํฌ๋จ" ์ํ๋ก ๋ณ๊ฒฝ (๋ฐฐ๊ฒฝ์ ๋ณํ + ์ฒดํฌ ์์ด์ฝ)
- ๋น๊ณ ์์ฑ ๊ฐ์ง (๊ฐ๋ก/์ธ๋ก/๋๊ฐ์ 5์ค)
- ๋น๊ณ ์์ฑ ์ ์ถํ ๋ชจ๋ฌ ํ์ ("๐ ๋น๊ณ ! {n}์ค ์์ฑ!")
- ํ์ฌ ์์ฑ๋ ๋น๊ณ ์ค ์ ์ค์๊ฐ ์นด์ดํธ ํ์
3. ๋ด ๋น๊ณ ํ ๋ชฉ๋ก ํ์ด์ง
- ๋ด๊ฐ ๋ง๋ ๋น๊ณ ํ ๋ฆฌ์คํธ (์ ๋ชฉ, ์์ฑ์ผ, ํ๋ ์ด ํ์)
- ๊ฐ ๋น๊ณ ํ์ ๊ณต์ URL ๋ณต์ฌ ๋ฒํผ
- ๋น๊ณ ํ ์ญ์ ๊ธฐ๋ฅ
4. ๋์์ธ & UX
- modern shell ์ ์ฉ (nav/footer/๋คํฌ๋ชจ๋ ์ง์)
- ๋์์ธ ํ ํฐ ํ์ฉ (
--m-surface,--m-text,--m-shadow๋ฑ) - ๋น๊ณ ์นธ ํด๋ฆญ ์ ๋ถ๋๋ฌ์ด CSS ์ ๋๋ฉ์ด์ (scale + opacity)
- ๋น๊ณ ์์ฑ ์ confetti ํจ๊ณผ (์์ CSS ๋๋ ๊ฐ๋จํ JS ์ ๋๋ฉ์ด์ )
- ๋ชจ๋ฐ์ผ ๋ฐ์ํ (๊ทธ๋ฆฌ๋ ํฌ๊ธฐ ์๋ ์กฐ์ )
์ ํ ์ถ๊ฐ ๊ธฐ๋ฅ (๋ณด๋์ค)
- ๋น๊ณ ํ ํ ํ๋ฆฟ ๊ฐค๋ฌ๋ฆฌ (์: "๊ฐ๋ฐ์ ๋น๊ณ ", "์ฌํ ๋น๊ณ ", "K-POP ๋น๊ณ " ๋ฑ ๋ฏธ๋ฆฌ ๋ง๋ค์ด์ง ํ ํ๋ฆฟ)
- ๋น๊ณ ํ ์ด๋ฏธ์ง๋ก ๋ด๋ณด๋ด๊ธฐ (HTML2Canvas ํ์ฉ)
- ๋น๊ณ ์์ฑ ์ ์์ฑ ์๊ฐ ๊ธฐ๋ก & ๋ฆฌ๋๋ณด๋
- ํญ๋ชฉ๋ณ ์์ด์ฝ ์ถ๊ฐ ์ต์ (์ด๋ชจ์ง ์ ํ๊ธฐ)
๊ธฐ์ ์คํ
- ํ๋ ์์ํฌ: g5se (PHP 8.2+)
- DB: SQLite (๋ชจ๋ ์ ์ฉ DB ํ์ผ)
- ํ๋ก ํธ์๋: ์์ HTML/CSS/JavaScript (๋น๋ ๋๊ตฌ ์์)
- ๋์์ธ: modern shell + ๋์์ธ ํ ํฐ + ์ปค์คํ CSS
ํ์ต ๋ชฉํ
- g5se ํด๋ ๊ธฐ๋ฐ ์๋ ๋ผ์ฐํ
ํ์ฉ (๋์ segment
[id]ํ์ฉ) - modern shell์ ํ์ฉํ ์ผ๊ด๋ UI ๊ตฌ์ถ
- SQLite CRUD ํจํด (๋น๊ณ ํ ์์ฑ/์กฐํ/์ญ์ )
- ์์ JavaScript๋ก ์ธํฐ๋ํฐ๋ธ ๊ฒ์ ๋ก์ง ๊ตฌํ
- CSS ์ ๋๋ฉ์ด์ ์ ํ์ฉํ ์ ์ ํผ๋๋ฐฑ ํฅ์
์ ์ฝ ์กฐ๊ฑด
- โข g5se (PHP 8.2+) ๊ธฐ๋ฐ ํ๋ก์ ํธ (Laravel/Composer/Node.js ๋น๋ ๋๊ตฌ ์ฌ์ฉ ๊ธ์ง)
- โข ํด๋ ๊ธฐ๋ฐ ์๋ ๋ผ์ฐํ ์ฌ์ฉ (`app/modules/bingo-game-maker/index.php` โ `/bingo-game-maker`)
- โข modern shell ํ์ ์ ์ฉ (nav/footer/๋คํฌ๋ชจ๋ ์ง์)
- โข ๋์์ธ ํ ํฐ ํ์ฉ (`--m-surface`, `--m-text`, `--m-shadow`, `--m-radius-lg` ๋ฑ)
- โข ๋ชจ๋ ์ ์ฉ SQLite DB ์ฌ์ฉ (`G5_DATA_PATH.'/modules/bingo-game-maker.sqlite'`)
- โข prepared statement๋ก SQL ์ธ์ ์ ๋ฐฉ์ง
- โข ์์ JavaScript ์ฌ์ฉ (์ธ๋ถ ๋ผ์ด๋ธ๋ฌ๋ฆฌ CDN ๊ธ์ง)
- โข ๋ชจ๋ฐ์ผ ๋ฐ์ํ ๋์์ธ (๋ฏธ๋์ด ์ฟผ๋ฆฌ ํ์ฉ)
- โข ๋น๊ณ ์์ฑ ๊ฐ์ง ์๊ณ ๋ฆฌ์ฆ ๊ตฌํ (๊ฐ๋ก/์ธ๋ก/๋๊ฐ์ ์ฒดํฌ)
- โข ๊ณต์ URL ์์ฑ ์ ๊ณ ์ ID๋ 8์๋ฆฌ ๋๋ค ๋ฌธ์์ด (์๋ฌธ+์ซ์)
ํ๋กฌํํธ ๋ ์ํผ
์๋ ํ๋กฌํํธ๋ฅผ ์์๋๋ก AI์๊ฒ ๋ณด๋ด๋ฉด ๋ฉ๋๋ค. ๋ณต์ฌ ๋ฒํผ์ ๋๋ฌ ๋ฐ๋ก ์ฌ์ฉํ์ธ์.
g5se๊ฐ ์ค์น๋ ๋๋ ํฐ๋ฆฌ(๋ฃจํธ์ `index.php`ยท`app/`์ด ์๋ ๊ณณ)์์ ์์
์ ์์ํฉ๋๋ค. (g5se ์ค์น๋ https://github.com/gnuboard/g5se ์ฐธ๊ณ )
๋น๊ณ ๊ฒ์ ๋ฉ์ด์ปค ๋ชจ๋์ ๋ฉ์ธ ํ์ด์ง๋ฅผ ๋ง๋ญ๋๋ค.
**์์
๋ด์ฉ:**
1. `app/modules/bingo-game-maker/index.php` ํ์ผ ์์ฑ
2. 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;">
<h1>๐ฏ ๋น๊ณ ๊ฒ์ ๋ฉ์ด์ปค</h1>
<p style="color: var(--m-text-muted); margin-bottom: 32px;">๋๋ง์ ์ปค์คํ
๋น๊ณ ํ์ ๋ง๋ค๊ณ ์น๊ตฌ๋ค๊ณผ ํจ๊ป ํ๋ ์ดํ์ธ์!</p>
<!-- ์ฌ๊ธฐ์ ๋ฉ๋ด ๋ฒํผ๋ค ์ถ๊ฐ -->
<div style="display: grid; gap: 16px; max-width: 500px;">
<a href="/bingo-game-maker/create" style="
display: block;
padding: 24px;
background: var(--m-surface);
border-radius: var(--m-radius-lg);
box-shadow: var(--m-shadow);
text-decoration: none;
color: var(--m-text);
transition: transform 0.2s;
">
<div style="font-size: 32px; margin-bottom: 8px;">โจ</div>
<h3 style="margin: 0 0 8px 0;">์ ๋น๊ณ ํ ๋ง๋ค๊ธฐ</h3>
<p style="margin: 0; color: var(--m-text-muted); font-size: 14px;">25๊ฐ ํญ๋ชฉ์ ์
๋ ฅํ์ฌ ์ปค์คํ
๋น๊ณ ํ ์์ฑ</p>
</a>
<a href="/bingo-game-maker/list" style="
display: block;
padding: 24px;
background: var(--m-surface);
border-radius: var(--m-radius-lg);
box-shadow: var(--m-shadow);
text-decoration: none;
color: var(--m-text);
transition: transform 0.2s;
">
<div style="font-size: 32px; margin-bottom: 8px;">๐</div>
<h3 style="margin: 0 0 8px 0;">๋ด ๋น๊ณ ํ ๋ชฉ๋ก</h3>
<p style="margin: 0; color: var(--m-text-muted); font-size: 14px;">๋ง๋ ๋น๊ณ ํ ๊ด๋ฆฌ & ๊ณต์ ๋งํฌ ๋ณต์ฌ</p>
</a>
</div>
</main>
<?php require G5_THEME_PATH.'/modern/_footer.inc.php'; ?>
</div>
<?php include_once('./tail.sub.php'); ?>```
3. ์นด๋ hover ํจ๊ณผ CSS ์ถ๊ฐ (์ธ๋ผ์ธ ์คํ์ผ์ ์ถ๊ฐ):
```css
a:hover {
transform: translateY(-4px);
}
```
**ํ์ธ ์ฌํญ:**
- `/bingo-game-maker` URL๋ก ์ ์ํ์ฌ ๋ฉ์ธ ํ์ด์ง ๋ ๋๋ง ํ์ธ
- modern shell์ nav/footer๊ฐ ์ ์ ํ์๋๋์ง ํ์ธ
- ๋คํฌ๋ชจ๋ ํ ๊ธ ์ ์์์ด ์๋ ์ ํ๋๋์ง ํ์ธ
- ๋ ๊ฐ์ ๋ฉ๋ด ์นด๋๊ฐ ์ ์ ํ์๋๋์ง ํ์ธ
๊ธฐ๋ ๊ฒฐ๊ณผ: ๋น๊ณ ๊ฒ์ ๋ฉ์ด์ปค ๋ฉ์ธ ํ์ด์ง๊ฐ ์์ฑ๋๊ณ , modern shell์ด ์ ์ฉ๋์ด nav/footer/๋คํฌ๋ชจ๋๊ฐ ์ ์ ์๋ํฉ๋๋ค. ๋ ๊ฐ์ ๋ฉ๋ด ์นด๋("์ ๋น๊ณ ํ ๋ง๋ค๊ธฐ", "๋ด ๋น๊ณ ํ ๋ชฉ๋ก")๊ฐ ํ์๋๋ฉฐ, hover ์ ์ด์ง ์๋ก ์ฌ๋ผ๊ฐ๋ ์ ๋๋ฉ์ด์ ์ด ์๋ํฉ๋๋ค.
๋น๊ณ ํ ์์ฑ ํ์ด์ง๋ฅผ ๋ง๋ญ๋๋ค.
**์์
๋ด์ฉ:**
1. `app/modules/bingo-game-maker/create/index.php` ํ์ผ ์์ฑ
2. ๋ค์ ๊ธฐ๋ฅ ๊ตฌํ:
```php
<?php
include_once('./_common.php');
// POST ์์ฒญ ์ฒ๋ฆฌ (๋น๊ณ ํ ์์ฑ)
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$title = trim($_POST['title'] ?? '');
$items = [];
// 25๊ฐ ํญ๋ชฉ ์์ง
for ($i = 1; $i <= 25; $i++) {
$item = trim($_POST["item_{$i}"] ?? '');
if ($item === '') {
die('๋ชจ๋ ํญ๋ชฉ์ ์
๋ ฅํด์ฃผ์ธ์.');
}
$items[] = $item;
}
// ์
ํ ์ต์
ํ์ธ
$shuffle = isset($_POST['shuffle']);
if ($shuffle) {
shuffle($items);
}
// ๊ณ ์ ID ์์ฑ (8์๋ฆฌ ๋๋ค)
$id = substr(str_shuffle('abcdefghijklmnopqrstuvwxyz0123456789'), 0, 8);
// DB ์ ์ฅ
@mkdir(G5_DATA_PATH.'/modules', 0707, true);
$db = new PDO('sqlite:'.G5_DATA_PATH.'/modules/bingo-game-maker.sqlite');
$db->exec("CREATE TABLE IF NOT EXISTS bingo_boards (
id TEXT PRIMARY KEY,
title TEXT NOT NULL,
items TEXT NOT NULL,
created_at TEXT NOT NULL,
play_count INTEGER DEFAULT 0
)");
$stmt = $db->prepare("INSERT INTO bingo_boards (id, title, items, created_at) VALUES (?, ?, ?, ?)");
$stmt->execute([$id, $title, json_encode($items, JSON_UNESCAPED_UNICODE), date('Y-m-d H:i:s')]);
// ํ๋ ์ด ํ์ด์ง๋ก ๋ฆฌ๋ค์ด๋ ํธ
header("Location: /bingo-game-maker/play/{$id}");
exit;
}
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>โจ ์ ๋น๊ณ ํ ๋ง๋ค๊ธฐ</h1>
<p style="color: var(--m-text-muted); margin-bottom: 32px;">25๊ฐ ํญ๋ชฉ์ ์
๋ ฅํ์ฌ ๋๋ง์ ๋น๊ณ ํ์ ๋ง๋์ธ์!</p>
<form method="POST" style="max-width: 700px;">
<div style="margin-bottom: 24px;">
<label style="display: block; margin-bottom: 8px; font-weight: 600;">๋น๊ณ ํ ์ ๋ชฉ</label>
<input type="text" name="title" required placeholder="์: ๊ฐ๋ฐ์ ๋น๊ณ " style="
width: 100%;
padding: 12px;
border: 1px solid #ddd;
border-radius: 8px;
font-size: 16px;
background: var(--m-surface);
color: var(--m-text);
">
</div>
<div style="margin-bottom: 24px;">
<label style="display: block; margin-bottom: 12px; font-weight: 600;">๋น๊ณ ํญ๋ชฉ (25๊ฐ)</label>
<div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 12px;">
<?php for ($i = 1; $i <= 25; $i++): ?>
<input type="text" name="item_<?php echo $i; ?>" required placeholder="ํญ๋ชฉ <?php echo $i; ?>" style="
padding: 10px;
border: 1px solid #ddd;
border-radius: 6px;
font-size: 14px;
background: var(--m-surface);
color: var(--m-text);
">
<?php endfor; ?>
</div>
</div>
<div style="margin-bottom: 24px;">
<label style="display: flex; align-items: center; gap: 8px; cursor: pointer;">
<input type="checkbox" name="shuffle" style="width: 18px; height: 18px;">
<span>ํญ๋ชฉ ์์ ๋๋ค ์๊ธฐ</span>
</label>
</div>
<button type="submit" style="
padding: 14px 32px;
background: #4F46E5;
color: white;
border: none;
border-radius: 8px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
">๋น๊ณ ํ ์์ฑํ๊ธฐ</button>
</form>
</main>
<?php require G5_THEME_PATH.'/modern/_footer.inc.php'; ?>
</div>
<?php include_once('./tail.sub.php'); ?>```
**ํ์ธ ์ฌํญ:**
- `/bingo-game-maker/create` URL๋ก ์ ์ํ์ฌ ํผ ๋ ๋๋ง ํ์ธ
- 25๊ฐ ์
๋ ฅ ํ๋๊ฐ ๊ทธ๋ฆฌ๋ ๋ ์ด์์์ผ๋ก ํ์๋๋์ง ํ์ธ
- ํผ ์ ์ถ ์ DB์ ์ ์ฅ๋๊ณ ํ๋ ์ด ํ์ด์ง๋ก ๋ฆฌ๋ค์ด๋ ํธ๋๋์ง ํ์ธ
๊ธฐ๋ ๊ฒฐ๊ณผ: ๋น๊ณ ํ ์์ฑ ํ์ด์ง๊ฐ ๋ง๋ค์ด์ง๊ณ , ์ ๋ชฉ ์ ๋ ฅ ํ๋์ 25๊ฐ์ ํญ๋ชฉ ์ ๋ ฅ ํ๋๊ฐ ๊ทธ๋ฆฌ๋ ๋ ์ด์์์ผ๋ก ํ์๋ฉ๋๋ค. ํผ ์ ์ถ ์ SQLite DB์ ๋น๊ณ ํ์ด ์ ์ฅ๋๊ณ , ๊ณ ์ ID๊ฐ ์์ฑ๋์ด ํ๋ ์ด ํ์ด์ง๋ก ๋ฆฌ๋ค์ด๋ ํธ๋ฉ๋๋ค.
๋น๊ณ ํ๋ ์ด ํ์ด์ง๋ฅผ ๋ง๋ญ๋๋ค. ๋์ segment๋ฅผ ํ์ฉํ์ฌ ๋น๊ณ ํ ID๋ฅผ ๋ฐ์ต๋๋ค.
**์์
๋ด์ฉ:**
1. `app/modules/bingo-game-maker/play/[id]/index.php` ํ์ผ ์์ฑ
2. ๋ค์ ๊ธฐ๋ฅ ๊ตฌํ:
```php
<?php
include_once('./_common.php');
// URL์์ ๋น๊ณ ํ ID ๊ฐ์ ธ์ค๊ธฐ (g5se๊ฐ ์๋์ผ๋ก $_GET['id']์ ์ฃผ์
)
$id = $_GET['id'] ?? '';
if ($id === '') {
die('์๋ชป๋ ์ ๊ทผ์
๋๋ค.');
}
// DB์์ ๋น๊ณ ํ ์กฐํ
$db = new PDO('sqlite:'.G5_DATA_PATH.'/modules/bingo-game-maker.sqlite');
$stmt = $db->prepare("SELECT * FROM bingo_boards WHERE id = ?");
$stmt->execute([$id]);
$board = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$board) {
die('๋น๊ณ ํ์ ์ฐพ์ ์ ์์ต๋๋ค.');
}
// ํ๋ ์ด ํ์ ์ฆ๊ฐ
$db->prepare("UPDATE bingo_boards SET play_count = play_count + 1 WHERE id = ?")->execute([$id]);
$title = htmlspecialchars($board['title']);
$items = json_decode($board['items'], true);
require_once(G5_THEME_PATH.'/modern/_head.inc.php');
include_once('./head.sub.php');
?>
<style>
.bingo-grid {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 8px;
max-width: 600px;
margin: 0 auto;
}
.bingo-cell {
aspect-ratio: 1;
display: flex;
align-items: center;
justify-content: center;
background: var(--m-surface);
border-radius: 8px;
box-shadow: var(--m-shadow);
cursor: pointer;
font-size: 14px;
text-align: center;
padding: 8px;
transition: all 0.3s;
position: relative;
}
.bingo-cell:hover {
transform: scale(1.05);
}
.bingo-cell.checked {
background: #4F46E5;
color: white;
}
.bingo-cell.checked::after {
content: 'โ';
position: absolute;
top: 4px;
right: 8px;
font-size: 20px;
}
@media (max-width: 600px) {
.bingo-cell {
font-size: 12px;
padding: 4px;
}
}
</style>
<div class="m-shell">
<?php require G5_THEME_PATH.'/modern/_nav.inc.php'; ?>
<main class="m-container" style="padding: 32px 20px 48px;">
<div style="text-align: center; margin-bottom: 32px;">
<h1><?php echo $title; ?></h1>
<p style="color: var(--m-text-muted);">์นธ์ ํด๋ฆญํ์ฌ ์ฒดํฌํ์ธ์!</p>
<div id="bingoCount" style="font-size: 24px; font-weight: 700; color: #4F46E5; margin-top: 16px;">๋น๊ณ : 0์ค</div>
</div>
<div class="bingo-grid" id="bingoGrid">
<?php foreach ($items as $index => $item): ?>
<div class="bingo-cell" data-index="<?php echo $index; ?>"><?php echo htmlspecialchars($item); ?></div>
<?php endforeach; ?>
</div>
<div style="text-align: center; margin-top: 32px;">
<button id="resetBtn" style="
padding: 12px 24px;
background: var(--m-surface);
color: var(--m-text);
border: 1px solid #ddd;
border-radius: 8px;
cursor: pointer;
">์ด๊ธฐํ</button>
</div>
</main>
<?php require G5_THEME_PATH.'/modern/_footer.inc.php'; ?>
</div>
<script>
const grid = document.getElementById('bingoGrid');
const cells = grid.querySelectorAll('.bingo-cell');
const bingoCount = document.getElementById('bingoCount');
const resetBtn = document.getElementById('resetBtn');
let checkedCells = new Set();
cells.forEach(cell => {
cell.addEventListener('click', () => {
const index = parseInt(cell.dataset.index);
if (checkedCells.has(index)) {
checkedCells.delete(index);
cell.classList.remove('checked');
} else {
checkedCells.add(index);
cell.classList.add('checked');
}
updateBingoCount();
});
});
resetBtn.addEventListener('click', () => {
checkedCells.clear();
cells.forEach(cell => cell.classList.remove('checked'));
updateBingoCount();
});
function updateBingoCount() {
const lines = [];
// ๊ฐ๋ก ์ฒดํฌ
for (let i = 0; i < 5; i++) {
const row = [];
for (let j = 0; j < 5; j++) {
row.push(i * 5 + j);
}
if (row.every(idx => checkedCells.has(idx))) lines.push(row);
}
// ์ธ๋ก ์ฒดํฌ
for (let i = 0; i < 5; i++) {
const col = [];
for (let j = 0; j < 5; j++) {
col.push(j * 5 + i);
}
if (col.every(idx => checkedCells.has(idx))) lines.push(col);
}
// ๋๊ฐ์ ์ฒดํฌ (\)
const diag1 = [0, 6, 12, 18, 24];
if (diag1.every(idx => checkedCells.has(idx))) lines.push(diag1);
// ๋๊ฐ์ ์ฒดํฌ (/)
const diag2 = [4, 8, 12, 16, 20];
if (diag2.every(idx => checkedCells.has(idx))) lines.push(diag2);
const count = lines.length;
bingoCount.textContent = `๋น๊ณ : ${count}์ค`;
if (count > 0 && count >= 5) {
setTimeout(() => {
alert(`๐ ์ถํํฉ๋๋ค! ${count}์ค ๋น๊ณ ์์ฑ!`);
}, 100);
}
}
</script>
<?php include_once('./tail.sub.php'); ?>```
**ํ์ธ ์ฌํญ:**
- `/bingo-game-maker/play/{์์ฑ๋ID}` URL๋ก ์ ์ํ์ฌ ๋น๊ณ ํ ๋ ๋๋ง ํ์ธ
- 5ร5 ๊ทธ๋ฆฌ๋๊ฐ ์ ์ ํ์๋๋์ง ํ์ธ
- ์นธ ํด๋ฆญ ์ ์ฒดํฌ ์ํ ํ ๊ธ์ด ์๋ํ๋์ง ํ์ธ
- ๋น๊ณ ์ค ์๊ฐ ์ค์๊ฐ์ผ๋ก ์
๋ฐ์ดํธ๋๋์ง ํ์ธ
๊ธฐ๋ ๊ฒฐ๊ณผ: ๋น๊ณ ํ๋ ์ด ํ์ด์ง๊ฐ ์์ฑ๋๊ณ , ๋์ segment๋ก ์ ๋ฌ๋ ID๋ฅผ ๊ธฐ๋ฐ์ผ๋ก DB์์ ๋น๊ณ ํ์ ๋ถ๋ฌ์ต๋๋ค. 5ร5 ๊ทธ๋ฆฌ๋๊ฐ ํ์๋๋ฉฐ, ์นธ ํด๋ฆญ ์ ์ฒดํฌ ์ํ๊ฐ ํ ๊ธ๋๊ณ , ๊ฐ๋ก/์ธ๋ก/๋๊ฐ์ ๋น๊ณ ์์ฑ ์ฌ๋ถ๋ฅผ ์ค์๊ฐ์ผ๋ก ๊ฐ์งํ์ฌ ๋น๊ณ ์ค ์๋ฅผ ํ์ํฉ๋๋ค.
๋ด ๋น๊ณ ํ ๋ชฉ๋ก ํ์ด์ง๋ฅผ ๋ง๋ญ๋๋ค.
**์์
๋ด์ฉ:**
1. `app/modules/bingo-game-maker/list/index.php` ํ์ผ ์์ฑ
2. ๋ค์ ๊ธฐ๋ฅ ๊ตฌํ:
```php
<?php
include_once('./_common.php');
// ์ญ์ ์์ฒญ ์ฒ๋ฆฌ
if (isset($_GET['delete'])) {
$id = $_GET['delete'];
$db = new PDO('sqlite:'.G5_DATA_PATH.'/modules/bingo-game-maker.sqlite');
$db->prepare("DELETE FROM bingo_boards WHERE id = ?")->execute([$id]);
header('Location: /bingo-game-maker/list');
exit;
}
// ๋น๊ณ ํ ๋ชฉ๋ก ์กฐํ
$db = new PDO('sqlite:'.G5_DATA_PATH.'/modules/bingo-game-maker.sqlite');
$stmt = $db->query("SELECT * FROM bingo_boards ORDER BY created_at DESC");
$boards = $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;">
<h1>๐ ๋ด ๋น๊ณ ํ ๋ชฉ๋ก</h1>
<p style="color: var(--m-text-muted); margin-bottom: 32px;">๋ง๋ ๋น๊ณ ํ์ ๊ด๋ฆฌํ๊ณ ๊ณต์ ํ์ธ์!</p>
<?php if (count($boards) === 0): ?>
<div style="text-align: center; padding: 48px; background: var(--m-surface); border-radius: var(--m-radius-lg);">
<p style="color: var(--m-text-muted);">์์ง ๋ง๋ ๋น๊ณ ํ์ด ์์ต๋๋ค.</p>
<a href="/bingo-game-maker/create" style="
display: inline-block;
margin-top: 16px;
padding: 12px 24px;
background: #4F46E5;
color: white;
text-decoration: none;
border-radius: 8px;
">์ฒซ ๋น๊ณ ํ ๋ง๋ค๊ธฐ</a>
</div>
<?php else: ?>
<div style="display: grid; gap: 16px;">
<?php foreach ($boards as $board): ?>
<div style="
padding: 20px;
background: var(--m-surface);
border-radius: var(--m-radius-lg);
box-shadow: var(--m-shadow);
">
<h3 style="margin: 0 0 8px 0;"><?php echo htmlspecialchars($board['title']); ?></h3>
<p style="color: var(--m-text-muted); font-size: 14px; margin-bottom: 16px;">
์์ฑ: <?php echo date('Y-m-d H:i', strtotime($board['created_at'])); ?> |
ํ๋ ์ด ํ์: <?php echo $board['play_count']; ?>ํ
</p>
<div style="display: flex; gap: 8px; flex-wrap: wrap;">
<a href="/bingo-game-maker/play/<?php echo $board['id']; ?>" style="
padding: 8px 16px;
background: #4F46E5;
color: white;
text-decoration: none;
border-radius: 6px;
font-size: 14px;
">ํ๋ ์ด</a>
<button onclick="copyUrl('<?php echo $board['id']; ?>')" style="
padding: 8px 16px;
background: var(--m-surface);
color: var(--m-text);
border: 1px solid #ddd;
border-radius: 6px;
font-size: 14px;
cursor: pointer;
">URL ๋ณต์ฌ</button>
<a href="?delete=<?php echo $board['id']; ?>" onclick="return confirm('์ ๋ง ์ญ์ ํ์๊ฒ ์ต๋๊น?')" style="
padding: 8px 16px;
background: #EF4444;
color: white;
text-decoration: none;
border-radius: 6px;
font-size: 14px;
">์ญ์ </a>
</div>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
</main>
<?php require G5_THEME_PATH.'/modern/_footer.inc.php'; ?>
</div>
<script>
function copyUrl(id) {
const url = window.location.origin + '/bingo-game-maker/play/' + id;
navigator.clipboard.writeText(url).then(() => {
alert('URL์ด ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ๋์์ต๋๋ค!');
});
}
</script>
<?php include_once('./tail.sub.php'); ?>```
**ํ์ธ ์ฌํญ:**
- `/bingo-game-maker/list` URL๋ก ์ ์ํ์ฌ ๋ชฉ๋ก ๋ ๋๋ง ํ์ธ
- ๋น๊ณ ํ์ด ์์ ๋ "์ฒซ ๋น๊ณ ํ ๋ง๋ค๊ธฐ" ๋ฒํผ์ด ํ์๋๋์ง ํ์ธ
- ๋น๊ณ ํ์ด ์์ ๋ ์ ๋ชฉ/์์ฑ์ผ/ํ๋ ์ด ํ์๊ฐ ํ์๋๋์ง ํ์ธ
- URL ๋ณต์ฌ ๋ฒํผ ํด๋ฆญ ์ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ๋๋์ง ํ์ธ
- ์ญ์ ๋ฒํผ ํด๋ฆญ ์ ํ์ธ ๋ํ์์๊ฐ ๋จ๊ณ ์ญ์ ๋๋์ง ํ์ธ
๊ธฐ๋ ๊ฒฐ๊ณผ: ๋ด ๋น๊ณ ํ ๋ชฉ๋ก ํ์ด์ง๊ฐ ์์ฑ๋๊ณ , DB์ ์ ์ฅ๋ ๋ชจ๋ ๋น๊ณ ํ์ด ์นด๋ ํํ๋ก ํ์๋ฉ๋๋ค. ๊ฐ ์นด๋์๋ ์ ๋ชฉ, ์์ฑ์ผ, ํ๋ ์ด ํ์๊ฐ ํ์๋๋ฉฐ, ํ๋ ์ด/URL ๋ณต์ฌ/์ญ์ ๋ฒํผ์ด ์ ๊ณต๋ฉ๋๋ค. URL ๋ณต์ฌ ๋ฒํผ ํด๋ฆญ ์ ํด๋ฆฝ๋ณด๋์ ๊ณต์ URL์ด ๋ณต์ฌ๋ฉ๋๋ค.
๋น๊ณ ์์ฑ ์ ์ถํ ํจ๊ณผ๋ฅผ ๊ฐ์ ํ๊ณ , ๊ณต์ ๊ธฐ๋ฅ์ ์ถ๊ฐํฉ๋๋ค.
**์์
๋ด์ฉ:**
1. `app/modules/bingo-game-maker/play/[id]/index.php` ํ์ผ ์์
2. ๋น๊ณ ์์ฑ ์ ๋ชจ๋ฌ ํ์
์ถ๊ฐ:
๊ธฐ์กด `</style>` ํ๊ทธ ๋ฐ๋ก ์์ ๋ค์ CSS ์ถ๊ฐ:
```css
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.7);
z-index: 9999;
align-items: center;
justify-content: center;
}
.modal.show {
display: flex;
}
.modal-content {
background: var(--m-surface);
padding: 40px;
border-radius: 16px;
text-align: center;
max-width: 400px;
animation: modalPop 0.3s ease-out;
}
@keyframes modalPop {
from {
transform: scale(0.8);
opacity: 0;
}
to {
transform: scale(1);
opacity: 1;
}
}
```
๊ธฐ์กด `<script>` ํ๊ทธ ๋ฐ๋ก ์์ ๋ค์ HTML ์ถ๊ฐ:
```html
<div id="bingoModal" class="modal">
<div class="modal-content">
<div style="font-size: 64px; margin-bottom: 16px;">๐</div>
<h2 id="modalTitle" style="margin: 0 0 16px 0;">๋น๊ณ !</h2>
<p id="modalText" style="color: var(--m-text-muted);"></p>
<button onclick="closeModal()" style="
margin-top: 24px;
padding: 12px 32px;
background: #4F46E5;
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
">๊ณ์ํ๊ธฐ</button>
</div>
</div>
```
๊ธฐ์กด `updateBingoCount()` ํจ์ ๋ง์ง๋ง ๋ถ๋ถ์ ๋ค์์ผ๋ก ๊ต์ฒด:
```javascript
const count = lines.length;
bingoCount.textContent = `๋น๊ณ : ${count}์ค`;
if (count > 0 && count >= 5) {
setTimeout(() => {
showModal(count);
}, 100);
}
```
`</script>` ํ๊ทธ ๋ฐ๋ก ์์ ๋ค์ ํจ์ ์ถ๊ฐ:
```javascript
function showModal(count) {
const modal = document.getElementById('bingoModal');
const modalText = document.getElementById('modalText');
modalText.textContent = `${count}์ค ๋น๊ณ ๋ฅผ ์์ฑํ์ต๋๋ค!`;
modal.classList.add('show');
}
function closeModal() {
const modal = document.getElementById('bingoModal');
modal.classList.remove('show');
}
```
3. ๊ณต์ ๋ฒํผ ์ถ๊ฐ (์ด๊ธฐํ ๋ฒํผ ์์):
๊ธฐ์กด ์ด๊ธฐํ ๋ฒํผ ์๋์ ๋ค์ HTML ์ถ๊ฐ:
```html
<button id="shareBtn" style="
margin-left: 8px;
padding: 12px 24px;
background: #4F46E5;
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
">๊ณต์ ํ๊ธฐ</button>
```
๊ธฐ์กด `</script>` ํ๊ทธ ๋ฐ๋ก ์์ ๋ค์ ์ด๋ฒคํธ ๋ฆฌ์ค๋ ์ถ๊ฐ:
```javascript
document.getElementById('shareBtn').addEventListener('click', () => {
const url = window.location.href;
if (navigator.share) {
navigator.share({
title: '<?php echo $title; ?>',
text: '์ด ๋น๊ณ ๊ฒ์์ ํจ๊ป ํ๋ ์ดํ์ธ์!',
url: url
});
} else {
navigator.clipboard.writeText(url).then(() => {
alert('URL์ด ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ๋์์ต๋๋ค!');
});
}
});
```
**ํ์ธ ์ฌํญ:**
- ๋น๊ณ 5์ค ์ด์ ์์ฑ ์ ๋ชจ๋ฌ ํ์
์ด ํ์๋๋์ง ํ์ธ
- ๋ชจ๋ฌ์ ์ ๋๋ฉ์ด์
ํจ๊ณผ๊ฐ ๋ถ๋๋ฝ๊ฒ ์๋ํ๋์ง ํ์ธ
- ๊ณต์ ๋ฒํผ ํด๋ฆญ ์ Web Share API ๋๋ ํด๋ฆฝ๋ณด๋ ๋ณต์ฌ๊ฐ ์๋ํ๋์ง ํ์ธ
๊ธฐ๋ ๊ฒฐ๊ณผ: ๋น๊ณ ์์ฑ ์ alert ๋์ ๋ชจ๋ฌ ํ์ ์ด ๋ํ๋๋ฉฐ, ๋ถ๋๋ฌ์ด ์ ๋๋ฉ์ด์ ํจ๊ณผ์ ํจ๊ป ์ถํ ๋ฉ์์ง๊ฐ ํ์๋ฉ๋๋ค. ๊ณต์ ๋ฒํผ์ ํตํด ๋น๊ณ ํ URL์ ์ฝ๊ฒ ๊ณต์ ํ ์ ์์ต๋๋ค. ๋ชจ๋ฐ์ผ์์๋ Web Share API๊ฐ, ๋ฐ์คํฌํฑ์์๋ ํด๋ฆฝ๋ณด๋ ๋ณต์ฌ๊ฐ ์๋ํฉ๋๋ค.
์ต์ข
๊ฐ์ : ๋ฐ์ํ ๋์์ธ ๊ฐํ ๋ฐ ์ฌ์ฉ์ ๊ฒฝํ ๊ฐ์
**์์
๋ด์ฉ:**
1. ๋ชจ๋ฐ์ผ ๋ฐ์ํ ๊ฐ์ - `app/modules/bingo-game-maker/play/[id]/index.php` ํ์ผ ์์
๊ธฐ์กด `@media (max-width: 600px)` ๋ธ๋ก์ ๋ค์์ผ๋ก ๊ต์ฒด:
```css
@media (max-width: 600px) {
.bingo-cell {
font-size: 11px;
padding: 4px;
}
.bingo-grid {
gap: 4px;
}
h1 {
font-size: 24px;
}
#bingoCount {
font-size: 20px;
}
}
```
2. ๋ก๋ฉ ์ํ ์ถ๊ฐ - `app/modules/bingo-game-maker/create/index.php` ํ์ผ ์์
๊ธฐ์กด ์ ์ถ ๋ฒํผ์ ๋ค์์ผ๋ก ๊ต์ฒด:
```html
<button type="submit" id="submitBtn" style="
padding: 14px 32px;
background: #4F46E5;
color: white;
border: none;
border-radius: 8px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
">๋น๊ณ ํ ์์ฑํ๊ธฐ</button>
```
`</form>` ํ๊ทธ ๋ฐ๋ก ์๋์ ๋ค์ ์คํฌ๋ฆฝํธ ์ถ๊ฐ:
```html
<script>
document.querySelector('form').addEventListener('submit', function() {
const btn = document.getElementById('submitBtn');
btn.textContent = '์์ฑ ์ค...';
btn.disabled = true;
btn.style.opacity = '0.6';
});
</script>
```
3. ๋น ํญ๋ชฉ ๋ฐฉ์ง ๊ฐ์ - ๋์ผ ํ์ผ์์ ํผ ์ ์ถ ์ ๊ฒ์ฆ ์ถ๊ฐ
์ ์คํฌ๋ฆฝํธ๋ฅผ ๋ค์์ผ๋ก ๊ต์ฒด:
```html
<script>
document.querySelector('form').addEventListener('submit', function(e) {
const inputs = document.querySelectorAll('input[name^="item_"]');
let emptyCount = 0;
inputs.forEach(input => {
if (input.value.trim() === '') {
emptyCount++;
input.style.borderColor = '#EF4444';
} else {
input.style.borderColor = '#ddd';
}
});
if (emptyCount > 0) {
e.preventDefault();
alert(`${emptyCount}๊ฐ์ ํญ๋ชฉ์ด ๋น์ด์์ต๋๋ค. ๋ชจ๋ ํญ๋ชฉ์ ์
๋ ฅํด์ฃผ์ธ์.`);
return;
}
const btn = document.getElementById('submitBtn');
btn.textContent = '์์ฑ ์ค...';
btn.disabled = true;
btn.style.opacity = '0.6';
});
</script>
```
4. ๋ฉ์ธ ํ์ด์ง์ ์ต๊ทผ ์์ฑ๋ ๋น๊ณ ํ ๋ฏธ๋ฆฌ๋ณด๊ธฐ ์ถ๊ฐ - `app/modules/bingo-game-maker/index.php` ํ์ผ ์์
๊ธฐ์กด `<!-- ์ฌ๊ธฐ์ ๋ฉ๋ด ๋ฒํผ๋ค ์ถ๊ฐ -->` ์ฃผ์ ์์ ๋ค์ PHP ์ฝ๋ ์ถ๊ฐ:
```php
<?php
// ์ต๊ทผ ๋น๊ณ ํ 3๊ฐ ์กฐํ
$db = new PDO('sqlite:'.G5_DATA_PATH.'/modules/bingo-game-maker.sqlite');
$db->exec("CREATE TABLE IF NOT EXISTS bingo_boards (
id TEXT PRIMARY KEY,
title TEXT NOT NULL,
items TEXT NOT NULL,
created_at TEXT NOT NULL,
play_count INTEGER DEFAULT 0
)");
$stmt = $db->query("SELECT * FROM bingo_boards ORDER BY created_at DESC LIMIT 3");
$recentBoards = $stmt->fetchAll(PDO::FETCH_ASSOC);
?>
<?php if (count($recentBoards) > 0): ?>
<div style="margin-bottom: 32px;">
<h2 style="margin-bottom: 16px;">๐ฅ ์ต๊ทผ ๋ง๋ค์ด์ง ๋น๊ณ ํ</h2>
<div style="display: grid; gap: 12px;">
<?php foreach ($recentBoards as $board): ?>
<a href="/bingo-game-maker/play/<?php echo $board['id']; ?>" style="
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px;
background: var(--m-surface);
border-radius: 8px;
box-shadow: var(--m-shadow);
text-decoration: none;
color: var(--m-text);
">
<div>
<div style="font-weight: 600;"><?php echo htmlspecialchars($board['title']); ?></div>
<div style="font-size: 12px; color: var(--m-text-muted);">ํ๋ ์ด <?php echo $board['play_count']; ?>ํ</div>
</div>
<div style="color: var(--m-text-muted);">โ</div>
</a>
<?php endforeach; ?>
</div>
</div>
<?php endif; ?>
```
**ํ์ธ ์ฌํญ:**
- ๋ชจ๋ฐ์ผ ํ๋ฉด์์ ๋น๊ณ ๊ทธ๋ฆฌ๋๊ฐ ์ ์ ํ ํฌ๊ธฐ๋ก ํ์๋๋์ง ํ์ธ
- ๋น๊ณ ํ ์์ฑ ์ ๋น ํญ๋ชฉ์ด ์์ผ๋ฉด ๋นจ๊ฐ ํ
๋๋ฆฌ์ ๊ฒฝ๊ณ ๋ฉ์์ง๊ฐ ํ์๋๋์ง ํ์ธ
- ๋ฉ์ธ ํ์ด์ง์ ์ต๊ทผ ๋ง๋ค์ด์ง ๋น๊ณ ํ 3๊ฐ๊ฐ ํ์๋๋์ง ํ์ธ
- ์ ์ฒด์ ์ธ ์ฌ์ฉ์ ๊ฒฝํ์ด ๋ถ๋๋ฝ๊ฒ ์๋ํ๋์ง ํ์ธ
๊ธฐ๋ ๊ฒฐ๊ณผ: ๋ชจ๋ฐ์ผ ๋ฐ์ํ์ด ๊ฐ์ ๋์ด ์์ ํ๋ฉด์์๋ ๋น๊ณ ํ์ด ๊น๋ํ๊ฒ ํ์๋ฉ๋๋ค. ๋น๊ณ ํ ์์ฑ ์ ๋น ํญ๋ชฉ์ด ์์ผ๋ฉด ์ค์๊ฐ์ผ๋ก ๊ฒ์ฆ๋์ด ์ฌ์ฉ์๊ฐ ์ค์๋ฅผ ๋ฐฉ์งํ ์ ์์ต๋๋ค. ๋ฉ์ธ ํ์ด์ง์ ์ต๊ทผ ๋ง๋ค์ด์ง ๋น๊ณ ํ์ด ํ์๋์ด ๋ค๋ฅธ ์ฌ์ฉ์๋ค์ ๋น๊ณ ํ์ ์ฝ๊ฒ ํ์ํ ์ ์์ต๋๋ค. ์ ์ฒด์ ์ผ๋ก ์์ฑ๋ ๋์ ๋น๊ณ ๊ฒ์ ํ๋ซํผ์ด ์์ฑ๋ฉ๋๋ค.
์ ์ถ๋ ์ํ (0)
๋ก๊ทธ์ธ ํ ์ ์ถ์์ง ์ ์ถ๋ ์ํ์ด ์์ต๋๋ค. ์ฒซ ๋ฒ์งธ ๋์ ์๊ฐ ๋์ด๋ณด์ธ์!