๐ฏ g5se ์จ๋ผ์ธ ๊ฐ์๋ฐ์๋ณด ํ ๋๋จผํธ - ์ค์๊ฐ ๋์งํ & ์ฐ์น์ ๊ธฐ๋ก
g5se-rps-tournament
๋ฌธ์ ์ค๋ช
๐ฎ ํ๋ก์ ํธ ๊ฐ์
์น๊ตฌ๋ค๊ณผ ํจ๊ป ์ฆ๊ธฐ๋ ๊ฐ์๋ฐ์๋ณด ํ ๋๋จผํธ ํ๋ซํผ! ์ฐธ๊ฐ์๋ฅผ ๋ฑ๋กํ๊ณ , ์๋์ผ๋ก ์์ฑ๋๋ ํ ๋๋จผํธ ๋์งํ์์ ํ ๊ฒฝ๊ธฐ์ฉ ๊ฒฐ๊ณผ๋ฅผ ์ ๋ ฅํ๋ฉด ๋ค์ ๋ผ์ด๋๊ฐ ์๋์ผ๋ก ๊ตฌ์ฑ๋ฉ๋๋ค. ์ต์ข ์ฐ์น์๊ฐ ๊ฒฐ์ ๋๋ฉด ๋ช ์์ ์ ๋น์ ๊ธฐ๋ก๋์ด ์ญ๋ ์ฑํผ์ธ์ ํ์ธํ ์ ์์ต๋๋ค.
โจ ํต์ฌ ๊ธฐ๋ฅ
ํ์ ๊ธฐ๋ฅ
- ํ ๋๋จผํธ ์์ฑ ํผ (ํ ๋๋จผํธ๋ช , ์ฐธ๊ฐ์ ์ด๋ฆ ์ ๋ ฅ)
- ์ฐธ๊ฐ์ ์์ ๋ฐ๋ฅธ ์๋ ๋์งํ ์์ฑ (4/8/16๋ช ์ง์)
- ๊ฒฝ๊ธฐ ๊ฒฐ๊ณผ ์ ๋ ฅ ์ธํฐํ์ด์ค (๊ฐ์/๋ฐ์/๋ณด ์ ํ)
- ๋ผ์ด๋๋ณ ์งํ ์ํฉ ์๊ฐํ (16๊ฐ โ 8๊ฐ โ 4๊ฐ โ ๊ฒฐ์น)
- ์ฐ์น์ ๊ฒฐ์ ์ ๋ช ์์ ์ ๋น ์๋ ๋ฑ๋ก
- ์งํ ์ค์ธ ํ ๋๋จผํธ ๋ชฉ๋ก & ์๋ฃ๋ ํ ๋๋จผํธ ์์นด์ด๋ธ
๋ณด๋์ค ๊ธฐ๋ฅ
- ์ฐธ๊ฐ์๋ณ ์น๋ฅ ํต๊ณ (์ ์ฒด ๊ฒฝ๊ธฐ ์ค ์น๋ฆฌ ๋น์จ)
- ํ ๋๋จผํธ ํ์คํ ๋ฆฌ ์์ธ ๋ณด๊ธฐ (๊ฐ ๊ฒฝ๊ธฐ ๊ฒฐ๊ณผ ๋ฆฌํ๋ ์ด)
- ๋คํฌ๋ชจ๋์์๋ ๊ฐ๋ ์ฑ ์ข์ ๋์งํ ๋์์ธ
- ๋ชจ๋ฐ์ผ ๋ฐ์ํ ๋ ์ด์์ (ํฐ์น๋ก ๊ฒฐ๊ณผ ์ ๋ ฅ)
๐ฏ ํ์ต ๋ชฉํ
- g5se ํด๋ ๊ธฐ๋ฐ ์๋ ๋ผ์ฐํ ์ดํด
- modern shell๋ก ์ผ๊ด๋ UI ๊ตฌ์ฑ
- SQLite ๊ธฐ๋ฐ ๋ ๋ฆฝ DB ์ค๊ณ (ํ ๋๋จผํธ, ๊ฒฝ๊ธฐ, ์ฐธ๊ฐ์ ํ ์ด๋ธ)
- ํ ๋๋จผํธ ๋ธ๋ํท ์๊ณ ๋ฆฌ์ฆ ๊ตฌํ (๋จ์ผ ํ๋ฝ์ )
- CSS ๋ณ์๋ฅผ ํ์ฉํ ๋คํฌ๋ชจ๋ ์๋ ๋์
๐ ๊ธฐ์ ์คํ
- PHP (๊ทธ๋๋ณด๋5 SE)
- SQLite (๋ชจ๋ ์ ์ฉ DB)
- HTML/CSS/JavaScript (๋ฐ๋๋ผ)
- modern shell (g5se ํ ๋ง ์์คํ )
๐ ์ ์ฝ ์กฐ๊ฑด
- ์ธ๋ถ ๋ผ์ด๋ธ๋ฌ๋ฆฌ/CDN ์ฌ์ฉ ๊ธ์ง (์์ ๋ฐ๋๋ผ JS)
- g5se ๋ฉ์ธ DB์ ํ ์ด๋ธ ์ถ๊ฐ ๊ธ์ง (๋ ๋ฆฝ SQLite ์ฌ์ฉ)
- ์ฐธ๊ฐ์ ์๋ 2์ ๊ฑฐ๋ญ์ ๊ณฑ (4/8/16๋ช )๋ง ์ง์
- ๊ฒฝ๊ธฐ ๊ฒฐ๊ณผ๋ ๊ฐ์/๋ฐ์/๋ณด ์ค ํ๋๋ง ์ ํ ๊ฐ๋ฅ
์ ์ฝ ์กฐ๊ฑด
- โข g5se ์ค์น๋ณธ ์์ ํ์ผ ์ถ๊ฐ (์ ํด๋ ์์ฑ ๊ธ์ง)
- โข app/modules/rps-tournament/index.php๋ก ํ์ด์ง ์์ฑ
- โข modern shell ํ์ ์ฌ์ฉ (_head.inc.php + m-shell + _nav.inc.php + m-container + _footer.inc.php)
- โข ๋ ๋ฆฝ SQLite DB ์ฌ์ฉ: G5_DATA_PATH.'/modules/rps_tournament.sqlite'
- โข ๋์์ธ ํ ํฐ ์ฌ์ฉ: var(--m-surface), var(--m-text), var(--m-shadow) ๋ฑ
- โข ์ธ๋ถ CDN/๋ผ์ด๋ธ๋ฌ๋ฆฌ ๊ธ์ง (์์ ๋ฐ๋๋ผ JS)
- โข ์ฐธ๊ฐ์ ์๋ 4/8/16๋ช ์ผ๋ก ์ ํ (2์ ๊ฑฐ๋ญ์ ๊ณฑ)
- โข htmlspecialchars()๋ก XSS ๋ฐฉ์ง
- โข prepared statement๋ก SQL ์ธ์ ์ ๋ฐฉ์ง
ํ๋กฌํํธ ๋ ์ํผ
์๋ ํ๋กฌํํธ๋ฅผ ์์๋๋ก AI์๊ฒ ๋ณด๋ด๋ฉด ๋ฉ๋๋ค. ๋ณต์ฌ ๋ฒํผ์ ๋๋ฌ ๋ฐ๋ก ์ฌ์ฉํ์ธ์.
g5se๊ฐ ์ค์น๋ ๋๋ ํฐ๋ฆฌ(๋ฃจํธ์ `index.php`ยท`app/`์ด ์๋ ๊ณณ)์์ ์์
์ ์์ํฉ๋๋ค. (g5se ์ค์น๋ https://github.com/gnuboard/g5se ์ฐธ๊ณ )
**1๋จ๊ณ: ๊ธฐ๋ณธ ํ์ด์ง ๊ณจ๊ฒฉ ์์ฑ**
`app/modules/rps-tournament/index.php` ํ์ผ์ ์์ฑํ๊ณ ๋ค์ ๋ด์ฉ์ ์์ฑํ์ธ์:
```php
<?php
include_once('./_common.php');
// SQLite DB ์ด๊ธฐํ
@mkdir(G5_DATA_PATH.'/modules', 0707, true);
$db_path = G5_DATA_PATH.'/modules/rps_tournament.sqlite';
$db = new PDO('sqlite:'.$db_path);
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// ํ
์ด๋ธ ์์ฑ (์ฒซ ์ ๊ทผ ์)
$db->exec("
CREATE TABLE IF NOT EXISTS tournaments (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
player_count INTEGER NOT NULL,
status TEXT DEFAULT 'ongoing',
winner_name TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)
");
$db->exec("
CREATE TABLE IF NOT EXISTS matches (
id INTEGER PRIMARY KEY AUTOINCREMENT,
tournament_id INTEGER NOT NULL,
round INTEGER NOT NULL,
match_number INTEGER NOT NULL,
player1_name TEXT,
player2_name TEXT,
player1_choice TEXT,
player2_choice TEXT,
winner_name TEXT,
FOREIGN KEY(tournament_id) REFERENCES tournaments(id)
)
");
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;">
<style>
.tournament-header {
text-align: center;
margin-bottom: 40px;
}
.tournament-header h1 {
font-size: 2rem;
font-weight: 700;
color: var(--m-text);
margin-bottom: 8px;
}
.tournament-header p {
font-size: 1rem;
color: var(--m-text-muted);
}
.tab-buttons {
display: flex;
gap: 12px;
justify-content: center;
margin-bottom: 32px;
}
.tab-btn {
padding: 12px 24px;
background: var(--m-surface);
border: 2px solid #e0e0e0;
border-radius: var(--m-radius-lg);
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
}
.tab-btn.active {
background: #4CAF50;
color: white;
border-color: #4CAF50;
}
.content-section {
display: none;
}
.content-section.active {
display: block;
}
.create-form {
max-width: 500px;
margin: 0 auto;
padding: 32px;
background: var(--m-surface);
border-radius: var(--m-radius-lg);
box-shadow: var(--m-shadow);
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
font-weight: 600;
margin-bottom: 8px;
color: var(--m-text);
}
.form-group input, .form-group select, .form-group textarea {
width: 100%;
padding: 12px;
border: 2px solid #e0e0e0;
border-radius: 8px;
font-size: 1rem;
background: var(--m-surface);
color: var(--m-text);
}
.form-group textarea {
min-height: 120px;
resize: vertical;
}
.submit-btn {
width: 100%;
padding: 14px;
background: #4CAF50;
color: white;
border: none;
border-radius: 8px;
font-size: 1.1rem;
font-weight: 700;
cursor: pointer;
transition: background 0.2s;
}
.submit-btn:hover {
background: #45a049;
}
</style>
<div class="tournament-header">
<h1>๐ฏ ๊ฐ์๋ฐ์๋ณด ํ ๋๋จผํธ</h1>
<p>์น๊ตฌ๋ค๊ณผ ํจ๊ป ์ฆ๊ธฐ๋ ์ค์๊ฐ ๋์งํ & ์ฐ์น์ ๊ธฐ๋ก</p>
</div>
<div class="tab-buttons">
<button class="tab-btn active" onclick="showTab('create')">์ ํ ๋๋จผํธ</button>
<button class="tab-btn" onclick="showTab('ongoing')">์งํ ์ค</button>
<button class="tab-btn" onclick="showTab('archive')">๋ช
์์ ์ ๋น</button>
</div>
<div id="create-section" class="content-section active">
<form method="POST" class="create-form">
<input type="hidden" name="action" value="create_tournament">
<div class="form-group">
<label>ํ ๋๋จผํธ ์ด๋ฆ</label>
<input type="text" name="tournament_name" placeholder="์: 2024 ๋ด ์ฑํผ์ธ์ญ" required>
</div>
<div class="form-group">
<label>์ฐธ๊ฐ์ ์</label>
<select name="player_count" required>
<option value="4">4๋ช
(2๋ผ์ด๋)</option>
<option value="8">8๋ช
(3๋ผ์ด๋)</option>
<option value="16">16๋ช
(4๋ผ์ด๋)</option>
</select>
</div>
<div class="form-group">
<label>์ฐธ๊ฐ์ ์ด๋ฆ (ํ ์ค์ ํ ๋ช
์ฉ)</label>
<textarea name="player_names" placeholder="ํ๊ธธ๋\n๊น์ฒ ์\n์ด์ํฌ\n๋ฐ๋ฏผ์" required></textarea>
</div>
<button type="submit" class="submit-btn">๐ ํ ๋๋จผํธ ์์</button>
</form>
</div>
<div id="ongoing-section" class="content-section">
<p style="text-align:center; color: var(--m-text-muted);">์งํ ์ค์ธ ํ ๋๋จผํธ๊ฐ ์์ต๋๋ค.</p>
</div>
<div id="archive-section" class="content-section">
<p style="text-align:center; color: var(--m-text-muted);">์๋ฃ๋ ํ ๋๋จผํธ๊ฐ ์์ต๋๋ค.</p>
</div>
<script>
function showTab(tab) {
document.querySelectorAll('.tab-btn').forEach(btn => btn.classList.remove('active'));
document.querySelectorAll('.content-section').forEach(sec => sec.classList.remove('active'));
event.target.classList.add('active');
document.getElementById(tab + '-section').classList.add('active');
}
</script>
</main>
<?php require G5_THEME_PATH.'/modern/_footer.inc.php'; ?>
</div>
<?php include_once('./tail.sub.php'); ?>
```
์ด ํ์ผ์ ์๋์ผ๋ก `/rps-tournament` URL์ ๋งคํ๋ฉ๋๋ค.
๊ธฐ๋ ๊ฒฐ๊ณผ: ๋ธ๋ผ์ฐ์ ์์ `/rps-tournament`์ ์ ์ํ๋ฉด modern shell ์คํ์ผ์ ํ์ด์ง๊ฐ ํ์๋๊ณ , 3๊ฐ ํญ(์ ํ ๋๋จผํธ/์งํ ์ค/๋ช ์์ ์ ๋น)๊ณผ ํ ๋๋จผํธ ์์ฑ ํผ์ด ๋ณด์ ๋๋ค. SQLite DB์ tournaments, matches ํ ์ด๋ธ์ด ์๋ ์์ฑ๋ฉ๋๋ค.
**2๋จ๊ณ: ํ ๋๋จผํธ ์์ฑ & ๋์งํ ์๋ ์์ฑ ๋ก์ง ๊ตฌํ**
`app/modules/rps-tournament/index.php` ํ์ผ์ `include_once('./_common.php');` ๋ฐ๋ก ์๋์ ๋ค์ ๋ก์ง์ ์ถ๊ฐํ์ธ์:
```php
// ํ ๋๋จผํธ ์์ฑ ์ฒ๋ฆฌ
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'create_tournament') {
$tournament_name = trim($_POST['tournament_name']);
$player_count = (int)$_POST['player_count'];
$player_names = array_filter(array_map('trim', explode("\n", $_POST['player_names'])));
if (count($player_names) !== $player_count) {
echo "<script>alert('์ฐธ๊ฐ์ ์๊ฐ ๋ง์ง ์์ต๋๋ค. {$player_count}๋ช
์ ์
๋ ฅํด์ฃผ์ธ์.'); history.back();</script>";
exit;
}
// ํ ๋๋จผํธ ์์ฑ
$stmt = $db->prepare("INSERT INTO tournaments (name, player_count, status) VALUES (?, ?, 'ongoing')");
$stmt->execute([$tournament_name, $player_count]);
$tournament_id = $db->lastInsertId();
// 1๋ผ์ด๋ ๋์ง ์์ฑ (์์๋๋ก ๋งค์นญ)
shuffle($player_names); // ๋ฌด์์ ๋ฐฐ์น
$match_number = 1;
for ($i = 0; $i < count($player_names); $i += 2) {
$stmt = $db->prepare("
INSERT INTO matches (tournament_id, round, match_number, player1_name, player2_name)
VALUES (?, 1, ?, ?, ?)
");
$stmt->execute([$tournament_id, $match_number, $player_names[$i], $player_names[$i+1]]);
$match_number++;
}
echo "<script>alert('ํ ๋๋จผํธ๊ฐ ์์ฑ๋์์ต๋๋ค!'); location.href='/rps-tournament';</script>";
exit;
}
```
๊ทธ๋ฆฌ๊ณ "์งํ ์ค" ํญ ์น์
์ ๋ค์๊ณผ ๊ฐ์ด ์์ ํ์ธ์:
```php
<div id="ongoing-section" class="content-section">
<style>
.tournament-list {
display: grid;
gap: 20px;
max-width: 800px;
margin: 0 auto;
}
.tournament-card {
padding: 24px;
background: var(--m-surface);
border-radius: var(--m-radius-lg);
box-shadow: var(--m-shadow);
}
.tournament-card h3 {
font-size: 1.3rem;
font-weight: 700;
color: var(--m-text);
margin-bottom: 12px;
}
.tournament-meta {
display: flex;
gap: 16px;
font-size: 0.9rem;
color: var(--m-text-muted);
margin-bottom: 16px;
}
.view-bracket-btn {
display: inline-block;
padding: 10px 20px;
background: #2196F3;
color: white;
border-radius: 8px;
text-decoration: none;
font-weight: 600;
transition: background 0.2s;
}
.view-bracket-btn:hover {
background: #1976D2;
}
</style>
<?php
$stmt = $db->query("SELECT * FROM tournaments WHERE status = 'ongoing' ORDER BY created_at DESC");
$ongoing = $stmt->fetchAll(PDO::FETCH_ASSOC);
if (empty($ongoing)) {
echo '<p style="text-align:center; color: var(--m-text-muted);">์งํ ์ค์ธ ํ ๋๋จผํธ๊ฐ ์์ต๋๋ค.</p>';
} else {
echo '<div class="tournament-list">';
foreach ($ongoing as $t) {
$created = date('Y-m-d H:i', strtotime($t['created_at']));
echo "
<div class='tournament-card'>
<h3>๐ ".htmlspecialchars($t['name'])."</h3>
<div class='tournament-meta'>
<span>๐ฅ {$t['player_count']}๋ช
</span>
<span>๐
{$created}</span>
</div>
<a href='/rps-tournament?view={$t['id']}' class='view-bracket-btn'>๋์งํ ๋ณด๊ธฐ</a>
</div>
";
}
echo '</div>';
}
?>
</div>
```
๊ธฐ๋ ๊ฒฐ๊ณผ: ํ ๋๋จผํธ ์์ฑ ํผ์ ์ ์ถํ๋ฉด DB์ ํ ๋๋จผํธ์ 1๋ผ์ด๋ ๊ฒฝ๊ธฐ๊ฐ ์์ฑ๋๊ณ , "์งํ ์ค" ํญ์์ ์์ฑ๋ ํ ๋๋จผํธ ๋ชฉ๋ก์ ์นด๋ ํํ๋ก ํ์ธํ ์ ์์ต๋๋ค. ๊ฐ ์นด๋์์ "๋์งํ ๋ณด๊ธฐ" ๋ฒํผ์ด ํ์๋ฉ๋๋ค.
**3๋จ๊ณ: ํ ๋๋จผํธ ๋์งํ ์๊ฐํ & ๊ฒฝ๊ธฐ ๊ฒฐ๊ณผ ์
๋ ฅ UI**
`app/modules/rps-tournament/index.php` ํ์ผ์ `// SQLite DB ์ด๊ธฐํ` ์น์
๋ฐ๋ก ์๋์ ๋ค์ ๋ก์ง์ ์ถ๊ฐํ์ธ์:
```php
// ํน์ ํ ๋๋จผํธ ์กฐํ
if (isset($_GET['view'])) {
$tournament_id = (int)$_GET['view'];
$stmt = $db->prepare("SELECT * FROM tournaments WHERE id = ?");
$stmt->execute([$tournament_id]);
$tournament = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$tournament) {
echo "<script>alert('์กด์ฌํ์ง ์๋ ํ ๋๋จผํธ์
๋๋ค.'); location.href='/rps-tournament';</script>";
exit;
}
// ๋ผ์ด๋๋ณ ๊ฒฝ๊ธฐ ์กฐํ
$stmt = $db->prepare("SELECT * FROM matches WHERE tournament_id = ? ORDER BY round, match_number");
$stmt->execute([$tournament_id]);
$all_matches = $stmt->fetchAll(PDO::FETCH_ASSOC);
$rounds = [];
foreach ($all_matches as $m) {
$rounds[$m['round']][] = $m;
}
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;">
<style>
.bracket-header {
text-align: center;
margin-bottom: 32px;
}
.bracket-header h1 {
font-size: 1.8rem;
font-weight: 700;
color: var(--m-text);
margin-bottom: 8px;
}
.bracket-header .status-badge {
display: inline-block;
padding: 6px 16px;
background: #4CAF50;
color: white;
border-radius: 20px;
font-size: 0.9rem;
font-weight: 600;
}
.rounds-container {
display: flex;
gap: 32px;
overflow-x: auto;
padding: 20px;
justify-content: center;
}
.round-column {
min-width: 280px;
}
.round-title {
text-align: center;
font-size: 1.1rem;
font-weight: 700;
color: var(--m-text);
margin-bottom: 20px;
}
.match-card {
padding: 16px;
background: var(--m-surface);
border: 2px solid #e0e0e0;
border-radius: var(--m-radius-lg);
margin-bottom: 16px;
}
.match-card.completed {
border-color: #4CAF50;
}
.player-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
margin-bottom: 8px;
background: rgba(0,0,0,0.02);
border-radius: 6px;
}
.player-row.winner {
background: #4CAF50;
color: white;
font-weight: 700;
}
.player-name {
font-weight: 600;
}
.rps-buttons {
display: flex;
gap: 8px;
margin-top: 12px;
}
.rps-btn {
flex: 1;
padding: 8px;
background: #2196F3;
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 1.2rem;
transition: background 0.2s;
}
.rps-btn:hover {
background: #1976D2;
}
</style>
<div class="bracket-header">
<h1>๐ <?php echo htmlspecialchars($tournament['name']); ?></h1>
<span class="status-badge"><?php echo $tournament['status'] === 'ongoing' ? '์งํ ์ค' : '์๋ฃ'; ?></span>
</div>
<div class="rounds-container">
<?php
$round_names = [1 => '1๋ผ์ด๋', 2 => '์ค๊ฒฐ์น', 3 => '์ค์ค๊ฒฐ์น', 4 => '16๊ฐ'];
foreach ($rounds as $round_num => $matches) {
echo "<div class='round-column'>";
echo "<div class='round-title'>{$round_names[$round_num]}</div>";
foreach ($matches as $match) {
$is_completed = !empty($match['winner_name']);
echo "<div class='match-card ".($is_completed ? 'completed' : '').">";
// Player 1
$p1_winner = ($match['winner_name'] === $match['player1_name']);
echo "<div class='player-row ".($p1_winner ? 'winner' : '').">";
echo "<span class='player-name'>".htmlspecialchars($match['player1_name']).".";
if ($match['player1_choice']) echo " (".htmlspecialchars($match['player1_choice']).")";
echo "</span>";
if ($p1_winner) echo "<span>๐</span>";
echo "</div>";
// Player 2
$p2_winner = ($match['winner_name'] === $match['player2_name']);
echo "<div class='player-row ".($p2_winner ? 'winner' : '').">";
echo "<span class='player-name'>".htmlspecialchars($match['player2_name']).".";
if ($match['player2_choice']) echo " (".htmlspecialchars($match['player2_choice']).")";
echo "</span>";
if ($p2_winner) echo "<span>๐</span>";
echo "</div>";
// ๊ฒฐ๊ณผ ์
๋ ฅ ๋ฒํผ (๋ฏธ์๋ฃ ๊ฒฝ๊ธฐ๋ง)
if (!$is_completed) {
echo "
<form method='POST' style='margin-top: 12px;'>
<input type='hidden' name='action' value='set_result'>
<input type='hidden' name='match_id' value='{$match['id']}'>
<input type='hidden' name='tournament_id' value='{$tournament_id}'>
<div style='margin-bottom: 8px; font-size: 0.9rem; color: var(--m-text-muted);'>Player 1 ์ ํ:</div>
<div class='rps-buttons'>
<button type='submit' name='p1_choice' value='๊ฐ์' class='rps-btn'>โ๏ธ</button>
<button type='submit' name='p1_choice' value='๋ฐ์' class='rps-btn'>โ</button>
<button type='submit' name='p1_choice' value='๋ณด' class='rps-btn'>๐</button>
</div>
<div style='margin: 12px 0 8px; font-size: 0.9rem; color: var(--m-text-muted);'>Player 2 ์ ํ:</div>
<div class='rps-buttons'>
<button type='submit' name='p2_choice' value='๊ฐ์' class='rps-btn'>โ๏ธ</button>
<button type='submit' name='p2_choice' value='๋ฐ์' class='rps-btn'>โ</button>
<button type='submit' name='p2_choice' value='๋ณด' class='rps-btn'>๐</button>
</div>
</form>
";
}
echo "</div>";
}
echo "</div>";
}
?>
</div>
<div style="text-align: center; margin-top: 32px;">
<a href="/rps-tournament" style="display: inline-block; padding: 12px 24px; background: var(--m-surface); border: 2px solid #e0e0e0; border-radius: 8px; text-decoration: none; color: var(--m-text); font-weight: 600;">โ ๋ชฉ๋ก์ผ๋ก</a>
</div>
</main>
<?php require G5_THEME_PATH.'/modern/_footer.inc.php'; ?>
</div>
<?php
include_once('./tail.sub.php');
exit;
}
```
์ด์ "๋์งํ ๋ณด๊ธฐ" ๋ฒํผ์ ํด๋ฆญํ๋ฉด ๋ผ์ด๋๋ณ ๊ฒฝ๊ธฐ ๋ชฉ๋ก๊ณผ ๊ฒฐ๊ณผ ์
๋ ฅ ๋ฒํผ์ด ํ์๋ฉ๋๋ค.
๊ธฐ๋ ๊ฒฐ๊ณผ: "๋์งํ ๋ณด๊ธฐ"๋ฅผ ํด๋ฆญํ๋ฉด ํ ๋๋จผํธ ์ ์ฉ ํ์ด์ง๋ก ์ด๋ํ๊ณ , ๋ผ์ด๋๋ณ๋ก ๊ฒฝ๊ธฐ๊ฐ ์นด๋ ํํ๋ก ํ์๋ฉ๋๋ค. ๊ฐ ๊ฒฝ๊ธฐ์์ Player 1๊ณผ Player 2์ ๊ฐ์/๋ฐ์/๋ณด ์ ํ ๋ฒํผ์ด ๋ณด์ด๋ฉฐ, ์๋ฃ๋ ๊ฒฝ๊ธฐ๋ ์ด๋ก์ ํ ๋๋ฆฌ์ ์ฐ์น์ ํ์๊ฐ ๋ํ๋ฉ๋๋ค.
**4๋จ๊ณ: ๊ฒฝ๊ธฐ ๊ฒฐ๊ณผ ์ฒ๋ฆฌ & ๋ค์ ๋ผ์ด๋ ์๋ ์์ฑ ๋ก์ง**
`app/modules/rps-tournament/index.php` ํ์ผ์ ํ ๋๋จผํธ ์์ฑ ๋ก์ง ๋ฐ๋ก ์๋์ ๋ค์ ์ฝ๋๋ฅผ ์ถ๊ฐํ์ธ์:
```php
// ๊ฒฝ๊ธฐ ๊ฒฐ๊ณผ ์
๋ ฅ ์ฒ๋ฆฌ
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'set_result') {
$match_id = (int)$_POST['match_id'];
$tournament_id = (int)$_POST['tournament_id'];
$p1_choice = $_POST['p1_choice'] ?? null;
$p2_choice = $_POST['p2_choice'] ?? null;
if (!$p1_choice || !$p2_choice) {
echo "<script>alert('์์ชฝ ์ ํ์ ๋ชจ๋ ์
๋ ฅํด์ฃผ์ธ์.'); history.back();</script>";
exit;
}
// ๊ฒฝ๊ธฐ ์ ๋ณด ์กฐํ
$stmt = $db->prepare("SELECT * FROM matches WHERE id = ?");
$stmt->execute([$match_id]);
$match = $stmt->fetch(PDO::FETCH_ASSOC);
// ์น์ ๊ฒฐ์ ๋ก์ง
$winner = null;
if ($p1_choice === $p2_choice) {
echo "<script>alert('๋ฌด์น๋ถ์
๋๋ค! ๋ค์ ์ ํํด์ฃผ์ธ์.'); history.back();</script>";
exit;
} elseif (
($p1_choice === '๊ฐ์' && $p2_choice === '๋ณด') ||
($p1_choice === '๋ฐ์' && $p2_choice === '๊ฐ์') ||
($p1_choice === '๋ณด' && $p2_choice === '๋ฐ์')
) {
$winner = $match['player1_name'];
} else {
$winner = $match['player2_name'];
}
// ๊ฒฝ๊ธฐ ๊ฒฐ๊ณผ ์
๋ฐ์ดํธ
$stmt = $db->prepare("
UPDATE matches SET player1_choice = ?, player2_choice = ?, winner_name = ?
WHERE id = ?
");
$stmt->execute([$p1_choice, $p2_choice, $winner, $match_id]);
// ํ์ฌ ๋ผ์ด๋์ ๋ชจ๋ ๊ฒฝ๊ธฐ๊ฐ ์๋ฃ๋์๋์ง ํ์ธ
$stmt = $db->prepare("
SELECT COUNT(*) as total, SUM(CASE WHEN winner_name IS NOT NULL THEN 1 ELSE 0 END) as completed
FROM matches WHERE tournament_id = ? AND round = ?
");
$stmt->execute([$tournament_id, $match['round']]);
$round_status = $stmt->fetch(PDO::FETCH_ASSOC);
// ๋ผ์ด๋ ์๋ฃ ์ ๋ค์ ๋ผ์ด๋ ์์ฑ
if ($round_status['total'] == $round_status['completed']) {
// ํ์ฌ ๋ผ์ด๋ ์น์ ๋ชฉ๋ก
$stmt = $db->prepare("SELECT winner_name FROM matches WHERE tournament_id = ? AND round = ? ORDER BY match_number");
$stmt->execute([$tournament_id, $match['round']]);
$winners = $stmt->fetchAll(PDO::FETCH_COLUMN);
if (count($winners) === 1) {
// ์ต์ข
์ฐ์น์ ๊ฒฐ์
$stmt = $db->prepare("UPDATE tournaments SET status = 'completed', winner_name = ? WHERE id = ?");
$stmt->execute([$winners[0], $tournament_id]);
echo "<script>alert('๐ ์ฐ์น์: {$winners[0]}'); location.href='/rps-tournament?view={$tournament_id}';</script>";
exit;
} else {
// ๋ค์ ๋ผ์ด๋ ์์ฑ
$next_round = $match['round'] + 1;
$match_number = 1;
for ($i = 0; $i < count($winners); $i += 2) {
$stmt = $db->prepare("
INSERT INTO matches (tournament_id, round, match_number, player1_name, player2_name)
VALUES (?, ?, ?, ?, ?)
");
$stmt->execute([$tournament_id, $next_round, $match_number, $winners[$i], $winners[$i+1]]);
$match_number++;
}
}
}
echo "<script>location.href='/rps-tournament?view={$tournament_id}';</script>";
exit;
}
```
์ด์ ๊ฒฝ๊ธฐ ๊ฒฐ๊ณผ๋ฅผ ์
๋ ฅํ๋ฉด ์น์๊ฐ ์๋์ผ๋ก ๊ฒฐ์ ๋๊ณ , ๋ผ์ด๋๊ฐ ์๋ฃ๋๋ฉด ๋ค์ ๋ผ์ด๋๊ฐ ์๋ ์์ฑ๋ฉ๋๋ค.
๊ธฐ๋ ๊ฒฐ๊ณผ: ๊ฒฝ๊ธฐ์์ ๊ฐ์/๋ฐ์/๋ณด๋ฅผ ์ ํํ๋ฉด ์น์๊ฐ ๊ฒฐ์ ๋๊ณ ๊ฒฝ๊ธฐ ์นด๋๊ฐ ์ด๋ก์ ํ ๋๋ฆฌ๋ก ๋ณ๊ฒฝ๋ฉ๋๋ค. ํ ๋ผ์ด๋์ ๋ชจ๋ ๊ฒฝ๊ธฐ๊ฐ ์๋ฃ๋๋ฉด ์๋์ผ๋ก ๋ค์ ๋ผ์ด๋๊ฐ ์์ฑ๋๋ฉฐ, ์ต์ข ์ฐ์น์๊ฐ ๊ฒฐ์ ๋๋ฉด alert๋ก ์ถํ ๋ฉ์์ง๊ฐ ํ์๋ฉ๋๋ค.
**5๋จ๊ณ: ๋ช
์์ ์ ๋น ๊ตฌํ & ๋ชจ๋ฐ์ผ ๋ฐ์ํ ๋์์ธ**
`app/modules/rps-tournament/index.php` ํ์ผ์ "๋ช
์์ ์ ๋น" ํญ ์น์
์ ๋ค์๊ณผ ๊ฐ์ด ์์ ํ์ธ์:
```php
<div id="archive-section" class="content-section">
<style>
.hall-of-fame {
max-width: 900px;
margin: 0 auto;
}
.champion-card {
display: flex;
align-items: center;
gap: 24px;
padding: 24px;
background: var(--m-surface);
border-radius: var(--m-radius-lg);
box-shadow: var(--m-shadow);
margin-bottom: 20px;
border-left: 6px solid #FFD700;
}
.champion-trophy {
font-size: 3rem;
}
.champion-info h3 {
font-size: 1.4rem;
font-weight: 700;
color: var(--m-text);
margin-bottom: 8px;
}
.champion-meta {
display: flex;
gap: 16px;
font-size: 0.9rem;
color: var(--m-text-muted);
}
@media (max-width: 768px) {
.rounds-container {
flex-direction: column;
}
.round-column {
width: 100%;
}
.champion-card {
flex-direction: column;
text-align: center;
}
}
</style>
<?php
$stmt = $db->query("SELECT * FROM tournaments WHERE status = 'completed' ORDER BY created_at DESC");
$completed = $stmt->fetchAll(PDO::FETCH_ASSOC);
if (empty($completed)) {
echo '<p style="text-align:center; color: var(--m-text-muted);">์๋ฃ๋ ํ ๋๋จผํธ๊ฐ ์์ต๋๋ค.</p>';
} else {
echo '<div class="hall-of-fame">';
foreach ($completed as $t) {
$created = date('Y-m-d', strtotime($t['created_at']));
echo "
<div class='champion-card'>
<div class='champion-trophy'>๐</div>
<div class='champion-info'>
<h3>".htmlspecialchars($t['winner_name'])."</h3>
<div class='champion-meta'>
<span>๐ฏ ".htmlspecialchars($t['name'])."</span>
<span>๐ฅ {$t['player_count']}๋ช
์ฐธ๊ฐ</span>
<span>๐
{$created}</span>
</div>
</div>
</div>
";
}
echo '</div>';
}
?>
</div>
```
๊ทธ๋ฆฌ๊ณ ํ์ด์ง ์ต์๋จ์ `<style>` ๋ธ๋ก์ ๋ค์ ๋ฏธ๋์ด ์ฟผ๋ฆฌ๋ฅผ ์ถ๊ฐํ์ธ์:
```css
@media (max-width: 768px) {
.tab-buttons {
flex-direction: column;
}
.tab-btn {
width: 100%;
}
.create-form {
padding: 20px;
}
.rps-buttons {
flex-direction: column;
}
.rps-btn {
width: 100%;
}
}
```
๋ง์ง๋ง์ผ๋ก ๋์งํ ํ์ด์ง ์๋จ์ ํ ๋๋จผํธ ์ ๋ณด ์์ฝ ์นด๋๋ฅผ ์ถ๊ฐํ์ธ์ (`.bracket-header` ์๋):
```php
<div style="max-width: 600px; margin: 0 auto 32px; padding: 20px; background: var(--m-surface); border-radius: var(--m-radius-lg); box-shadow: var(--m-shadow);">
<div style="display: flex; justify-content: space-around; text-align: center;">
<div>
<div style="font-size: 2rem; font-weight: 700; color: #4CAF50;"><?php echo $tournament['player_count']; ?></div>
<div style="font-size: 0.9rem; color: var(--m-text-muted);">์ฐธ๊ฐ์</div>
</div>
<div>
<div style="font-size: 2rem; font-weight: 700; color: #2196F3;"><?php echo max(array_keys($rounds)); ?></div>
<div style="font-size: 0.9rem; color: var(--m-text-muted);">์ด ๋ผ์ด๋</div>
</div>
<div>
<div style="font-size: 2rem; font-weight: 700; color: #FF9800;"><?php echo count($all_matches); ?></div>
<div style="font-size: 0.9rem; color: var(--m-text-muted);">์ด ๊ฒฝ๊ธฐ</div>
</div>
</div>
</div>
```
๊ธฐ๋ ๊ฒฐ๊ณผ: ์๋ฃ๋ ํ ๋๋จผํธ์ ์ฐ์น์๊ฐ "๋ช ์์ ์ ๋น" ํญ์ ๊ธ์ ํธ๋กํผ์ ํจ๊ป ํ์๋ฉ๋๋ค. ๋ชจ๋ฐ์ผ์์ ์ ์ํ๋ฉด ํญ ๋ฒํผ์ด ์ธ๋ก๋ก ๋ฐฐ์น๋๊ณ , ๋์งํ๊ฐ ์คํฌ๋กค ๊ฐ๋ฅํ ์ธ๋ก ๋ ์ด์์์ผ๋ก ๋ณ๊ฒฝ๋ฉ๋๋ค. ํ ๋๋จผํธ ์๋จ์ ์ฐธ๊ฐ์ ์, ์ด ๋ผ์ด๋, ์ด ๊ฒฝ๊ธฐ ์๊ฐ ์์ฝ ์นด๋๋ก ํ์๋ฉ๋๋ค.
์ ์ถ๋ ์ํ (0)
๋ก๊ทธ์ธ ํ ์ ์ถ์์ง ์ ์ถ๋ ์ํ์ด ์์ต๋๋ค. ์ฒซ ๋ฒ์งธ ๋์ ์๊ฐ ๋์ด๋ณด์ธ์!