
If anyone can help me out, and point me in the right direction please. I can make the character move with the keyboard, but that doesn't animate. Mouse Clicks do nothing, if you know and can help, let me know.
gaulent.js
https://github.com/PaulMoon410/pekablo
Play the errors here
https://geocities.ws/peakecoin/games/pekablo/
(function() {
// --- ARROW KEY & MOBILE CONTROL ---
function handleMoveKey(key) {
if (game.state !== 'playing') return;
let dx = 0, dy = 0;
const step = s * 0.5;
if (key === 'ArrowUp' || key === 'w') dy = -step;
else if (key === 'ArrowDown' || key === 's') dy = step;
else if (key === 'ArrowLeft' || key === 'a') dx = -step;
else if (key === 'ArrowRight' || key === 'd') dx = step;
else return;
let nx = hero.x + dx;
let ny = hero.y + dy;
if (!isWayWall(nx, ny)) {
hero.x = nx;
hero.y = ny;
hero.to_x = nx;
hero.to_y = ny;
if (typeof hero.setState === 'function' && hero.run) hero.setState(hero.run);
if (typeof hero.currentState === 'object' && typeof hero.step === 'number' && hero.currentState.steps) {
hero.step = (hero.step + 1) % hero.currentState.steps;
hero.sprite = hero.currentState;
}
} else {
if (typeof hero.setState === 'function' && hero.stay) hero.setState(hero.stay);
}
}
function handleAttackKey() {
if (game.state !== 'playing') return;
if (typeof hero.tryAttack === 'function') hero.tryAttack();
}
document.addEventListener('keydown', function(e) {
// Prevent default scrolling on mobile for arrow keys and space
if (["ArrowUp","ArrowDown","ArrowLeft","ArrowRight"," ","w","a","s","d"].includes(e.key)) {
e.preventDefault && e.preventDefault();
}
if (["ArrowUp","ArrowDown","ArrowLeft","ArrowRight","w","a","s","d"].includes(e.key)) {
handleMoveKey(e.key);
} else if (e.key === ' ') {
handleAttackKey();
}
});
// Support for on-screen controls via custom events
window.handleMoveKey = handleMoveKey;
window.handleAttackKey = handleAttackKey;
var imageCount = 0;
function loadImage(url, angles, steps, offsetX) {
imageCount++;
var image = new Image();
image.onload = function() {
imageCount--;
image.offsetX = offsetX ? ((image.height / angles) >> 2) : 0;
};
image.src = url;
if (typeof angles !== "undefined" && typeof steps !== "undefined") {
image.angles = angles;
image.steps = steps;
}
return image;
}
function load(img, callback) {
if (img.complete) callback();
else img.addEventListener("load", callback, false);
}
var level = {
floor: {
prefix: "dttool/output/1/",
map: [
// 30x30 large level, mostly open with some obstacles
...Array(30).fill().map((_, y) => Array(30).fill(756)),
],
header: {
372: false,
660: false,
756: false,
1140: false,
1908: false
}
},
wall: {
prefix: "dttool/output/0/",
map: [
// 30x30, border walls and some internal obstacles
...Array(30).fill().map((_, y) => Array(30).fill(0)),
],
header: {
276: {orientation: 8, main_index: 5, sub_index: 2, direction: 1, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0]},
372: {orientation: 2, main_index: 5, sub_index: 0, direction: 2, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1]},
468: {orientation: 1, main_index: 5, sub_index: 0, direction: 1, walk: [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0]},
564: {orientation: 2, main_index: 5, sub_index: 0, direction: 2, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1]},
660: {orientation: 2, main_index: 5, sub_index: 0, direction: 2, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1]},
756: {orientation: 1, main_index: 5, sub_index: 0, direction: 1, walk: [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0]},
852: {orientation: 1, main_index: 5, sub_index: 0, direction: 1, walk: [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0]},
948: {orientation: 3, main_index: 5, sub_index: 0, direction: 3, walk: [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,1,1,1,1]},
1044: {orientation: 4, main_index: 5, sub_index: 0, direction: 3, walk: [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,1,1,1,1]},
1140: {orientation: 7, main_index: 5, sub_index: 0, direction: 4, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,1,1,0,0,0]},
1236: {orientation: 9, main_index: 5, sub_index: 0, direction: 2, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1]},
1332: {orientation: 9, main_index: 5, sub_index: 1, direction: 2, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0]},
1428: {orientation: 8, main_index: 5, sub_index: 0, direction: 1, walk: [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0]},
1524: {orientation: 8, main_index: 5, sub_index: 1, direction: 1, walk: [0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0]},
1620: {orientation: 9, main_index: 5, sub_index: 0, direction: 2, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1]},
1716: {orientation: 9, main_index: 5, sub_index: 1, direction: 2, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0]},
1812: {orientation: 8, main_index: 5, sub_index: 1, direction: 1, walk: [0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0]},
1908: {orientation: 8, main_index: 5, sub_index: 0, direction: 1, walk: [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0]},
2004: {orientation: 6, main_index: 5, sub_index: 0, direction: 2, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,1,1,1,1,1]},
2100: {orientation: 5, main_index: 5, sub_index: 0, direction: 1, walk: [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,1,0,0,0,1,1,0,0,0]},
2196: {orientation: 6, main_index: 5, sub_index: 0, direction: 2, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,1,1,1,1,1]},
2292: {orientation: 5, main_index: 5, sub_index: 0, direction: 1, walk: [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,1,0,0,0,1,1,0,0,0]},
2388: {orientation: 2, main_index: 5, sub_index: 0, direction: 2, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1]},
2484: {orientation: 1, main_index: 5, sub_index: 0, direction: 1, walk: [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0]},
2580: {orientation: 3, main_index: 5, sub_index: 0, direction: 3, walk: [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,1,1,1,1]},
2676: {orientation: 4, main_index: 5, sub_index: 0, direction: 3, walk: [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,1,1,1,1]},
2772: {orientation: 12, main_index: 5, sub_index: 0, direction: 3, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,1,1,0,0,0]}
}
},
object: {
prefix: "dttool/output/2/",
map: [
// 30x30, sparse objects
...Array(30).fill().map(() => Array(30).fill(0)),
],
header: {
276: {orientation: 2, main_index: 9, sub_index: 12, direction: 2, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,1,1,0,0,0]},
372: {orientation: 2, main_index: 9, sub_index: 11, direction: 2, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1]},
468: {orientation: 12, main_index: 50, sub_index: 0, direction: 3, walk: [0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,1,1,0,0,0,0,0,0,0]},
564: {orientation: 12, main_index: 9, sub_index: 33, direction: 3, walk: [0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1,1,1,1,0,1,1,1,1,0]},
660: {orientation: 1, main_index: 9, sub_index: 33, direction: 1, walk: [1,1,1,1,0,1,1,1,1,0,1,1,1,1,0,1,1,1,1,0,1,1,1,1,0]},
756: {orientation: 7, main_index: 9, sub_index: 33, direction: 4, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,1,1,1,1,0]},
852: {orientation: 12, main_index: 9, sub_index: 32, direction: 3, walk: [0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0]},
948: {orientation: 12, main_index: 9, sub_index: 31, direction: 3, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,1,1,1,0,0]},
1044: {orientation: 1, main_index: 9, sub_index: 10, direction: 1, walk: [0,0,0,0,0,1,1,0,0,0,1,1,0,0,0,1,1,0,0,0,1,1,0,0,0]},
1140: {orientation: 1, main_index: 9, sub_index: 9, direction: 1, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,1,1,0,0,0]},
1236: {orientation: 1, main_index: 9, sub_index: 8, direction: 1, walk: [1,1,0,0,0,1,1,0,0,0,1,1,0,0,0,1,1,0,0,0,1,1,0,0,0]},
1332: {orientation: 12, main_index: 9, sub_index: 11, direction: 3, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0]},
1428: {orientation: 12, main_index: 9, sub_index: 10, direction: 3, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0]},
1524: {orientation: 12, main_index: 9, sub_index: 9, direction: 3, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]},
1620: {orientation: 12, main_index: 9, sub_index: 8, direction: 3, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]},
1716: {orientation: 12, main_index: 9, sub_index: 7, direction: 3, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0]},
1812: {orientation: 12, main_index: 9, sub_index: 6, direction: 3, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0]},
1908: {orientation: 12, main_index: 9, sub_index: 5, direction: 3, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0]},
2004: {orientation: 12, main_index: 9, sub_index: 4, direction: 3, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0]},
2100: {orientation: 12, main_index: 9, sub_index: 3, direction: 3, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0]},
2196: {orientation: 12, main_index: 9, sub_index: 2, direction: 3, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0]},
2292: {orientation: 12, main_index: 9, sub_index: 1, direction: 3, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0]},
2388: {orientation: 12, main_index: 9, sub_index: 0, direction: 3, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0]},
2484: {orientation: 2, main_index: 9, sub_index: 10, direction: 2, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]},
2580: {orientation: 2, main_index: 9, sub_index: 9, direction: 2, walk: [0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1]},
2676: {orientation: 2, main_index: 9, sub_index: 8, direction: 2, walk: [0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]},
2772: {orientation: 2, main_index: 9, sub_index: 7, direction: 2, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0]},
2868: {orientation: 2, main_index: 9, sub_index: 6, direction: 2, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0]},
2964: {orientation: 1, main_index: 9, sub_index: 7, direction: 1, walk: [0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0]},
3060: {orientation: 1, main_index: 9, sub_index: 6, direction: 1, walk: [0,0,0,0,0,1,1,0,0,0,1,1,0,0,0,1,1,0,0,0,1,1,0,0,0]},
3156: {orientation: 2, main_index: 9, sub_index: 5, direction: 2, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0]},
3252: {orientation: 2, main_index: 9, sub_index: 4, direction: 2, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,1,1,1,1,0]},
3348: {orientation: 1, main_index: 9, sub_index: 5, direction: 1, walk: [0,0,0,0,0,1,1,0,0,0,1,1,0,0,0,1,1,0,0,0,1,1,0,0,0]},
3444: {orientation: 1, main_index: 9, sub_index: 4, direction: 1, walk: [0,0,0,0,0,1,1,0,0,0,1,1,0,0,0,1,1,0,0,0,1,1,0,0,0]},
3540: {orientation: 12, main_index: 9, sub_index: 30, direction: 3, walk: [0,0,0,0,0,0,0,1,0,0,0,1,1,0,0,0,1,1,0,0,0,0,0,0,0]},
3636: {orientation: 12, main_index: 9, sub_index: 29, direction: 3, walk: [0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,1,1,1,0,0,0,0,0,0]},
3732: {orientation: 1, main_index: 9, sub_index: 3, direction: 1, walk: [0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,1,1,0,0,0,1,1,0,0,0]},
3828: {orientation: 1, main_index: 9, sub_index: 2, direction: 1, walk: [1,1,0,0,0,1,1,0,0,0,1,1,0,0,0,1,1,0,0,0,1,1,0,0,0]},
3924: {orientation: 2, main_index: 9, sub_index: 3, direction: 2, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,1,1,1,1,1]},
4020: {orientation: 2, main_index: 9, sub_index: 2, direction: 2, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1]},
4116: {orientation: 2, main_index: 9, sub_index: 1, direction: 2, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1]},
4212: {orientation: 2, main_index: 9, sub_index: 0, direction: 2, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1]},
4308: {orientation: 1, main_index: 9, sub_index: 1, direction: 1, walk: [0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,1,1,0,0,0,1,1,0,0,0]},
4404: {orientation: 1, main_index: 9, sub_index: 0, direction: 1, walk: [1,1,0,0,0,1,1,0,0,0,1,1,0,0,0,1,1,0,0,0,1,1,0,0,0]},
4500: {orientation: 12, main_index: 9, sub_index: 28, direction: 3, walk: [0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0]},
4596: {orientation: 12, main_index: 9, sub_index: 27, direction: 3, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0]},
4692: {orientation: 12, main_index: 9, sub_index: 24, direction: 3, walk: [0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0]},
4788: {orientation: 12, main_index: 9, sub_index: 23, direction: 3, walk: [0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0]},
4884: {orientation: 12, main_index: 9, sub_index: 22, direction: 3, walk: [0,0,0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0]},
4980: {orientation: 12, main_index: 9, sub_index: 21, direction: 3, walk: [0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0]},
5076: {orientation: 12, main_index: 9, sub_index: 20, direction: 3, walk: [0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0]},
5172: {orientation: 12, main_index: 9, sub_index: 17, direction: 3, walk: [0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1,1,0,0,0,1,1,0,0,0]},
5268: {orientation: 12, main_index: 9, sub_index: 18, direction: 3, walk: [0,0,0,0,0,1,1,0,0,0,1,1,0,0,0,1,1,0,0,0,1,1,0,0,0]},
5364: {orientation: 12, main_index: 9, sub_index: 19, direction: 3, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,1,1,1,0,0]},
5460: {orientation: 12, main_index: 9, sub_index: 16, direction: 3, walk: [0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,1,1,0,0,0,1,1,0,0,0]},
5556: {orientation: 12, main_index: 9, sub_index: 15, direction: 3, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0]},
5652: {orientation: 12, main_index: 9, sub_index: 13, direction: 3, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,1,1,0]},
5748: {orientation: 12, main_index: 9, sub_index: 12, direction: 3, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0]},
5844: {orientation: 12, main_index: 9, sub_index: 14, direction: 3, walk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,1,1,1,1,0]}
}
}
};
for (var layer in level) {
level[layer].tiles = {};
for (var tileId in level[layer].header) {
if (!level[layer].tiles[tileId]) {
level[layer].tiles[tileId] = loadImage(level[layer].prefix + tileId + ".png");
}
}
}
var floor = document.getElementById("floor").getContext("2d");
floor.w = floor.canvas.width;
floor.h = floor.canvas.height;
var tw = 160;
var th = tw / 2;
var s = tw * 0.705;
var a = Math.PI / 4;
var visible = 12; // show more tiles on large map
var asin = Math.sin(a);
var acos = Math.sin(a);
var barrelSprite = loadImage("sprite/barrel64.png");
var coinSprite = loadImage("sprite/coins10.png");
var potionSprite = loadImage("sprite/potions.png");
var gateSprite = loadImage("sprite/house.png");
var monsterMap = {
SK: {
A1: loadImage("monsters/SK/A1/map.png", 8, 16, true),
NU: loadImage("monsters/SK/NU/map.png", 8, 8, true),
WL: loadImage("monsters/SK/WL/map.png", 8, 8, true),
DD: loadImage("monsters/SK/DD/map.png", 8, 1),
attackOffset: 10
},
FS: {
A1: loadImage("monsters/FS/A1/map.png", 8, 17, true),
NU: loadImage("monsters/FS/NU/map.png", 8, 12, true),
WL: loadImage("monsters/FS/WL/map.png", 8, 14, true),
DD: loadImage("monsters/FS/DD/map.png", 8, 1)
},
SI: {
A1: loadImage("monsters/SI/A1/map.png", 8, 16, true),
NU: loadImage("monsters/SI/NU/map.png", 8, 8, true),
WL: loadImage("monsters/SI/WL/map.png", 8, 9, true),
DD: loadImage("monsters/SI/DD/map.png", 8, 1)
},
BA: {
A1: loadImage("monsters/BA/A1/map.png", 16, 9, true),
NU: loadImage("monsters/BA/NU/map.png", 16, 8, true),
WL: loadImage("monsters/BA/WL/map.png", 16, 8, true)
}
};
var STAGES = [
{
name: "The Gatehouse",
intro: "Clear the scavengers haunting the gatehouse.",
monsters: [
{type: "SK", count: 3, health: 700, damage: 22},
{type: "FS", count: 2, health: 900, damage: 28}
],
potionCount: 2,
barrelCount: 3,
healOnStart: 0,
gateLabel: "Descend"
},
{
name: "The Bone Hall",
intro: "A deeper chamber wakes. Survive the second trial.",
monsters: [
{type: "SK", count: 3, health: 900, damage: 28},
{type: "FS", count: 3, health: 1200, damage: 32},
{type: "SI", count: 2, health: 1000, damage: 36}
],
potionCount: 2,
barrelCount: 4,
healOnStart: 250,
gateLabel: "Challenge the Warden"
},
{
name: "Warden's Ring",
intro: "Defeat the crypt warden and claim the relic.",
monsters: [
{type: "SI", count: 2, health: 1200, damage: 38},
{type: "FS", count: 2, health: 1300, damage: 40},
{type: "SK", count: 1, health: 1600, damage: 45, title: "Bone Captain"},
{type: "SI", count: 1, health: 3200, damage: 65, resistance: 180, speed: 10, title: "Crypt Warden", lootCoins: 900}
],
potionCount: 3,
barrelCount: 4,
healOnStart: 300,
gateLabel: "Claim Victory"
}
];
var monsters = [];
var deathmobs = [];
var barrels = [];
var coins = [];
var potions = [];
var walls = [];
var interactives = [];
var stageKills = 0;
var game = {
state: "loading",
stageIndex: -1,
message: "Loading assets...",
messageUntil: 0,
showMap: false,
questText: "",
gate: null
};
function now() {
return new Date().getTime();
}
// DEBUG: Draw walkability overlay
function drawWalkabilityOverlay(ctx) {
if (!window.hero) return;
ctx.save();
for (let y = 0; y < level.floor.map.length; y++) {
for (let x = 0; x < level.floor.map[0].length; x++) {
let wx = x * s;
let wy = y * s;
if (isWayWall(wx + s/2, wy + s/2)) {
ctx.fillStyle = 'rgba(255,0,0,0.2)';
ctx.fillRect(wx, wy, s, s);
}
}
}
// Draw hero position
ctx.fillStyle = 'lime';
ctx.beginPath();
ctx.arc(hero.x, hero.y, 6, 0, 2 * Math.PI);
ctx.fill();
ctx.restore();
}
function setMessage(text, duration) {
game.message = text;
game.messageUntil = now() + (duration || 2200);
}
function clearMessage() {
if (game.messageUntil && now() > game.messageUntil) {
game.message = "";
game.messageUntil = 0;
}
}
function remove(arr, value) {
var index = arr.indexOf(value);
if (index >= 0) arr.splice(index, 1);
}
function distance(ax, ay, bx, by) {
var dx = ax - bx;
var dy = ay - by;
return Math.sqrt(dx * dx + dy * dy);
}
function isWayWall(x, y) {
var block_x = Math.floor(x / s);
var block_y = Math.floor(y / s);
if (block_x < 0 || block_y < 0 || block_x >= level.wall.map[0].length || block_y >= level.wall.map.length) return true;
// Border walls
if (block_x === 0 || block_y === 0 || block_x === level.wall.map[0].length - 1 || block_y === level.wall.map.length - 1) return true;
// Internal walls
if (level.wall.map[block_y][block_x] !== 0) return true;
// Objects block movement
if (level.object.map[block_y][block_x] !== 0) return true;
return false;
}
function getFloorTile(x, y) {
if (!level.floor.map[y]) return null;
if (!level.floor.map[y][x]) return null;
var f = level.floor.map[y][x];
return level.floor.tiles[f];
}
function randomFreePoint(minDistance) {
for (var attempt = 0; attempt < 500; attempt++) {
var point = {
x: s * (1 + Math.random() * (level.floor.map[0].length - 2)),
y: s * (1 + Math.random() * (level.floor.map.length - 2))
};
if (!isWayWall(point.x, point.y)) {
if (!minDistance || distance(point.x, point.y, hero.x, hero.y) >= minDistance) return point;
}
}
return {x: s * 3, y: s * 3};
}
function Shape(sprite, x, y) {
this.x = x;
this.y = y;
this.offset_x = 0;
this.offset_y = 0;
this.sprite = sprite;
this.isAboveHero = function() {
var maxlen = tw * visible / 2;
return Math.abs(this.x - hero.x) <= maxlen && Math.abs(this.y - hero.y) <= maxlen;
};
}
function BaseWall(sprite, header, x, y) {
Shape.call(this, sprite, x, y);
this.header = header;
this.isAboveHero = function() { return true; };
this.offset_x -= 14;
this.offset_y += 82;
}
function Wall(index, x, y) {
BaseWall.call(this, level.wall.tiles[index], level.wall.header[index], x, y);
switch (this.header.orientation) {
case 2:
case 6:
this.offset_x += 16;
break;
case 5:
this.offset_x -= 16;
break;
case 3:
for (var inx in level.wall.header) {
var h = level.wall.header[inx];
if (h.orientation === 4 && h.main_index === this.header.main_index && h.sub_index === this.header.sub_index) {
walls.push(new Wall(inx, x, y));
break;
}
}
this.offset_x += 16;
break;
case 4:
this.offset_x -= 16;
break;
}
}
function WallObject(index, x, y) {
BaseWall.call(this, level.object.tiles[index], level.object.header[index], x, y);
this.offset_x += 16;
var self = this;
load(this.sprite, function() {
if (self.sprite.width < 160) self.offset_x -= (160 - self.sprite.width) / 2;
});
}
function DeathMob(mob) {
Shape.call(this, mob.death, mob.x, mob.y);
this.step = 0;
this.angle = mob.angle;
this.used = false;
this.use = function() {
if (this.used) return;
this.used = true;
var bonusCoins = mob.lootCoins || Math.floor(120 + Math.random() * 180);
coins.push(new Coin(this.x + 28, this.y + 20, bonusCoins));
if (Math.random() > 0.45) potions.push(new PotionHealth(this.x + 54, this.y));
if (Math.random() > 0.75) coins.push(new Coin(this.x - 22, this.y + 8, Math.floor(bonusCoins / 2)));
setMessage("Loot gathered from " + (mob.title || "the fallen") + ".", 1800);
};
}
function Barrel(x, y) {
Shape.call(this, barrelSprite, x, y);
this.use = function(mob) {
if (mob.doAttack) mob.doAttack(this);
};
this.damage = function() {
remove(barrels, this);
if (Math.random() > 0.35) coins.push(new Coin(this.x, this.y, Math.floor(45 + Math.random() * 60)));
if (Math.random() > 0.8) potions.push(new PotionHealth(this.x + 12, this.y));
};
}
function Coin(x, y, amount) {
Shape.call(this, coinSprite, x, y);
this.coins = amount || Math.floor(Math.random() * 100 + 50);
this.use = function(mob) {
remove(coins, this);
mob.coins += this.coins;
setMessage("Collected " + this.coins + " gold.", 1200);
};
}
function Potion(x, y) {
Shape.call(this, potionSprite, x, y);
this.sprite.steps = 6;
this.sprite.angles = 4;
this.use = function(mob) {
if (mob.addToBelt(this)) {
remove(potions, this);
setMessage("Potion stored on your belt.", 1200);
}
};
}
function PotionHealth(x, y) {
Potion.call(this, x, y);
this.step = 0;
this.angle = 0;
this.health = 850;
this.drink = function(mob) {
mob.health = Math.min(mob.origin_health, mob.health + this.health);
setMessage("You recover " + this.health + " vitality.", 1200);
};
}
function AncientGate(x, y) {
Shape.call(this, gateSprite, x, y);
this.offset_y += 24;
this.active = false;
this.label = "Ward Locked";
this.use = function() {
if (game.state !== "playing") return;
if (!this.active) {
setMessage("The gate remains sealed. Finish the trial first.", 1800);
return;
}
if (game.stageIndex === STAGES.length - 1) {
game.state = "won";
setMessage("The relic is yours. The gauntlet is broken.", 4000);
return;
}
advanceStage();
};
}
function Mob(x, y, name) {
this.to_x = x;
this.to_y = y;
this.name = name;
this.title = name;
this.stay = monsterMap[name].NU;
this.run = monsterMap[name].WL;
this.death = monsterMap[name].DD;
this.currentState = this.stay;
this.step = 0;
this.angle = 0;
this.st = 8;
this.lootCoins = 150;
Shape.call(this, this.currentState, x, y);
this.rotate = function(sx, sy) {
var l = this.currentState.angles;
this.angle = Math.round((Math.atan2(sy, sx) / Math.PI + 2.75) * l / 2 + l / 2) % l;
};
this.rotateTo = function(point) {
this.rotate(point.x - this.x, point.y - this.y);
};
this.setState = function(state) {
if (this.currentState !== state) {
this.currentState = state;
this.step = -1;
}
};
this.nextStep = function() {
var dx = this.to_x - this.x;
var dy = this.to_y - this.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 1) {
var maxStep = this.st;
var tx = 0, ty = 0;
for (var st = 0; st <= maxStep; st += 1) {
var sx = st * dx / dist;
var sy = st * dy / dist;
if (isWayWall(this.x + sx, this.y + sy)) {
tx = sx;
ty = sy;
} else break;
}
this.rotate(tx, ty);
if (Math.abs(tx) > 0.5 || Math.abs(ty) > 0.5) {
this.x += tx;
this.y += ty;
this.setState(this.run);
} else {
this.setState(this.stay);
this.x += tx;
this.y += ty;
this.to_x = this.x;
this.to_y = this.y;
}
} else {
this.setState(this.stay);
this.to_x = this.x;
this.to_y = this.y;
}
this.step = (this.step + 1) % this.currentState.steps;
this.sprite = this.currentState;
};
this.origin_health = this.health = 1000;
this.resistance = 10;
this.use = function(mob) {
if (mob.doAttack) mob.doAttack(this);
};
this.damage = function(damage) {
var nextHealth = this.health - damage * 1000 / (1000 - this.resistance);
if (nextHealth <= 0) {
this.health = 0;
remove(monsters, this);
if (this.death) deathmobs.push(new DeathMob(this));
stageKills++;
if (monsters.length === 0 && game.gate) {
game.gate.active = true;
game.gate.label = STAGES[game.stageIndex].gateLabel;
hero.health = Math.min(hero.origin_health, hero.health + 180);
setMessage("The seal gives way. Approach the gate.", 2600);
}
} else {
this.health = nextHealth;
}
};
}
function AgressiveMob(x, y, name) {
Mob.call(this, x, y, name);
this.attack = monsterMap[name].A1;
this.attackOffset = monsterMap[name].attackOffset || 0;
this.normalOffset = 0;
this._nextStep = this.nextStep;
this.nextStep = function() {
if (this.currentState === this.attack) {
if (this.step === this.attack.steps - 1) {
this.currentState = this.stay;
this.step = -1;
if (this.attacked) {
this.attacked.damage(this.getDamage());
this.attacked = null;
}
}
this.step = (this.step + 1) % this.currentState.steps;
this.sprite = this.currentState;
} else this._nextStep();
this.offset_y = this.currentState === this.attack ? this.attackOffset : this.normalOffset;
};
this.currentDamage = 30;
this.getDamage = function() {
return this.currentDamage;
};
this.attacked = null;
this.doAttack = function(mob) {
if (this.attacked !== mob) {
this.rotateTo(mob);
this.setState(this.attack);
this.attacked = mob;
}
};
}
function HeroBarbarian(x, y) {
AgressiveMob.call(this, x, y, "BA");
this.attackOffset = 40;
this.normalOffset = 10;
this.health = this.origin_health = 1500;
this.belt = {items: [], size: 10};
this.st = 16;
this.coins = 0;
this.resistance = 140;
this.title = "The Wardenbreaker";
this.addToBelt = function(potion) {
for (var i = 0; i < this.belt.size; i++) {
if (typeof this.belt.items[i] === "undefined") {
this.belt.items[i] = potion;
return true;
}
}
setMessage("Your belt is full.", 1200);
return false;
};
this.criticalDamage = 0.35;
this.currentDamage = 130;
this.getDamage = function() {
return this.currentDamage * (Math.random() <= this.criticalDamage ? 3.5 : 1);
};
// Override nextStep to always move toward to_x, to_y
// Save AgressiveMob's nextStep as _superNextStep
this._superNextStep = this.nextStep;
this.nextStep = function() {
var dx = this.to_x - this.x;
var dy = this.to_y - this.y;
if (Math.abs(dx) > 1 || Math.abs(dy) > 1) {
this._superNextStep();
} else {
this.setState(this.stay);
this.to_x = this.x;
this.to_y = this.y;
}
this.step = (this.step + 1) % this.currentState.steps;
this.sprite = this.currentState;
};
this.damage = function(damage) {
var nextHealth = this.health - damage * 1000 / (1000 - this.resistance);
this.health = Math.max(0, nextHealth);
if (this.health <= 0 && game.state === "playing") {
this.to_x = this.x;
this.to_y = this.y;
game.state = "lost";
setMessage("You fall beneath the gatehouse stones. Press R to try again.", 5000);
}
};
this.resetAdventure = function() {
this.x = s * 3;
this.y = s * 3;
this.to_x = this.x;
this.to_y = this.y;
this.health = this.origin_health;
this.coins = 0;
this.belt.items = [];
this.currentDamage = 130;
};
}
var hero = new HeroBarbarian(s * 3, s * 3);
function buildWalls() {
walls.length = 0;
// Border walls
for (var y = 0; y < level.wall.map.length; y++) {
for (var x = 0; x < level.wall.map[y].length; x++) {
if (x === 0 || y === 0 || x === level.wall.map[y].length - 1 || y === level.wall.map.length - 1) {
level.wall.map[y][x] = 948; // Use a wall tile
} else if ((x % 7 === 0 && y % 7 > 1 && y % 7 < 6) || (y % 9 === 0 && x % 9 > 1 && x % 9 < 7)) {
level.wall.map[y][x] = 372; // Internal obstacles
} else {
level.wall.map[y][x] = 0;
}
if (level.wall.map[y][x] > 0) walls.push(new Wall(level.wall.map[y][x], x * s, y * s));
}
}
// Place a few objects
for (var i = 0; i < 20; i++) {
var ox = Math.floor(2 + Math.random() * (level.object.map[0].length - 4));
var oy = Math.floor(2 + Math.random() * (level.object.map.length - 4));
level.object.map[oy][ox] = 5844;
walls.push(new WallObject(5844, ox * s, oy * s));
}
}
function spawnMonster(config) {
var point = randomFreePoint(s * 1.8);
var mob = new AgressiveMob(point.x, point.y, config.type);
mob.title = config.title || config.type;
mob.origin_health = mob.health = config.health || mob.health;
mob.currentDamage = config.damage || mob.currentDamage;
mob.resistance = typeof config.resistance === "number" ? config.resistance : mob.resistance;
mob.st = config.speed || mob.st;
mob.lootCoins = config.lootCoins || Math.floor(130 + Math.random() * 170);
monsters.push(mob);
return mob;
}
function spawnBarrels(count) {
for (var i = 0; i < count; i++) {
var point = randomFreePoint(s * 1.2);
barrels.push(new Barrel(point.x, point.y));
}
}
function spawnPotions(count) {
for (var i = 0; i < count; i++) {
var point = randomFreePoint(s * 1.1);
potions.push(new PotionHealth(point.x, point.y));
}
}
function setupStage(index) {
var stage = STAGES[index];
monsters.length = 0;
deathmobs.length = 0;
barrels.length = 0;
coins.length = 0;
potions.length = 0;
interactives.length = 0;
stageKills = 0;
hero.to_x = hero.x;
hero.to_y = hero.y;
hero.health = Math.min(hero.origin_health, hero.health + stage.healOnStart);
hero.x = s * 3;
hero.y = s * 3;
hero.to_x = hero.x;
hero.to_y = hero.y;
for (var groupIndex = 0; groupIndex < stage.monsters.length; groupIndex++) {
var group = stage.monsters[groupIndex];
for (var count = 0; count < group.count; count++) spawnMonster(group);
}
spawnBarrels(stage.barrelCount);
spawnPotions(stage.potionCount);
game.gate = new AncientGate(s * 8.3, s * 2.3);
game.gate.label = "Ward Locked";
interactives.push(game.gate);
game.questText = stage.intro;
setMessage(stage.name + ": " + stage.intro, 3000);
}
function startGame() {
hero.resetAdventure();
game.state = "playing";
game.stageIndex = 0;
setupStage(0);
}
function advanceStage() {
game.stageIndex++;
if (game.stageIndex >= STAGES.length) {
game.state = "won";
setMessage("The relic is yours. The gauntlet is broken.", 4000);
return;
}
setupStage(game.stageIndex);
}
function loadZb(order, click) {
var tmp_zb = [];
var zb = [];
var all = [monsters, potions, barrels, interactives, click ? [] : [hero], click ? [] : walls];
for (var t in all) {
for (var m in all[t]) {
if (all[t][m].isAboveHero()) tmp_zb.push(all[t][m]);
}
}
tmp_zb.sort(function(objA, objB) {
var compare = (objB.x + objB.offset_x) + (objB.y + objB.offset_y) - (objA.x + objA.offset_x) - (objA.y + objA.offset_y);
return order ? compare : -compare;
});
var ordered = [coins, deathmobs, tmp_zb];
for (var i in ordered) for (var j in ordered[i]) zb.push(ordered[i][j]);
return zb;
}
function processClick() {
var zb = loadZb(true, true);
var cx = (floor.click_x - floor.click_y) * acos;
var cy = (floor.click_x + floor.click_y) / 2 * asin;
for (var i in zb) {
var entity = zb[i];
var sprite = entity.sprite;
var sx = (entity.x - entity.y) * acos + entity.offset_x;
var sy = (entity.x + entity.y) / 2 * asin + entity.offset_y;
var spriteWidth = sprite.angles ? sprite.width / sprite.angles : sprite.width;
var spriteHeight = sprite.steps ? sprite.height / sprite.steps : sprite.height;
if (cx >= sx - spriteWidth / 2 && cx <= sx + spriteWidth / 2 && cy >= sy - spriteHeight && cy <= sy) {
entity.use(hero);
return true;
}
}
return false;
}
floor.canvas.onclick = function(e) {
console.log('Canvas clicked');
if (game.state === "won" || game.state === "lost") {
startGame();
return;
}
if (game.state !== "playing") return;
// Get canvas bounding rect for correct mouse position
var rect = floor.canvas.getBoundingClientRect();
var mx = (e.clientX - rect.left) - floor.w / 2 + th;
var my = (e.clientY - rect.top) - floor.h / 2;
// Inverse isometric transform to get world coordinates
var mrx = hero.x * acos - hero.y * asin;
var mry = (hero.x * asin + hero.y * acos) / 2;
var wx = (mx + mrx) / acos;
var wy = (2 * my + mry * 2 - mx - mrx) / (2 * asin);
floor.click_x = wx;
floor.click_y = wy;
console.log('[CLICK]', {
hero_x: hero.x,
hero_y: hero.y,
click_x: wx,
click_y: wy,
to_x: hero.to_x,
to_y: hero.to_y
});
processClick(); // still allow interaction, but always set move target
hero.to_x = floor.click_x;
hero.to_y = floor.click_y;
if (typeof hero.setState === 'function' && hero.run) hero.setState(hero.run);
console.log('[MOVE TARGET]', {to_x: hero.to_x, to_y: hero.to_y});
};
window.onkeydown = function(e) {
var beltKeys = [49,50,51,52,53,54,55,56,57,48];
var beltIndex = beltKeys.indexOf(e.keyCode);
if (beltIndex >= 0) {
if (hero.belt.items[beltIndex] instanceof PotionHealth) {
hero.belt.items[beltIndex].drink(hero);
delete hero.belt.items[beltIndex];
}
return false;
}
if (e.keyCode === 9 || e.keyCode === 77) {
game.showMap = !game.showMap;
return false;
}
if (e.keyCode === 82) {
startGame();
return false;
}
};
function renderObjects() {
var zb = loadZb(false);
for (var z in zb) {
var entity = zb[z];
floor.save();
var sx = (entity.x - entity.y) * acos + entity.offset_x;
var sy = (entity.x + entity.y) / 2 * asin + entity.offset_y;
var tile = entity.sprite;
var renderW = tile.width;
var renderH = tile.height;
if (tile.steps && tile.angles) {
renderW /= tile.steps;
renderH /= tile.angles;
floor.drawImage(tile, renderW * entity.step, renderH * entity.angle, renderW, renderH, Math.round(sx - renderW / 2 - tile.offsetX), Math.round(sy - renderH), renderW, renderH);
} else {
if (entity instanceof AncientGate && !entity.active) floor.globalAlpha = 0.45;
floor.drawImage(tile, Math.round(sx - tile.width / 2) + 1, Math.round(sy - tile.height) + 1);
}
floor.restore();
if (entity.health && entity.origin_health && entity !== hero) {
floor.save();
floor.globalAlpha = 0.75;
sy -= 92;
var maxLine = Math.floor(entity.origin_health / 20);
var currentLine = Math.max(0, Math.floor(entity.health / 20));
floor.fillStyle = "black";
floor.fillRect(sx - maxLine / 2 - 1, sy, maxLine + 2, 6);
floor.fillStyle = entity.title === "Crypt Warden" ? "#d9c26e" : "#b22222";
floor.fillRect(sx - maxLine / 2, sy + 1, currentLine, 4);
floor.restore();
}
if (entity instanceof AncientGate || entity.title === "Crypt Warden" || entity.title === "Bone Captain") {
floor.save();
floor.textAlign = "center";
floor.fillStyle = "#d8c392";
floor.font = "14px Verdana";
floor.fillText(entity.label || entity.title, sx, sy - 120);
floor.restore();
}
}
}
function renderFloor() {
floor.save();
floor.translate(floor.w / 2 - th, floor.h / 2);
var fdx = Math.floor(hero.x / s);
var fdy = Math.floor(hero.y / s);
var miny = Math.max(0, fdy - visible);
var maxy = Math.min(level.floor.map.length - 1, fdy + visible);
var minx = Math.max(0, fdx - visible);
var maxx = Math.min(level.floor.map[0].length - 1, fdx + visible);
var mrx = hero.x * acos - hero.y * asin;
var mry = hero.x * asin + hero.y * acos;
mry = mry / 2;
floor.translate(-mrx, -mry);
for (var y = miny; y <= maxy; y++) {
for (var x = minx; x <= maxx; x++) {
var tile = getFloorTile(x, y);
if (tile) {
var tx = (x - y) * th;
var ty = (x + y) * th / 2;
floor.drawImage(tile, tx, ty, tile.width + 0.707, tile.height + 0.707);
}
}
}
floor.translate(th, 0);
// DEBUG: draw walkability overlay
drawWalkabilityOverlay(floor);
renderObjects();
floor.restore();
}
function renderMap() {
floor.save();
floor.translate(floor.w - 180, 160);
var sc = 0.25;
floor.scale(1 * sc, 0.5 * sc);
floor.rotate(Math.PI * 0.25);
floor.translate(-hero.x, -hero.y);
floor.fillStyle = "rgba(0,0,0,0.55)";
var wallOffset = [];
for (var y = 4; y >= 0; y--) for (var x = 0; x <= 4; x++) wallOffset.push({x: x * s / 5, y: y * s / 5});
for (var i in walls) {
var wall = walls[i];
var walk = wall.header.walk;
if (wall.header.orientation === 4) continue;
for (var j = 0; j < 25; j++) if (walk[j] === 1) floor.fillRect(wall.x + wallOffset[j].x, wall.y + wallOffset[j].y, s / 5, s / 5);
}
floor.fillStyle = "rgba(216,195,146,0.95)";
floor.fillRect(hero.x, hero.y, s / 5, s / 5);
if (game.gate) {
floor.fillStyle = game.gate.active ? "rgba(70,200,110,0.85)" : "rgba(210,90,70,0.75)";
floor.fillRect(game.gate.x, game.gate.y, s / 5, s / 5);
}
floor.restore();
}
function renderHeroHealth() {
var radius = 68;
var padding = 22;
floor.save();
floor.globalAlpha = 0.55;
floor.fillStyle = "black";
floor.beginPath();
floor.arc(radius + padding, floor.h - radius - padding, radius + 5, 0, Math.PI * 2);
floor.closePath();
floor.fill();
floor.fillStyle = "#851414";
var percent = hero.health / hero.origin_health;
var angleFrom = Math.PI * (0.5 - percent);
var angleTo = Math.PI * (0.5 + percent);
floor.beginPath();
floor.arc(radius + padding, floor.h - radius - padding, radius, angleFrom, angleTo);
floor.closePath();
floor.fill();
floor.restore();
floor.save();
floor.fillStyle = "#d8c392";
floor.font = "bold 14px Verdana";
floor.fillText("HP", 18, floor.h - 26);
floor.fillText(Math.floor(hero.health) + " / " + hero.origin_health, 62, floor.h - 26);
floor.restore();
}
function renderHeroBelt() {
floor.save();
var tile = potionSprite;
var slotW = tile.width / tile.steps;
var slotH = tile.height / tile.angles;
for (var i = 0; i < hero.belt.size; i++) {
floor.globalAlpha = 0.8;
floor.drawImage(tile, slotW * 2, slotH * 3, slotW, slotH, 188 + slotW * i, 606, slotW, slotH);
floor.globalAlpha = 1;
var potion = hero.belt.items[i];
if (potion) floor.drawImage(tile, slotW * potion.step, slotH * potion.angle, slotW, slotH, 188 + slotW * i, 606, slotW, slotH);
floor.fillStyle = "#d8c392";
floor.font = "11px Verdana";
floor.fillText((i + 1) % 10, 200 + slotW * i, 655);
}
floor.restore();
}
function renderHud() {
floor.save();
floor.fillStyle = "rgba(0,0,0,0.55)";
floor.fillRect(18, 16, 310, 128);
floor.strokeStyle = "rgba(216,195,146,0.5)";
floor.strokeRect(18, 16, 310, 128);
floor.fillStyle = "#d8c392";
floor.font = "bold 22px Verdana";
floor.fillText("PeakeCoin Gauntlet", 34, 44);
floor.font = "14px Verdana";
var stage = STAGES[game.stageIndex] || {name: "Preparing..."};
floor.fillText("Stage: " + stage.name, 34, 70);
floor.fillText("Enemies left: " + monsters.length, 34, 92);
floor.fillText("Gold: " + hero.coins, 34, 114);
floor.fillText("Objective: " + (game.gate && game.gate.active ? game.gate.label : "Defeat every foe"), 34, 136);
floor.restore();
}
function renderMessage() {
if (!game.message) return;
floor.save();
floor.fillStyle = "rgba(0,0,0,0.65)";
floor.fillRect(210, 18, 560, 38);
floor.strokeStyle = "rgba(216,195,146,0.5)";
floor.strokeRect(210, 18, 560, 38);
floor.fillStyle = "#f0e0b6";
floor.font = "15px Verdana";
floor.textAlign = "center";
floor.fillText(game.message, 490, 43);
floor.restore();
}
function renderHelp() {
floor.save();
floor.fillStyle = "rgba(0,0,0,0.45)";
floor.fillRect(680, 530, 282, 108);
floor.strokeStyle = "rgba(216,195,146,0.4)";
floor.strokeRect(680, 530, 282, 108);
floor.fillStyle = "#d8c392";
floor.font = "13px Verdana";
floor.fillText("Click to move. Click nearby targets to attack or loot.", 694, 555);
floor.fillText("1-0 drink belt potions. M or Tab toggles the map.", 694, 578);
floor.fillText("R restarts the run at any time.", 694, 601);
floor.fillText("Clear each trial, then touch the gate to continue.", 694, 624);
floor.restore();
}
function renderOverlay() {
if (game.state === "playing") return;
floor.save();
floor.fillStyle = "rgba(0,0,0,0.55)";
floor.fillRect(0, 0, floor.w, floor.h);
floor.fillStyle = "#d8c392";
floor.textAlign = "center";
floor.font = "bold 42px Verdana";
var title = game.state === "won" ? "Victory" : (game.state === "lost" ? "Defeat" : "Loading");
floor.fillText(title, floor.w / 2, 220);
floor.font = "18px Verdana";
if (game.state === "won") {
floor.fillText("You emerged with " + hero.coins + " gold and shattered the gauntlet.", floor.w / 2, 270);
floor.fillText("Click or press R to begin another run.", floor.w / 2, 305);
} else if (game.state === "lost") {
floor.fillText("The wardens hold the chamber. Click or press R to try again.", floor.w / 2, 270);
} else {
floor.fillText("Gathering sprites and stonework...", floor.w / 2, 270);
}
floor.restore();
}
function updateMonsters() {
if (game.state !== "playing") return;
for (var i = 0; i < monsters.length; i++) monsters[i].nextStep();
}
setInterval(function() {
if (game.state !== "playing") return;
if (monsters.length === 0) return;
var randomMob = monsters[Math.ceil(Math.random() * (monsters.length - 1))];
if (randomMob && typeof randomMob.attacked !== "object") {
randomMob.to_x = randomMob.x + (Math.random() * s - s / 2);
randomMob.to_y = randomMob.y + (Math.random() * s - s / 2);
}
for (var i = 0; i < monsters.length; i++) {
var mob = monsters[i];
var attackDist = mob.title === "Crypt Warden" ? 128 : 104;
if (mob.attack && mob.isAboveHero()) {
if (Math.abs(hero.x - mob.x) < attackDist && Math.abs(hero.y - mob.y) < attackDist) {
mob.doAttack(hero);
mob.to_x = mob.x;
mob.to_y = mob.y;
} else {
mob.to_x = hero.x;
mob.to_y = hero.y;
}
}
}
}, 220);
setInterval(function() {
if (game.state === "playing") hero.health = Math.min(hero.origin_health, hero.health + 18);
}, 2500);
setInterval(function() {
clearMessage();
if (imageCount > 0) {
floor.fillStyle = "black";
floor.fillRect(0, 0, floor.w, floor.h);
renderOverlay();
return;
}
if (game.state === "loading") startGame();
if (game.state === "playing") hero.nextStep();
updateMonsters();
floor.fillStyle = "black";
floor.fillRect(0, 0, floor.w, floor.h);
renderFloor();
renderHeroHealth();
renderHeroBelt();
renderHud();
renderHelp();
renderMessage();
if (game.showMap) renderMap();
renderOverlay();
}, 66);
buildWalls();
})();
🪙 PeakeCoin Ecosystem
⚙️ HiveP.I.M.P. — Market Stabilization Layer
::contentReference[oaicite:0]{index=0}
Operated by @hivepimp, P.I.M.P. stabilizes PEK markets and supports liquidity on Hive Engine.
Community participation strengthens long-term market health.
👉 https://peakecoin.info
🤖 PeakeBot — Autonomous Trading System (RC-AWARE)
::contentReference[oaicite:1]{index=1}
Independent multi-token trading bot featuring:
- RC-aware execution
- Adaptive delay logic
- Self-regulating trade cycles
📊 Trading bot details:
👉 https://geocities.ws/p/e/peakecoin/trading-bot/peakebot_v0_01.html
💻 Open-source repositories:
👉 https://github.com/paulmoon410
🎰 PeakeSino — The PeakeCoin Casino (Beta)
::contentReference[oaicite:2]{index=2}
Blockchain-powered games using PEK as the native in-game currency.
Built on Hive with provable fairness and community-driven growth.
🃏 Play the beta games here:
👉 https://geocities.ws/peakecoin/pek_casino/beta_games/index.html
🙏 Acknowledgements
Thanks to and please follow:
@enginewitty @ecoinstant @neoxian @txracer @thecrazygm @holdonia @aggroed
For their continued support, guidance, and help expanding the PeakeCoin ecosystem.



