rpg fleshing out x2
All checks were successful
Build Org Website / build (push) Successful in 46s

This commit is contained in:
2026-05-09 23:03:42 +01:00
parent 083ade12eb
commit be254d661d
4 changed files with 77 additions and 24 deletions

View File

@@ -138,9 +138,10 @@
let keys = new Set();
let last = performance.now();
let saveBusy = false;
let backendAvailable = true;
bind();
load();
loadLocal();
requestAnimationFrame(frame);
function freshState() {
@@ -193,8 +194,13 @@
function bind() {
root.querySelector("[data-rpg-start]").addEventListener("click", start);
root.querySelector("[data-rpg-save]").addEventListener("click", save);
root.querySelector("[data-rpg-load]").addEventListener("click", load);
root.querySelector("[data-rpg-load]").addEventListener("click", () => load(true));
root.querySelector("[data-rpg-reset]").addEventListener("click", newGame);
root.querySelector("[data-rpg-sound]").addEventListener("click", () => {
audio.start();
audio.setTrack(state.started ? areas[state.area].track : "title");
els.status.textContent = audio.supported() ? "Sound on." : "This browser does not expose Web Audio.";
});
root.querySelector("[data-rpg-act]").addEventListener("click", act);
root.querySelectorAll("[data-rpg-move]").forEach((button) => {
button.addEventListener("click", () => move(button.dataset.rpgMove));
@@ -227,18 +233,25 @@
async function newGame() {
state = freshState();
localStorage.removeItem(fallbackKey);
try {
await fetch(`/api/play/rpg/save/${slot}`, { method: "DELETE" });
} catch (_error) {
/* Local reset is enough when offline. */
if (backendAvailable) {
try {
const response = await fetch(`/api/play/rpg/save/${slot}`, { method: "DELETE" });
if (response.status === 404) backendAvailable = false;
} catch (_error) {
backendAvailable = false;
}
}
audio.setTrack("title");
els.status.textContent = backendAvailable ? "New game started and local save cleared." : "New game started locally. Backend save endpoint is not available.";
renderHud();
}
async function save() {
localStorage.setItem(fallbackKey, JSON.stringify(state));
if (saveBusy) return;
if (saveBusy || !backendAvailable) {
els.status.textContent = backendAvailable ? "Save already running." : "Saved locally. Backend save endpoint is not available.";
return;
}
saveBusy = true;
try {
const response = await fetch(`/api/play/rpg/save/${slot}`, {
@@ -246,33 +259,58 @@
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ payload: state })
});
els.status.textContent = response.ok ? "Saved to backend." : "Saved locally. Backend rejected the save.";
if (response.status === 404) {
backendAvailable = false;
els.status.textContent = "Saved locally. Backend save endpoint is not deployed yet.";
} else {
els.status.textContent = response.ok ? "Saved to backend." : "Saved locally. Backend rejected the save.";
}
} catch (_error) {
backendAvailable = false;
els.status.textContent = "Saved locally. Backend save API was not reachable.";
} finally {
saveBusy = false;
}
}
async function load() {
async function load(allowBackend) {
if (loadLocal("Loaded local save.")) return;
if (!allowBackend || !backendAvailable) {
els.status.textContent = backendAvailable ? "No local save found." : "No local save found. Backend save endpoint is not available.";
return;
}
try {
const response = await fetch(`/api/play/rpg/save/${slot}`);
if (response.ok) {
const data = await response.json();
state = normalize(data.payload);
els.status.textContent = "Loaded from backend.";
audio.setTrack(state.started ? areas[state.area].track : "title");
if (response.status === 404) {
backendAvailable = false;
els.status.textContent = "No local save found. Backend save endpoint is not deployed yet.";
return;
}
if (!response.ok) {
els.status.textContent = "No local save found. Backend did not return a save.";
return;
}
const data = await response.json();
state = normalize(data.payload);
localStorage.setItem(fallbackKey, JSON.stringify(state));
els.status.textContent = "Loaded from backend.";
audio.setTrack(state.started ? areas[state.area].track : "title");
} catch (_error) {
/* Fall through to localStorage. */
backendAvailable = false;
els.status.textContent = "No local save found. Backend save API was not reachable.";
}
}
function loadLocal(message) {
const local = localStorage.getItem(fallbackKey);
if (local) {
state = normalize(JSON.parse(local));
els.status.textContent = "Loaded local save.";
if (message) els.status.textContent = message;
audio.setTrack(state.started ? areas[state.area].track : "title");
return true;
}
audio.setTrack(state.started ? areas[state.area].track : "title");
return false;
}
function normalize(candidate) {
@@ -637,11 +675,11 @@
for (let y = 0; y < rows; y += 1) {
for (let x = 0; x < cols; x += 1) {
const wall = area.walls.has(`${x},${y}`);
const c = area.floor[(x + y + Math.floor(now / 800)) % area.floor.length];
const c = area.floor[(x + y) % area.floor.length];
ctx.fillStyle = wall ? "#0b0d10" : c;
ctx.fillRect(x * tile, y * tile, tile, tile);
if (!wall) {
ctx.fillStyle = "rgba(255,255,255,0.025)";
ctx.fillStyle = (x + y) % 2 === 0 ? "rgba(255,255,255,0.018)" : "rgba(0,0,0,0.035)";
ctx.fillRect(x * tile, y * tile + 30, tile, 2);
}
}
@@ -896,12 +934,20 @@
void: [110, 98, 82, 73]
};
function start() {
if (ctx) return;
ctx = new (window.AudioContext || window.webkitAudioContext)();
const Audio = window.AudioContext || window.webkitAudioContext;
if (!Audio) return;
if (ctx) {
if (ctx.state === "suspended") ctx.resume();
return;
}
ctx = new Audio();
master = ctx.createGain();
master.gain.value = 0.055;
master.gain.value = 0.14;
master.connect(ctx.destination);
if (ctx.state === "suspended") ctx.resume();
loop();
const notes = patterns[track] || patterns.title;
tone(notes[0], 0.22, "triangle", 0.9);
}
function loop() {
clearInterval(timer);
@@ -929,12 +975,18 @@
}
return {
start,
supported() {
return Boolean(window.AudioContext || window.webkitAudioContext);
},
setTrack(next) {
track = next;
if (ctx && ctx.state === "suspended") ctx.resume();
if (ctx) loop();
if (ctx) tone((patterns[track] || patterns.title)[0], 0.16, track === "battle" ? "sawtooth" : "triangle", 0.65);
},
sfx(kind) {
if (!ctx) return;
if (ctx.state === "suspended") ctx.resume();
const map = { item: 660, talk: 440, battle: 130, hit: 180, listen: 523, forget: 96, hurt: 80 };
tone(map[kind] || 330, kind === "forget" ? 0.8 : 0.18, kind === "forget" ? "sawtooth" : "square", kind === "hurt" ? 1.3 : 0.8);
}

View File

@@ -27,6 +27,7 @@
<button type="button" data-rpg-save>Save</button>
<button type="button" data-rpg-load>Load</button>
<button type="button" data-rpg-reset>New Game</button>
<button type="button" data-rpg-sound>Sound</button>
</div>
<div class="rpg-mobile-pad" aria-label="Movement controls">

View File

@@ -4,7 +4,7 @@
See the categories: @@html:<a href="../home/categories.html">Categories</a>@@
* Posts:
- [[file:career/career-list.org][Career List]] @@html:<span class="post-date">09-05-2026 22:36</span>@@
- [[file:career/career-list.org][Career List]] @@html:<span class="post-date">09-05-2026 22:50</span>@@
- [[file:career/ha-dr.org][High Availability, Disaster Recovery and Business Continuity]] @@html:<span class="post-date">11-03-2026 17:18</span>@@ @@html:<a href="/tags/learning.html"><span class="post-tag">learning</span></a>@@ @@html:<a href="/tags/notes.html"><span class="post-tag">notes</span></a>@@
- [[file:career/javascript.org][Understands the Javascript language]] @@html:<span class="post-date">11-03-2026 16:52</span>@@ @@html:<a href="/tags/learning.html"><span class="post-tag">learning</span></a>@@ @@html:<a href="/tags/notes.html"><span class="post-tag">notes</span></a>@@
- [[file:career/restful-api.org][Restful API]] @@html:<span class="post-date">15-02-2026 23:00</span>@@ @@html:<a href="/tags/learning.html"><span class="post-tag">learning</span></a>@@ @@html:<a href="/tags/notes.html"><span class="post-tag">notes</span></a>@@

View File

@@ -50,15 +50,15 @@
- tags
- [[file:tags/life.sync-conflict-20260417-233423-VT6366A.org][Tag: life]]
- [[file:tags/review.sync-conflict-20260328-203248-VT6366A.org][Tag: review]]
- [[file:tags/introduction.org][Tag: introduction]]
- [[file:tags/learning.org][Tag: learning]]
- [[file:tags/introduction.org][Tag: introduction]]
- [[file:tags/notes.org][Tag: notes]]
- [[file:tags/website.org][Tag: website]]
- [[file:tags/review.org][Tag: review]]
- [[file:tags/life.org][Tag: life]]
- [[file:tags/emacs.org][Tag: emacs]]
- [[file:tags/education.org][Tag: education]]
- [[file:tags/update.org][Tag: update]]
- [[file:tags/life.org][Tag: life]]
- [[file:tags/reading.org][Tag: reading]]
- [[file:tags/maths.org][Tag: maths]]
- [[file:tags/insights.org][Tag: insights]]