Compare commits
10
Commits
ff9f235364
...
d3abe9bce7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d3abe9bce7 | ||
|
|
94e1b59389 | ||
|
|
f68ab48c58 | ||
|
|
e4b30117ed | ||
|
|
7a2df21039 | ||
|
|
ddf96d87ac | ||
|
|
2c88aa2a5b | ||
|
|
b74b8f9f17 | ||
|
|
ab75ac4ba2 | ||
|
|
5cc404efde |
No files matched your search
@@ -15,6 +15,7 @@ pokesql/
|
||||
├── index.html ← Game screens & structure
|
||||
├── style.css ← GBA Pokémon aesthetic, fully responsive
|
||||
├── game.js ← Game engine, controller, walking, save system
|
||||
├── config.js ← Game-specific config (name, text, badges, milestones)
|
||||
├── questions.json ← 100 T-SQL questions & answers
|
||||
├── player.png ← Player sprite sheet (24 frames, 16×32px)
|
||||
├── oak.png ← Professor Oak NPC sprite
|
||||
@@ -184,6 +185,12 @@ Then reference `"npc": "Your NPC Name"` in `questions.json`. PNG files are auto-
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ Customising the Game
|
||||
|
||||
All game-specific text lives in **`config.js`** — the only file that differs between PokéSQL and its sister game PokéAzure. Edit it to change the game name, town name, intro dialog, badge names, battle move names, and more without touching `game.js` or `index.html`.
|
||||
|
||||
---
|
||||
|
||||
## 📱 Mobile Tips
|
||||
|
||||
- Add to Home Screen on iOS/Android for a full-screen app-like experience
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
const CONFIG = {
|
||||
gameName: 'PokéSQL',
|
||||
subject: 'SQL',
|
||||
examName: 'SQL',
|
||||
townName: 'SQL Town',
|
||||
saveKey: 'pokesql_save_v2',
|
||||
titleText: 'SQL',
|
||||
titleEdition: '★ SQL TRAINER ★',
|
||||
titleFooter: 'PokéSQL v1.0 · SQL · github.com/JustinOros',
|
||||
townNameUpper:'SQL TOWN',
|
||||
continueLogoSuffix: 'SQL',
|
||||
expLabel: 'SQL EXP',
|
||||
battleMove: 'SQL KNOWLEDGE',
|
||||
battleMiss: 'SQL QUERY',
|
||||
completeMsg: "You've conquered SQL Town and mastered Microsoft T-SQL! Your SQL journey is just beginning — more towns and exams await!",
|
||||
badges: [
|
||||
'🥉 SQL EXPLORER — Q25',
|
||||
'🥈 SQL APPRENTICE — Q50',
|
||||
'🥇 SQL PRACTITIONER — Q75',
|
||||
'🏆 SQL CHAMPION — Q100',
|
||||
],
|
||||
milestones: {
|
||||
25: { badge: '🥉 SQL EXPLORER', stars: '★ ★ ☆ ☆' },
|
||||
50: { badge: '🥈 SQL APPRENTICE', stars: '★ ★ ★ ☆' },
|
||||
75: { badge: '🥇 SQL PRACTITIONER', stars: '★ ★ ★ ★' },
|
||||
},
|
||||
introLines: [
|
||||
"Hello {name}! Welcome to PokéSQL, the world of SQL training! My name is Professor Oak, the SQL Professor!",
|
||||
"Your mission? Journey through SQL Town, challenge SQL masters, and conquer 100 database questions to become the ultimate SQL Trainer!",
|
||||
"But first... every trainer needs a partner Pokémon! Let's choose yours, {name}!",
|
||||
],
|
||||
namePrompt: "Hello there! Welcome to PokéSQL! My name is Professor Oak — the SQL Professor. Now tell me, what is your name?",
|
||||
};
|
||||
@@ -1,16 +1,3 @@
|
||||
const CONFIG = {
|
||||
gameName: 'PokéSQL',
|
||||
subject: 'SQL',
|
||||
examName: 'SQL',
|
||||
townName: 'SQL Town',
|
||||
saveKey: 'pokesql_save_v2',
|
||||
introLines: [
|
||||
"Hello {name}! Welcome to PokéSQL, the world of SQL training! My name is Professor Oak, the SQL Professor!",
|
||||
"Your mission? Journey through SQL Town, challenge SQL masters, and conquer 100 database questions to become the ultimate SQL Trainer!",
|
||||
"But first... every trainer needs a partner Pokémon! Let's choose yours, {name}!",
|
||||
],
|
||||
namePrompt: "Hello there! Welcome to PokéSQL! My name is Professor Oak — the SQL Professor. Now tell me, what is your name?",
|
||||
};
|
||||
|
||||
const ALL_POKEMON = [
|
||||
{ name:'Charmander', emoji:'🔥', type:'Fire' },
|
||||
@@ -81,13 +68,13 @@ const NPC = {
|
||||
'Old Man Bob': 'bob.png',
|
||||
'Nurse Joy': 'joy.png',
|
||||
'Officer Jenny': 'jenny.png',
|
||||
'Brock': '🧑🍳',
|
||||
'Misty': '🧜♀️',
|
||||
'Brock': 'brock.png',
|
||||
'Misty': 'misty.png',
|
||||
'Giovanni': '😈',
|
||||
'Team Rocket Grunt': '🚀',
|
||||
'Scientist Bill': '🔬',
|
||||
'Fisherman Ralph': 'ralph.png',
|
||||
'Hiker Taro': '🧗',
|
||||
'Hiker Taro': 'taro.png',
|
||||
'Gym Leader Surge': '⚡',
|
||||
'Erika': '🌸',
|
||||
'Koga': '🥷',
|
||||
@@ -106,13 +93,9 @@ const INTRO_MSGS = CONFIG.introLines.map(text => ({ speaker:'PROFESSOR OAK', npc
|
||||
const CORRECT_FB = ["That's right!","Excellent work!","Perfect!","Outstanding!","Correct!","You're a natural!","Great answer!","Spot on!","Impressive!","Well done!"];
|
||||
const WRONG_FB = ["Not quite...","Hmm, that's not it.","Try again next time!","Almost...","Not this time!"];
|
||||
|
||||
const MILESTONES = {
|
||||
25: { badge:'🥉 CLOUD EXPLORER', stars:'★ ★ ☆ ☆' },
|
||||
50: { badge:'🥈 AZURE APPRENTICE', stars:'★ ★ ★ ☆' },
|
||||
75: { badge:'🥇 CLOUD PRACTITIONER', stars:'★ ★ ★ ★' },
|
||||
};
|
||||
|
||||
const SAVE_KEY = CONFIG.saveKey;
|
||||
const MILESTONES = CONFIG.milestones;
|
||||
|
||||
function saveGame(s) {
|
||||
try { localStorage.setItem(SAVE_KEY, JSON.stringify({
|
||||
@@ -255,6 +238,7 @@ const SFX = (() => {
|
||||
encounter() { seq([{f:523,d:.1},{f:659,d:.18}], 'square', 0.22); },
|
||||
type() { tone(880 + Math.random()*200, 'square', 0.03, 0.005, 0.01, 0.02); },
|
||||
select() { const c = getCtx(); tone(440, 'square', 0.12, 0.005, 0.03, 0.04, c.currentTime + 0.05); },
|
||||
backspace() { seq([{f:350,d:.05},{f:250,d:.08}], 'triangle', 0.12); },
|
||||
correct() { seq([{f:523,d:.08},{f:659,d:.08},{f:784,d:.08},{f:1047,d:.2}], 'square', 0.18); },
|
||||
streak() { seq([{f:523,d:.06},{f:659,d:.06},{f:784,d:.06},{f:1047,d:.06},{f:1319,d:.2}], 'square', 0.18); },
|
||||
wrong() { seq([{f:330,d:.1},{f:277,d:.1},{f:233,d:.18}], 'sawtooth', 0.15); },
|
||||
@@ -403,10 +387,21 @@ class Game {
|
||||
const keys = this._getNameKeys();
|
||||
const total = keys.length;
|
||||
let c = this._gpKeyCursor;
|
||||
if (dir === 'right') c = Math.min(c + 1, total - 1);
|
||||
if (dir === 'left') c = Math.max(c - 1, 0);
|
||||
if (dir === 'down') c = Math.min(c + COLS, total - 1);
|
||||
if (dir === 'up') c = Math.max(c - COLS, 0);
|
||||
const row = Math.floor(c / COLS);
|
||||
const col = c % COLS;
|
||||
const totalRows = Math.ceil(total / COLS);
|
||||
if (dir === 'right') {
|
||||
c = (col + 1 >= COLS || c + 1 >= total) ? row * COLS : c + 1;
|
||||
}
|
||||
if (dir === 'left') {
|
||||
c = (col - 1 < 0) ? Math.min(row * COLS + COLS - 1, total - 1) : c - 1;
|
||||
}
|
||||
if (dir === 'down') {
|
||||
c = c + COLS >= total ? col : c + COLS;
|
||||
}
|
||||
if (dir === 'up') {
|
||||
c = c - COLS < 0 ? Math.min((totalRows - 1) * COLS + col, total - 1) : c - COLS;
|
||||
}
|
||||
this._gpKeyCursor = c;
|
||||
this._renderNameCursor();
|
||||
SFX.select();
|
||||
@@ -1150,21 +1145,16 @@ class Game {
|
||||
boot() {
|
||||
this.show('boot');
|
||||
this._showController(true);
|
||||
try {
|
||||
const bootAudio = new Audio('./loading.mp3');
|
||||
bootAudio.volume = 0.5;
|
||||
bootAudio.play().catch(() => {});
|
||||
} catch(e) {}
|
||||
setTimeout(() => this.showTitle(), 1800);
|
||||
}
|
||||
|
||||
showTitle() {
|
||||
this.show('title');
|
||||
SFX.boot();
|
||||
this._showController(true);
|
||||
const go = () => {
|
||||
document.getElementById('screen-title').removeEventListener('click', go);
|
||||
Music.unblock();
|
||||
Music.play();
|
||||
SFX.boot();
|
||||
this.showContinueOrName();
|
||||
};
|
||||
document.getElementById('screen-title').addEventListener('click', go);
|
||||
@@ -1283,7 +1273,7 @@ class Game {
|
||||
}
|
||||
|
||||
addChar(ch) { if (this.state.playerName.length<10){ this.state.playerName+=ch; this.refreshNameDisplay(); SFX.select(); } }
|
||||
delChar() { this.state.playerName=this.state.playerName.slice(0,-1); this.refreshNameDisplay(); }
|
||||
delChar() { this.state.playerName=this.state.playerName.slice(0,-1); this.refreshNameDisplay(); SFX.backspace(); }
|
||||
refreshNameDisplay() {
|
||||
const el=document.getElementById('name-display');
|
||||
if(el) el.textContent=this.state.playerName+(this.state.playerName.length<10?'_':'');
|
||||
@@ -1448,6 +1438,7 @@ class Game {
|
||||
showCatchScreen() {
|
||||
const wild = this.state.currentWild;
|
||||
if (!wild) return;
|
||||
this._throwing = false;
|
||||
|
||||
this.show('catch');
|
||||
this._showController(true);
|
||||
@@ -1489,6 +1480,9 @@ class Game {
|
||||
}
|
||||
|
||||
throwPokeball(wild, canCatch) {
|
||||
if (this._throwing) return;
|
||||
if (this.state.pokeballs <= 0) return;
|
||||
this._throwing = true;
|
||||
this.state.pokeballs--;
|
||||
SFX.pokeball();
|
||||
|
||||
@@ -1556,6 +1550,7 @@ class Game {
|
||||
}
|
||||
|
||||
_showCatchResult(wild, canCatch) {
|
||||
this._throwing = false;
|
||||
this.show('catch-result');
|
||||
this._showController(true);
|
||||
|
||||
@@ -2016,7 +2011,7 @@ class Game {
|
||||
const msg = document.getElementById('battle-anim-msg');
|
||||
|
||||
if (correct) {
|
||||
msg.textContent = `${playerPoke.name} used SQL KNOWLEDGE!`;
|
||||
msg.textContent = `${playerPoke.name} used ${CONFIG.battleMove}!`;
|
||||
SFX.attack();
|
||||
setTimeout(() => {
|
||||
npcHp.style.width = '0%';
|
||||
@@ -2032,7 +2027,7 @@ class Game {
|
||||
this.showResult(true, q);
|
||||
}, 2200);
|
||||
} else {
|
||||
msg.textContent = `${playerPoke.name} used SQL QUERY... but it missed!`;
|
||||
msg.textContent = `${playerPoke.name} used ${CONFIG.battleMiss}... but it missed!`;
|
||||
SFX.attack();
|
||||
setTimeout(() => {
|
||||
msg.textContent = `${npcPoke.name} used COUNTER ATTACK!`;
|
||||
|
||||
+32
-13
@@ -2,12 +2,12 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover, user-scalable=no">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, viewport-fit=cover, user-scalable=no">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
||||
<meta name="theme-color" content="#0a0520">
|
||||
<title>PokéSQL — Lean SQL</title>
|
||||
<title id="page-title">PokéSQL — Learn SQL</title>
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
@@ -28,19 +28,19 @@
|
||||
<div class="title-stars"></div>
|
||||
<div class="title-main">
|
||||
<div class="title-logo">
|
||||
<span class="title-poke">POKÉ</span><span class="title-sql">SQL</span>
|
||||
<span class="title-poke">POKÉ</span><span class="title-game" id="title-game-text">SQL</span>
|
||||
</div>
|
||||
<div class="title-edition">★ SQL TRAINER ★</div>
|
||||
<div class="title-edition" id="title-edition-text">★ SQL TRAINER ★</div>
|
||||
<div class="title-tap blink">PRESS START</div>
|
||||
</div>
|
||||
<div class="title-footer">PokéSQL v1.0 · SQL · github.com/JustinOros</div>
|
||||
<div class="title-footer" id="title-footer-text">PokéSQL v1.0 · SQL · github.com/JustinOros</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- CONTINUE OR NEW GAME -->
|
||||
<div id="screen-continue" class="screen">
|
||||
<div class="continue-bg">
|
||||
<div class="continue-logo">POKÉ<span>SQL</span></div>
|
||||
<div class="continue-logo">POKÉ<span id="continue-logo-text">SQL</span></div>
|
||||
<div class="continue-card" id="continue-card">
|
||||
<div class="continue-card-header">📂 CONTINUE</div>
|
||||
<div class="save-row"><span class="save-lbl">TRAINER</span><span class="save-val" id="save-name-disp">—</span></div>
|
||||
@@ -142,7 +142,7 @@
|
||||
<div class="map-header">
|
||||
<div class="map-left">
|
||||
<span class="map-pin">📍</span>
|
||||
<span class="map-town-name">SQL TOWN</span>
|
||||
<span class="map-town-name" id="map-town-name-text">SQL TOWN</span>
|
||||
</div>
|
||||
<div class="map-center">
|
||||
<div class="prog-bar-outer"><div class="prog-bar-inner" id="map-prog-bar"></div></div>
|
||||
@@ -260,7 +260,7 @@
|
||||
<div class="levelup-box">
|
||||
<div class="lu-stars blink" id="lu-stars">★ BADGE EARNED! ★</div>
|
||||
<div class="lu-sprite"><div class="lu-player-sprite"></div></div>
|
||||
<div class="lu-text"><span id="lu-name">ASH</span> gained SQL EXP!</div>
|
||||
<div class="lu-text"><span id="lu-name">ASH</span> gained <span id="exp-label-text">SQL EXP</span>!</div>
|
||||
<div class="lu-badge" id="lu-badge">🏅 CLOUD TRAINEE</div>
|
||||
<div class="lu-prog-wrap">
|
||||
<div class="lu-prog-outer"><div class="lu-prog-inner" id="lu-prog-inner"></div></div>
|
||||
@@ -277,14 +277,14 @@
|
||||
<div class="complete-trophy">🏆</div>
|
||||
<div class="complete-title">CHAMPION!</div>
|
||||
<div class="complete-name" id="complete-name">ASH</div>
|
||||
<p class="complete-msg">You've conquered SQL Town and mastered Microsoft T-SQL! Your SQL journey is just beginning — more towns and exams await!</p>
|
||||
<p class="complete-msg"><span id="complete-msg-text">You've conquered SQL Town and mastered Microsoft T-SQL! Your SQL journey is just beginning — more towns and exams await!</span></p>
|
||||
<div class="complete-stats" id="complete-stats"></div>
|
||||
<div class="complete-pokemon" id="complete-pokemon"></div>
|
||||
<div class="complete-badges">
|
||||
<div class="badge-row">🥉 SQL EXPLORER — Q25</div>
|
||||
<div class="badge-row">🥈 SQL APPRENTICE — Q50</div>
|
||||
<div class="badge-row">🥇 SQL PRACTITIONER — Q75</div>
|
||||
<div class="badge-row">🏆 SQL CHAMPION — Q100</div>
|
||||
<div class="badge-row"><span id="badge-1">🥉 SQL EXPLORER — Q25</span></div>
|
||||
<div class="badge-row"><span id="badge-2">🥈 SQL APPRENTICE — Q50</span></div>
|
||||
<div class="badge-row"><span id="badge-3">🥇 SQL PRACTITIONER — Q75</span></div>
|
||||
<div class="badge-row"><span id="badge-4">🏆 SQL CHAMPION — Q100</span></div>
|
||||
<div class="badge-row">🗺️ More towns & exams coming soon...</div>
|
||||
</div>
|
||||
<button class="btn-pixel btn-red btn-full" id="btn-play-again">▶ PLAY AGAIN</button>
|
||||
@@ -316,6 +316,25 @@
|
||||
</div>
|
||||
|
||||
<div id="toast" class="toast hidden"></div>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const set = (id, val) => { const el = document.getElementById(id); if (el) el.textContent = val; };
|
||||
set('title-game-text', CONFIG.titleText);
|
||||
set('title-edition-text', CONFIG.titleEdition);
|
||||
set('title-footer-text', CONFIG.titleFooter);
|
||||
set('continue-logo-text', CONFIG.continueLogoSuffix);
|
||||
set('map-town-name-text', CONFIG.townNameUpper);
|
||||
set('exp-label-text', CONFIG.expLabel);
|
||||
set('complete-msg-text', CONFIG.completeMsg);
|
||||
if (CONFIG.badges) {
|
||||
CONFIG.badges.forEach((b, i) => set(`badge-${i+1}`, b));
|
||||
}
|
||||
set('page-title', CONFIG.gameName + ' — Learn ' + CONFIG.subject);
|
||||
document.title = CONFIG.gameName + ' — Learn ' + CONFIG.subject;
|
||||
});
|
||||
</script>
|
||||
<script src="config.js"></script>
|
||||
<script src="game.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
BIN
Binary file not shown.
@@ -42,6 +42,7 @@ html, body, #app {
|
||||
user-select: none;
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
touch-action: manipulation;
|
||||
}
|
||||
|
||||
/* ─── BODY ───────────────────────────────────────────────── */
|
||||
@@ -378,7 +379,7 @@ html, body {
|
||||
.title-main { text-align:center; z-index:1; padding:24px 16px; }
|
||||
.title-logo { margin-bottom:16px; line-height:1; }
|
||||
.title-poke { font-size:clamp(22px,7.5vw,38px); color:var(--yellow); text-shadow:4px 4px 0 #7a5800,0 0 24px rgba(248,192,48,.5); animation:goldShimmer 3s ease-in-out infinite; display:inline-block; }
|
||||
.title-sql { font-size:clamp(22px,7.5vw,38px); color:#60c8ff; text-shadow:4px 4px 0 #003d70,0 0 24px rgba(96,200,255,.5); display:inline-block; animation:blueShimmer 3s ease-in-out infinite; }
|
||||
.title-game { font-size:clamp(22px,7.5vw,38px); color:#60c8ff; text-shadow:4px 4px 0 #003d70,0 0 24px rgba(96,200,255,.5); display:inline-block; animation:blueShimmer 3s ease-in-out infinite; }
|
||||
.title-sub { font-size:clamp(6px,1.8vw,8px); color:rgba(255,255,255,.75); letter-spacing:1px; margin:10px 0 6px; }
|
||||
.title-edition { font-size:clamp(5.5px,1.6vw,7px); color:var(--yellow); margin-bottom:24px; animation:goldShimmer 3s ease-in-out infinite; }
|
||||
.title-deco { font-size:clamp(22px,6vw,32px); margin-bottom:28px; display:flex; gap:16px; justify-content:center; }
|
||||
|
||||
Reference in new issue
Block a user