Game Sourcecode has been added and Style was changed
This commit is contained in:
parent
d907e90218
commit
8d31cb5bc7
17 changed files with 4689 additions and 136 deletions
3
controllers/game.php
Normal file
3
controllers/game.php
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<?php
|
||||
$heading = "Game";
|
||||
require "views/game.view.php";
|
||||
3
controllers/login.php
Normal file
3
controllers/login.php
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<?php
|
||||
$heading = "Login";
|
||||
require "views/login.view.php";
|
||||
BIN
images/01coin.gif
Normal file
BIN
images/01coin.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.5 KiB |
BIN
images/bg.png
Normal file
BIN
images/bg.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
BIN
images/colored_tilemap_packed.png
Normal file
BIN
images/colored_tilemap_packed.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.3 KiB |
BIN
images/icon.png
Normal file
BIN
images/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 863 B |
|
|
@ -5,8 +5,10 @@ return[
|
|||
'/contact' => 'controllers/contact.php',
|
||||
'/learn' => 'controllers/learn.php',
|
||||
'/mathe' => 'controllers/mathe.php',
|
||||
'/game' => 'controllers/game.php',
|
||||
'/note' => 'controllers/notes/show.php',
|
||||
'/notes' => 'controllers/notes/index.php',
|
||||
'/addition' => 'controllers/addition.php',
|
||||
'/notes/create' => 'controllers/notes/create.php'
|
||||
'/notes/create' => 'controllers/notes/create.php',
|
||||
'/login' => 'controllers/login.php'
|
||||
];
|
||||
947
scripts/game.js
Normal file
947
scripts/game.js
Normal file
|
|
@ -0,0 +1,947 @@
|
|||
(function (w) {
|
||||
let count = 1;
|
||||
// game title
|
||||
const gametitle = "The Math Wizard";
|
||||
|
||||
/*****************
|
||||
*** resources ***
|
||||
*****************/
|
||||
|
||||
// This tileset is from kenney.nl
|
||||
// It's the "microrogue" tileset
|
||||
|
||||
const tileSet = document.createElement("img");
|
||||
tileSet.src = "colored_tilemap_packed.png";
|
||||
|
||||
const tileOptions = {
|
||||
layout: "tile",
|
||||
bg: "transparent",
|
||||
tileWidth: 8,
|
||||
tileHeight: 8,
|
||||
tileSet: tileSet,
|
||||
tileMap: {
|
||||
"@": [40, 0], // player
|
||||
".": [32, 32], // floor
|
||||
"M": [88, 0], // monster
|
||||
"*": [72, 24], // treasure chest
|
||||
"g": [64, 40], // gold
|
||||
"x": [56, 32], // axe
|
||||
"p": [56, 64], // potion
|
||||
"a": [40, 32], // tree 1
|
||||
"b": [32, 40], // tree 2
|
||||
"c": [40, 40], // tree 3
|
||||
"d": [48, 40], // tree 4
|
||||
"e": [56, 40], // tree 5
|
||||
"T": [72, 56], // tombstone
|
||||
"╔": [0, 72], // room corner
|
||||
"╗": [24, 72], // room corner
|
||||
"╝": [72, 72], // room corner
|
||||
"╚": [48, 72], // room corner
|
||||
"═": [8, 72], // room edge
|
||||
"║": [32, 72], // room edge
|
||||
"o": [40, 72], // room corner
|
||||
"D": [16, 16], //Door to win
|
||||
"s": [32, 24], //stairs to next Stage
|
||||
},
|
||||
width: 25,
|
||||
height: 40,
|
||||
};
|
||||
|
||||
const usePointer = true;
|
||||
const useArrows = true;
|
||||
const touchOffsetY = -20; // move the center by this much
|
||||
const scaleMobile = 4; // scale mobile screens by this much
|
||||
const scaleMonitor = 6; // scale computer screens by this much
|
||||
const turnLengthMS = 200; // shortest time between turns
|
||||
|
||||
// these map tiles are walkable
|
||||
const walkable = [".", "*", "g", "D", "s"];
|
||||
|
||||
// these map tiles should not be replaced by room edges
|
||||
const noreplace = walkable.concat(["M", "╔", "╗", "╚", "╝", "═", "║"]);
|
||||
|
||||
// These sound effects are generated using sfxr.me
|
||||
|
||||
const sfx = {
|
||||
rubber:
|
||||
"5EoyNVaezhPnpFZjpkcJkF8FNCioPncLoztbSHU4u9wDQ8W3P7puffRWvGMnrLRdHa61kGcwhZK3RdoDRitmtwn4JjrQsZCZBmDQgkP5uGUGk863wbpRi1xdA",
|
||||
step: "34T6PkwiBPcxMGrK7aegATo5WTMWoP17BTc6pwXbwqRvndwRjGYXx6rG758rLSU5suu35ZTkRCs1K2NAqyrTZbiJUHQmra9qvbBrSdbBbJ7JvmyBFVDo6eiVD",
|
||||
choice:
|
||||
"34T6PkzXyyB6jHiwFztCFWEWsogkzrhzAH3FH2d97BCuFhqmZgfuXG3xtz8YYSKMzn95yyX8xZXJyesKmpcjpEL3dPP5h2e8mt5MmhExAksyqZyqgavBgsWMd",
|
||||
hide: "34T6PkzXyyB6jHiwFztCFWEniygA1GJtjsQuGxcd38JLDquhRqTB28dQgigseMjQSjSY14Z3aBmAtzz9KWcJZ2o9S1oCcgqQY4dxTAXikS7qCs3QJ3KuWJUyD",
|
||||
empty:
|
||||
"111112RrwhZ2Q7NGcdAP21KUHHKNQa3AhmK4Xea8mbiXfzkxr9aX41M8XYt5xYaaLeo9iZdUKUVL3u2N6XASue2wPv2wCCDy6W6TeFiUjk3dXSzFcBY7kTAM",
|
||||
hit: "34T6Pks4nddGzchAFWpSTRAKitwuQsfX8bfzRpJx5eDR7NSqxeeLMEkLjcuwvTCDS1ve7amXBg4eipzDdgKWoYnJBsQVESZh2X1DFV2GWybY5bAihB2EdHsbd",
|
||||
miss: "8R25jogvbp3Qy6A4GTPxRP4aT2SywwsAgoJ2pKmxUFMExgNashjgd311MnmZ2ThwrPQz71LA53QCfFmYQLHaXo6SocUv4zcfNAU5SFocZnoQSDCovnjpioNz3",
|
||||
win: "34T6Pkv34QJsqDqEa8aV4iwF2LnASMc3683oFUPKZic6kVUHvwjUQi6rz8qNRUHRs34cu37P5iQzz2AzipW3DHMoG5h4BZgDmZnyLhsXgPKsq2r4Fb2eBFVuR",
|
||||
lose: "7BMHBGHKKnn7bgcmGprqiBmpuRaTytcd4JS9eRNDzUTRuQy8BTBzs5g8XzS7rrp4C9cNeSaqAtWR9qdvXvtnWVTmTC8GXgDuCXD2KyHJNXzfUahbZrce8ibuy",
|
||||
kill: "7BMHBGKMhg8NZkxKcJxNfTWXKtMPiZVNsLR4aPEAghCSpz5ZxpjS5k4j4ZQpJ65UZnHSr4R2d7ALCHJe41pAS2ZPjauM7SveudhDGAxw2dhXpiNwEhG8xUYkX",
|
||||
};
|
||||
|
||||
for (let s in sfx) {
|
||||
sfx[s] = new SoundEffect(sfx[s]).generate().getAudio();
|
||||
}
|
||||
|
||||
const keyMap = {
|
||||
38: 0,
|
||||
33: 1,
|
||||
39: 2,
|
||||
34: 3,
|
||||
40: 4,
|
||||
35: 5,
|
||||
37: 6,
|
||||
36: 7,
|
||||
};
|
||||
|
||||
/*****************
|
||||
*** game code ***
|
||||
*****************/
|
||||
|
||||
// based on the original tutorial by Ondřej Žára
|
||||
// www.roguebasin.com/index.php?title=Rot.js_tutorial,_part_1
|
||||
|
||||
const Game = {
|
||||
// this is the ROT.js display handler
|
||||
display: null,
|
||||
// this is our map data
|
||||
map: {},
|
||||
// map of all items
|
||||
items: {},
|
||||
// reference to the ROT.js engine which
|
||||
// manages stuff like scheduling
|
||||
engine: null,
|
||||
// schedules events in the game for ROT.js
|
||||
scheduler: null,
|
||||
// reference to the player object
|
||||
player: null,
|
||||
// reference to the game monsters array
|
||||
monsters: null,
|
||||
door: null,
|
||||
// arrow handler
|
||||
lastArrow: null, // arrow keys held
|
||||
arrowInterval: null, // arrow key repeat
|
||||
arrowListener: null, // registered listener for arrow event
|
||||
// clean up this game instance
|
||||
cleanup: cleanup,
|
||||
playerAllowedToMove: true,
|
||||
};
|
||||
|
||||
// this gets called by the menu system
|
||||
// to launch the actual game
|
||||
function init(game) {
|
||||
game.map = {};
|
||||
game.items = {};
|
||||
// first create a ROT.js display manager
|
||||
game.display = new ROT.Display(tileOptions);
|
||||
resetCanvas(game.display.getContainer());
|
||||
|
||||
generateMap(game, count);
|
||||
|
||||
// let ROT.js schedule the player and monster entities
|
||||
game.scheduler = new ROT.Scheduler.Simple();
|
||||
game.scheduler.add(game.player, true);
|
||||
game.monsters.map((m) => game.scheduler.add(m, true));
|
||||
|
||||
// render the stats hud at the bottom of the screen
|
||||
renderStats(game.player.stats);
|
||||
|
||||
// kick everything off
|
||||
game.engine = new ROT.Engine(game.scheduler);
|
||||
game.engine.start();
|
||||
count = 1;
|
||||
}
|
||||
|
||||
function nextStage(game, stage, stats) {
|
||||
game.map = {};
|
||||
game.items = {};
|
||||
game.display = new ROT.Display(tileOptions);
|
||||
resetCanvas(game.display.getContainer());
|
||||
if (game.engine) {
|
||||
game.scheduler.clear();
|
||||
game.scheduler = null;
|
||||
game.monsters = null;
|
||||
game.door = null;
|
||||
game.stairs = null;
|
||||
};
|
||||
generateMap(game, stage);
|
||||
|
||||
// let ROT.js schedule the player and monster entities
|
||||
game.scheduler = new ROT.Scheduler.Simple();
|
||||
game.scheduler.add(game.player, true);
|
||||
game.monsters.map((m) => game.scheduler.add(m, true));
|
||||
|
||||
// render the stats hud at the bottom of the screen
|
||||
game.player.stats = stats;
|
||||
renderStats(game.player.stats);
|
||||
|
||||
// kick everything off
|
||||
game.engine = new ROT.Engine(game.scheduler);
|
||||
game.engine.start();;
|
||||
}
|
||||
|
||||
// this gets called at the end of the game when we want
|
||||
// to exit back out and clean everything up to display
|
||||
// the menu and get ready for next round
|
||||
function destroy(game) {
|
||||
// remove all listening event handlers
|
||||
removeListeners(game);
|
||||
|
||||
// tear everything down
|
||||
if (game.engine) {
|
||||
game.engine.lock();
|
||||
game.display = null;
|
||||
game.map = {};
|
||||
game.items = {};
|
||||
game.engine = null;
|
||||
game.scheduler.clear();
|
||||
game.scheduler = null;
|
||||
game.player = null;
|
||||
game.monsters = null;
|
||||
game.door = null;
|
||||
game.stairs = null;
|
||||
}
|
||||
|
||||
// hide the toast message
|
||||
hideToast(true);
|
||||
// close out the game screen and show the title
|
||||
showScreen("title");
|
||||
}
|
||||
|
||||
// this generates the game map
|
||||
function generateMap(game, stage) {
|
||||
const digger = new ROT.Map.Digger(tileOptions.width, tileOptions.height);
|
||||
// list of floor tiles that can be walked on
|
||||
const freeCells = [];
|
||||
// list of non-floor tiles that can't be traversed
|
||||
const zeroCells = [];
|
||||
|
||||
const digCallback = function (x, y, value) {
|
||||
const key = x + "," + y;
|
||||
if (value) {
|
||||
zeroCells.push(key);
|
||||
} else {
|
||||
game.map[key] = ".";
|
||||
freeCells.push(key);
|
||||
}
|
||||
};
|
||||
digger.create(digCallback.bind(game));
|
||||
|
||||
generateItems(game, freeCells);
|
||||
generateScenery(game.map, zeroCells);
|
||||
generateRooms(game.map, digger);
|
||||
|
||||
game.player = createBeing(makePlayer, freeCells);
|
||||
game.monsters = []
|
||||
for ( var i= 0; i<= stage; i++) {
|
||||
game.monsters.push(createBeing(makeMonster, freeCells));
|
||||
}
|
||||
|
||||
// draw the map and items
|
||||
for (let key in game.map) {
|
||||
drawTile(game, key);
|
||||
}
|
||||
|
||||
rescale(game.player._x, game.player._y, game);
|
||||
}
|
||||
|
||||
function generateItems(game, freeCells) {
|
||||
for (let i = 0; i < 15; i++) {
|
||||
const key = takeFreeCell(freeCells);
|
||||
if (!i) {
|
||||
if(count < 5) {
|
||||
game.stairs = key;
|
||||
game.items[key] = "s";
|
||||
} else {
|
||||
game.door = key;
|
||||
game.items[key] = "D";
|
||||
}
|
||||
} else {
|
||||
game.items[key] = ROT.RNG.getItem(["g"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function takeFreeCell(freeCells) {
|
||||
const index = Math.floor(ROT.RNG.getUniform() * freeCells.length);
|
||||
const key = freeCells.splice(index, 1)[0];
|
||||
return key;
|
||||
}
|
||||
|
||||
function posFromKey(key) {
|
||||
const parts = key.split(",");
|
||||
const x = parseInt(parts[0]);
|
||||
const y = parseInt(parts[1]);
|
||||
return [x, y];
|
||||
}
|
||||
|
||||
function generateScenery(map, freeCells) {
|
||||
for (let i = 0; i < 100; i++) {
|
||||
if (freeCells.length) {
|
||||
const key = takeFreeCell(freeCells);
|
||||
map[key] = ROT.RNG.getItem("abcde");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function generateRooms(map, mapgen) {
|
||||
const rooms = mapgen.getRooms();
|
||||
for (let rm = 0; rm < rooms.length; rm++) {
|
||||
const room = rooms[rm];
|
||||
|
||||
const l = room.getLeft() - 1;
|
||||
const r = room.getRight() + 1;
|
||||
const t = room.getTop() - 1;
|
||||
const b = room.getBottom() + 1;
|
||||
|
||||
map[l + "," + t] = "╔";
|
||||
map[r + "," + t] = "╗";
|
||||
map[l + "," + b] = "╚";
|
||||
map[r + "," + b] = "╝";
|
||||
|
||||
for (let i = room.getLeft(); i <= room.getRight(); i++) {
|
||||
const j = i + "," + t;
|
||||
const k = i + "," + b;
|
||||
if (noreplace.indexOf(map[j]) == -1) {
|
||||
map[j] = "═";
|
||||
}
|
||||
if (noreplace.indexOf(map[k]) == -1) {
|
||||
map[k] = "═";
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = room.getTop(); i <= room.getBottom(); i++) {
|
||||
const j = l + "," + i;
|
||||
const k = r + "," + i;
|
||||
if (noreplace.indexOf(map[j]) == -1) {
|
||||
map[j] = "║";
|
||||
}
|
||||
if (noreplace.indexOf(map[k]) == -1) {
|
||||
map[k] = "║";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function drawTile(game, key, ignore) {
|
||||
const map = game.map;
|
||||
if (map[key]) {
|
||||
const parts = posFromKey(key);
|
||||
const monster = monsterAt(parts[0], parts[1]);
|
||||
const player = playerAt(parts[0], parts[1]);
|
||||
const display = game.display;
|
||||
const items = game.items;
|
||||
const draw = [map[key], items[key]];
|
||||
draw.push(monster && monster != ignore ? monster.character : null);
|
||||
draw.push(player && player != ignore ? player.character : null);
|
||||
display.draw(
|
||||
parts[0],
|
||||
parts[1],
|
||||
draw.filter((i) => i)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// both the player and monster initial position is set
|
||||
function createBeing(what, freeCells) {
|
||||
const key = takeFreeCell(freeCells);
|
||||
const pos = posFromKey(key);
|
||||
const being = what(pos[0], pos[1]);
|
||||
return being;
|
||||
}
|
||||
|
||||
/******************
|
||||
*** the player ***
|
||||
******************/
|
||||
|
||||
// creates a player object with position, and stats
|
||||
function makePlayer(x, y) {
|
||||
return {
|
||||
// player's position
|
||||
_x: x,
|
||||
_y: y,
|
||||
character: "@",
|
||||
name: "you",
|
||||
// the player's stats
|
||||
stats: { hp: 10, xp: 0, gold: 0 },
|
||||
// the ROT.js scheduler calls this method when it is time
|
||||
// for the player to act
|
||||
act: () => {
|
||||
Game.engine.lock();
|
||||
if (!Game["arrowListener"]) {
|
||||
document.addEventListener("arrow", arrowEventHandler);
|
||||
Game.arrowListener = true;
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// this method gets called by the `movePlayer` function
|
||||
function checkItem(entity) {
|
||||
const key = entity._x + "," + entity._y;
|
||||
if (key == Game.door) {
|
||||
if(count < 5) {
|
||||
nextStage(Game, ++count, Game.player.stats);
|
||||
} else {
|
||||
win();
|
||||
}
|
||||
} else if (key == Game.stairs) {
|
||||
nextStage(Game, ++count, Game.player.stats);
|
||||
}else if (Game.items[key] == "g") {
|
||||
Game.player.stats.gold += 1;
|
||||
renderStats(Game.player.stats);
|
||||
toast("You found gold!");
|
||||
sfx["win"].play();
|
||||
delete Game.items[key];
|
||||
}
|
||||
drawTile(Game, key);
|
||||
}
|
||||
|
||||
function movePlayer(dir) {
|
||||
const p = Game.player;
|
||||
return movePlayerTo(p._x + dir[0], p._y + dir[1]);
|
||||
}
|
||||
|
||||
function movePlayerTo(x, y) {
|
||||
const p = Game.player;
|
||||
|
||||
const newKey = x + "," + y;
|
||||
if (walkable.indexOf(Game.map[newKey]) == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
// check if we've hit the monster
|
||||
const hitMonster = monsterAt(x, y);
|
||||
if (hitMonster) {
|
||||
//combat(p, hitMonster);
|
||||
setTimeout(function () {
|
||||
Game.engine.unlock();
|
||||
}, 250);
|
||||
} else {
|
||||
hideToast();
|
||||
|
||||
drawTile(Game, p._x + "," + p._y, p);
|
||||
|
||||
// update the player's coordinates
|
||||
p._x = x;
|
||||
p._y = y;
|
||||
|
||||
// re-draw the player
|
||||
for (let key in Game.map) {
|
||||
drawTile(Game, key);
|
||||
}
|
||||
// re-locate the game screen to center the player
|
||||
rescale(x, y, Game);
|
||||
window.removeEventListener("arrow", arrowEventHandler);
|
||||
Game.engine.unlock();
|
||||
sfx["step"].play();
|
||||
// check if the player stepped on an item
|
||||
checkItem(p);
|
||||
}
|
||||
}
|
||||
|
||||
/*******************
|
||||
*** The monster ***
|
||||
*******************/
|
||||
|
||||
// basic ROT.js entity with position and stats
|
||||
function makeMonster(x, y) {
|
||||
return {
|
||||
// monster position
|
||||
_x: x,
|
||||
_y: y,
|
||||
character: "M",
|
||||
name: "Orc",
|
||||
stats: { hp: 3 },
|
||||
// called by the ROT.js scheduler
|
||||
act: monsterAct,
|
||||
};
|
||||
}
|
||||
|
||||
function monsterAct() {
|
||||
const m = this;
|
||||
const p = Game.player;
|
||||
const map = Game.map;
|
||||
const display = Game.display;
|
||||
|
||||
const passableCallback = function (x, y) {
|
||||
return walkable.indexOf(map[x + "," + y]) != -1;
|
||||
};
|
||||
const astar = new ROT.Path.AStar(p._x, p._y, passableCallback, {
|
||||
topology: 4,
|
||||
});
|
||||
const path = [];
|
||||
const pathCallback = function (x, y) {
|
||||
path.push([x, y]);
|
||||
};
|
||||
astar.compute(m._x, m._y, pathCallback);
|
||||
|
||||
path.shift();
|
||||
if (path.length <= 1) {
|
||||
Game.playerAllowedToMove = false;
|
||||
Game.engine.lock();
|
||||
combat(m, p);
|
||||
} else {
|
||||
drawTile(Game, m._x + "," + m._y, m);
|
||||
m._x = path[0][0];
|
||||
m._y = path[0][1];
|
||||
drawTile(Game, m._x + "," + m._y);
|
||||
}
|
||||
}
|
||||
|
||||
function monsterAt(x, y) {
|
||||
if (Game.monsters && Game.monsters.length) {
|
||||
for (let mi = 0; mi < Game.monsters.length; mi++) {
|
||||
const m = Game.monsters[mi];
|
||||
if (m && m._x == x && m._y == y) {
|
||||
return m;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function playerAt(x, y) {
|
||||
return Game.player && Game.player._x == x && Game.player._y == y
|
||||
? Game.player
|
||||
: null;
|
||||
}
|
||||
|
||||
// if the monster is dead remove it from the game
|
||||
function checkDeath(m) {
|
||||
if (m.stats.hp <= 0) {
|
||||
if (m == Game.player) {
|
||||
toast("You died!");
|
||||
lose();
|
||||
} else {
|
||||
const key = m._x + "," + m._y;
|
||||
removeMonster(m);
|
||||
sfx["kill"].play();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// remove a monster from the game
|
||||
function removeMonster(m) {
|
||||
const key = m._x + "," + m._y;
|
||||
Game.scheduler.remove(m);
|
||||
Game.monsters = Game.monsters.filter((mx) => mx != m);
|
||||
drawTile(Game, key);
|
||||
}
|
||||
|
||||
/******************************
|
||||
*** combat/win/lose events ***
|
||||
******************************/
|
||||
// this is how the player fights a monster
|
||||
function checkSolution(solution, answer, hitter, receiver) {
|
||||
console.log("Click: " + solution + " Antwort: " + answer);
|
||||
if (solution == answer) {
|
||||
hitter.stats.hp -= 1;
|
||||
sfx["hit"].play();
|
||||
if (checkDeath(hitter)) {
|
||||
Game.player.stats.xp += 1;
|
||||
showScreen("game");
|
||||
Game.playerAllowedToMove = true;
|
||||
Game.engine.unlock();
|
||||
} else {
|
||||
combat(hitter, receiver);
|
||||
}
|
||||
checkDeath(hitter);
|
||||
} else {
|
||||
sfx["miss"].play();
|
||||
//showScreen("game");
|
||||
//Game.playerAllowedToMove = true;
|
||||
//Game.engine.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
function setupButtons(answerValue, hitter, receiver) {
|
||||
const randomValue = (min, max) =>
|
||||
Math.floor(Math.random() * (max - min)) + min;
|
||||
let randomVar = randomValue(1, 4);
|
||||
if (randomVar == 1) {
|
||||
document.getElementById("answer1").innerHTML = `${answerValue}`;
|
||||
document.getElementById("answer2").innerHTML = `${
|
||||
answerValue + randomValue(1, 4)
|
||||
}`;
|
||||
document.getElementById("answer3").innerHTML = `${
|
||||
answerValue - randomValue(1, 4)
|
||||
}`;
|
||||
} else if (randomVar == 2) {
|
||||
document.getElementById("answer1").innerHTML = `${
|
||||
answerValue + randomValue(1, 4)
|
||||
}`;
|
||||
document.getElementById("answer2").innerHTML = `${answerValue}`;
|
||||
document.getElementById("answer3").innerHTML = `${
|
||||
answerValue - randomValue(1, 4)
|
||||
}`;
|
||||
} else {
|
||||
document.getElementById("answer1").innerHTML = `${
|
||||
answerValue - randomValue(1, 4)
|
||||
}`;
|
||||
document.getElementById("answer2").innerHTML = `${
|
||||
answerValue + randomValue(1, 4)
|
||||
}`;
|
||||
document.getElementById("answer3").innerHTML = `${answerValue}`;
|
||||
}
|
||||
document.getElementById("answer1").addEventListener("click", async() => {
|
||||
checkSolution(document.getElementById("answer1").innerText, answerValue, hitter, receiver);
|
||||
}, {once: true});
|
||||
document.getElementById("answer2").addEventListener("click", async() => {
|
||||
checkSolution(document.getElementById("answer2").innerText, answerValue, hitter, receiver);
|
||||
}, {once: true});
|
||||
document.getElementById("answer3").addEventListener("click", async() => {
|
||||
checkSolution(document.getElementById("answer3").innerText, answerValue, hitter, receiver);
|
||||
}, {once: true});
|
||||
}
|
||||
|
||||
function combat(hitter, receiver) {
|
||||
const randomValue = (min, max) =>
|
||||
Math.floor(Math.random() * (max - min)) + min;
|
||||
let [num1, num2] = [randomValue(1, 10), randomValue(1, 10)];
|
||||
const answerValue = eval(`${num1} * ${num2}`);
|
||||
document.getElementById("question").innerHTML = `${num1} * ${num2} = ? `;
|
||||
setupButtons(answerValue, hitter, receiver);
|
||||
showScreen("combat");
|
||||
checkDeath(receiver);
|
||||
renderStats(Game.player.stats);
|
||||
}
|
||||
|
||||
// this gets called when the player wins the game
|
||||
function win() {
|
||||
Game.engine.lock();
|
||||
for (let i = 0; i < 5; i++) {
|
||||
setTimeout(function () {
|
||||
sfx["win"].play();
|
||||
}, 100 * i);
|
||||
}
|
||||
// set our stats for the end screen
|
||||
setEndScreenValues(Game.player.stats.xp, Game.player.stats.gold);
|
||||
// tear down the game
|
||||
destroy(Game);
|
||||
showScreen("win");
|
||||
}
|
||||
|
||||
// this gets called when the player loses the game
|
||||
function lose() {
|
||||
Game.engine.lock();
|
||||
// change the player into a tombstone tile
|
||||
const p = Game.player;
|
||||
p.character = "T";
|
||||
drawTile(Game, p._x + "," + p._y);
|
||||
const ghost = createGhost([p._x, p._y]);
|
||||
removeListeners(Game);
|
||||
sfx["lose"].play();
|
||||
setTimeout(function () {
|
||||
setEndScreenValues(Game.player.stats.xp, Game.player.stats.gold);
|
||||
// tear down the game
|
||||
destroy(Game);
|
||||
showScreen("lose");
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
/************************************
|
||||
*** graphics, UI & browser utils ***
|
||||
************************************/
|
||||
|
||||
const clickevt = !!("ontouchstart" in window) ? "touchstart" : "click";
|
||||
|
||||
const $ = document.querySelector.bind(document);
|
||||
const $$ = document.querySelectorAll.bind(document);
|
||||
NodeList.prototype.forEach = Array.prototype.forEach;
|
||||
|
||||
// this code resets the ROT.js display canvas
|
||||
function resetCanvas(el) {
|
||||
$("#canvas").innerHTML = "";
|
||||
$("#canvas").appendChild(el);
|
||||
window.onkeydown = keyHandler;
|
||||
window.onkeyup = arrowStop;
|
||||
if (useArrows) {
|
||||
document.ontouchend = arrowStop;
|
||||
}
|
||||
showScreen("game");
|
||||
}
|
||||
|
||||
function rescale(x, y, game) {
|
||||
const c = $("canvas");
|
||||
const scale = window.innerWidth < 600 ? scaleMobile : scaleMonitor;
|
||||
const offset = game.touchScreen ? touchOffsetY : 0;
|
||||
const tw =
|
||||
x * -tileOptions.tileWidth +
|
||||
(tileOptions.width * tileOptions.tileWidth) / 2 +
|
||||
-4;
|
||||
const th =
|
||||
y * -tileOptions.tileHeight +
|
||||
(tileOptions.height * tileOptions.tileHeight) / 2 +
|
||||
offset;
|
||||
if (canvas) {
|
||||
canvas.style.transition = "transform 0.5s ease-out 0s";
|
||||
if (game.display) {
|
||||
game.display
|
||||
.getContainer()
|
||||
.getContext("2d").imageSmoothingEnabled = false;
|
||||
}
|
||||
canvas.style.transform =
|
||||
"scale(" +
|
||||
scale +
|
||||
") " +
|
||||
"translate3d(" +
|
||||
Math.floor(tw) +
|
||||
"px," +
|
||||
Math.floor(th) +
|
||||
"px,0px)";
|
||||
}
|
||||
}
|
||||
|
||||
function removeListeners(game) {
|
||||
if (game.engine) {
|
||||
game.lastArrow = null;
|
||||
clearInterval(game.arrowInterval);
|
||||
game.arrowInterval = null;
|
||||
game.engine.lock();
|
||||
game.scheduler.clear();
|
||||
window.removeEventListener("arrow", arrowEventHandler);
|
||||
game.arrowListener = false;
|
||||
window.onkeydown = null;
|
||||
window.onkeyup = null;
|
||||
}
|
||||
}
|
||||
|
||||
// hides all screens and shows the requested screen
|
||||
function showScreen(which, ev) {
|
||||
ev && ev.preventDefault();
|
||||
const el = $("#" + which);
|
||||
const actionbutton = $("#" + which + ">.action");
|
||||
document.querySelectorAll(".screen").forEach(function (s) {
|
||||
s.classList.remove("show");
|
||||
s.classList.add("hide");
|
||||
});
|
||||
el.classList.remove("hide");
|
||||
el.classList.remove("show");
|
||||
void el.offsetHeight; // trigger CSS reflow
|
||||
el.classList.add("show");
|
||||
if (actionbutton) {
|
||||
actionbutton.focus();
|
||||
}
|
||||
}
|
||||
|
||||
// set the end-screen message
|
||||
function setEndScreenValues(xp, gold) {
|
||||
$$(".xp-stat").forEach((el) => (el.textContent = Math.floor(xp)));
|
||||
$$(".gold-stat").forEach((el) => (el.textContent = gold));
|
||||
}
|
||||
|
||||
// updates the stats listed at the bottom of the screen
|
||||
function renderStats(stats) {
|
||||
const st = $("#hud");
|
||||
st.innerHTML = "";
|
||||
for (let s in stats) {
|
||||
attach(st, el("span", {}, [s.toUpperCase() + ": " + stats[s]]));
|
||||
}
|
||||
}
|
||||
|
||||
// creates the ghost sprite when the player dies
|
||||
function createGhost(pos) {
|
||||
const tw = tileOptions.tileWidth;
|
||||
const th = tileOptions.tileHeight;
|
||||
const left = "left:" + pos[0] * tw + "px;";
|
||||
const top = "top:" + pos[1] * th + "px;";
|
||||
const ghost = el("div", {
|
||||
className: "sprite ghost free float-up",
|
||||
style: left + top,
|
||||
});
|
||||
ghost.onanimationend = function () {
|
||||
rmel(ghost);
|
||||
};
|
||||
return attach($("#canvas"), ghost);
|
||||
}
|
||||
|
||||
function battleMessage(messages) {
|
||||
const components = messages.reduce(function (msgs, m) {
|
||||
return msgs
|
||||
.concat(
|
||||
m.split(" ").map(function (p) {
|
||||
const match = p.match(/hit|miss/);
|
||||
return el("span", { className: match ? match[0] : "" }, [p, " "]);
|
||||
})
|
||||
)
|
||||
.concat(el("br", {}));
|
||||
}, []);
|
||||
return el("span", {}, components);
|
||||
}
|
||||
|
||||
function toast(message) {
|
||||
const m = $("#message");
|
||||
if (
|
||||
Game.scheduler._current == Game.player ||
|
||||
m.className.indexOf("show") == -1
|
||||
) {
|
||||
m.innerHTML = "";
|
||||
}
|
||||
m.classList.remove("fade-out");
|
||||
m.classList.add("show");
|
||||
if (typeof message == "string") {
|
||||
m.appendChild(el("span", {}, [message]));
|
||||
} else {
|
||||
m.appendChild(message);
|
||||
}
|
||||
}
|
||||
|
||||
function hideToast(instant) {
|
||||
const m = $("#message");
|
||||
if (instant) {
|
||||
m.classList.remove("show");
|
||||
m.classList.remove("fade-out");
|
||||
m.innerHTML = "";
|
||||
} else if (m.className.match("show")) {
|
||||
m.classList.remove("show");
|
||||
m.classList.add("fade-out");
|
||||
m.onanimationend = function () {
|
||||
m.classList.remove("fade-out");
|
||||
m.innerHTML = "";
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// create an HTML element
|
||||
function el(tag, attrs, children) {
|
||||
const node = document.createElement(tag);
|
||||
for (a in attrs) {
|
||||
node[a] = attrs[a];
|
||||
}
|
||||
(children || []).forEach(function (c) {
|
||||
if (typeof c == "string") {
|
||||
node.appendChild(document.createTextNode(c));
|
||||
} else {
|
||||
attach(node, c);
|
||||
}
|
||||
});
|
||||
return node;
|
||||
}
|
||||
|
||||
// add an HTML element to a parent node
|
||||
function attach(node, el) {
|
||||
node.appendChild(el);
|
||||
return el;
|
||||
}
|
||||
|
||||
// remove an element from the dom
|
||||
function rmel(node) {
|
||||
node.parentNode.removeChild(node);
|
||||
}
|
||||
|
||||
/*************************
|
||||
*** UI event handlers ***
|
||||
*************************/
|
||||
|
||||
function keyHandler(ev) {
|
||||
const code = ev.keyCode;
|
||||
if (code == 187 || code == 189) {
|
||||
ev.preventDefault();
|
||||
return;
|
||||
}
|
||||
if (code == 70 && ev.altKey && ev.ctrlKey && ev.shiftKey) {
|
||||
document.body.requestFullscreen();
|
||||
console.log("Full screen pressed.");
|
||||
return;
|
||||
}
|
||||
if (code == 73) {
|
||||
toggleInventory(ev, true);
|
||||
return;
|
||||
}
|
||||
if (code == 190) {
|
||||
Game.engine.unlock();
|
||||
return;
|
||||
} // skip turn
|
||||
if (!(code in keyMap)) {
|
||||
return;
|
||||
}
|
||||
const dir = ROT.DIRS[8][keyMap[code]];
|
||||
if (Game.display) {
|
||||
ev.preventDefault();
|
||||
}
|
||||
if(Game.playerAllowedToMove) {
|
||||
arrowStart(dir);
|
||||
}
|
||||
}
|
||||
|
||||
function arrowStart(dir) {
|
||||
const last = Game.lastArrow;
|
||||
Game.lastArrow = dir;
|
||||
if (!last) {
|
||||
document.dispatchEvent(new Event("arrow"));
|
||||
if (Game.arrowInterval) {
|
||||
clearInterval(Game.arrowInterval);
|
||||
}
|
||||
Game.arrowInterval = setInterval(function () {
|
||||
document.dispatchEvent(new Event("arrow"));
|
||||
}, turnLengthMS);
|
||||
}
|
||||
}
|
||||
|
||||
function arrowStop(ev) {
|
||||
clearInterval(Game.arrowInterval);
|
||||
Game.arrowInterval = null;
|
||||
Game.lastArrow = null;
|
||||
}
|
||||
|
||||
function arrowEventHandler() {
|
||||
if (Game.lastArrow) {
|
||||
movePlayer(Game.lastArrow);
|
||||
} else {
|
||||
arrowStop();
|
||||
}
|
||||
}
|
||||
|
||||
function startGame(ev) {
|
||||
showScreen("game");
|
||||
sfx["rubber"].play();
|
||||
init(Game);
|
||||
}
|
||||
|
||||
function handleMenuChange(which, ev) {
|
||||
ev.preventDefault();
|
||||
const choice = which.getAttribute("value");
|
||||
showScreen(choice);
|
||||
sfx["choice"].play();
|
||||
}
|
||||
|
||||
function hideModal(ev) {
|
||||
ev.preventDefault();
|
||||
showScreen("title");
|
||||
sfx["hide"].play();
|
||||
}
|
||||
|
||||
function cleanup() {
|
||||
destroy(Game);
|
||||
$("#play").removeEventListener(clickevt, startGame);
|
||||
}
|
||||
|
||||
/***************
|
||||
*** Startup ***
|
||||
***************/
|
||||
|
||||
// this code is called at load time and sets the game title
|
||||
document.querySelectorAll(".game-title-text").forEach(function (t) {
|
||||
t.textContent = gametitle;
|
||||
});
|
||||
|
||||
// listen for the start game button
|
||||
$("#play").addEventListener(clickevt, startGame);
|
||||
|
||||
if (w["rbb"]) {
|
||||
w["rbb"].cleanup();
|
||||
} else {
|
||||
$("#plate").addEventListener(
|
||||
"animationend",
|
||||
showScreen.bind(null, "title")
|
||||
);
|
||||
document.querySelectorAll("#options #menu input").forEach(function (el) {
|
||||
el.addEventListener("click", handleMenuChange.bind(null, el));
|
||||
});
|
||||
document.querySelectorAll(".modal button.action").forEach(function (el) {
|
||||
el.addEventListener(clickevt, hideModal);
|
||||
});
|
||||
}
|
||||
|
||||
w["rbb"] = Game;
|
||||
})(window);
|
||||
476
styles/game.css
Normal file
476
styles/game.css
Normal file
|
|
@ -0,0 +1,476 @@
|
|||
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P');
|
||||
|
||||
.game-title-text {
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
touch-action: manipulation;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-webkit-touch-callout: none;
|
||||
}
|
||||
|
||||
html {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Press Start 2P', cursive;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0px auto;
|
||||
font-size: 1.5em;
|
||||
background-color: #222323;
|
||||
color: #eee;
|
||||
}
|
||||
|
||||
@media (max-width: 700px), (max-height: 820px) {
|
||||
body {
|
||||
font-size: 0.75em;
|
||||
}
|
||||
}
|
||||
|
||||
canvas {
|
||||
image-rendering: optimizeSpeed;
|
||||
image-rendering: crisp-edges;
|
||||
image-rendering: -moz-crisp-edges;
|
||||
image-rendering: -o-crisp-edges;
|
||||
image-rendering: -webkit-optimize-contrast;
|
||||
-ms-interpolation-mode: nearest-neighbor;
|
||||
image-rendering: pixelated;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #e77;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #faa;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/*** NES.css overrides ***/
|
||||
|
||||
.nes-container.is-rounded.is-dark {
|
||||
border-image-slice: 9 9 9 9 fill;
|
||||
border-image-source: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAphgAAKYYBIuzfjAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAABgSURBVEiJY2AYBQQAIzGK/v///x+rZkZGgvqZSHURqYDmFrDgkkAOFikpZYJqcAXX0A8iFG8REyy4wLNndxGGIgXX0A+iUQsIApxlCTEpClfKQQbDOIiQwcgurkcBQQAARlMedugABy8AAAAASUVORK5CYII=');
|
||||
background-color: transparent;
|
||||
border-image-repeat: stretch;
|
||||
}
|
||||
|
||||
.nes-container.is-fake-rounded.is-dark::after {
|
||||
background: none;
|
||||
}
|
||||
|
||||
/*** screens ***/
|
||||
|
||||
.screen {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
position: absolute;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.modal {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
bottom: 0px;
|
||||
right: 0px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #222323;
|
||||
}
|
||||
|
||||
#title {
|
||||
background-image: url(bg.png);
|
||||
background-size: cover;
|
||||
animation: 20s para infinite ease;
|
||||
}
|
||||
|
||||
@keyframes para {
|
||||
0% {
|
||||
background-position: 0px 0%;
|
||||
}
|
||||
50% {
|
||||
background-position: 0px 80px;
|
||||
}
|
||||
100% {
|
||||
background-position: 0px 0px;
|
||||
}
|
||||
}
|
||||
|
||||
#plate {
|
||||
display: flex;
|
||||
animation: 2s plate-fade;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
#plate > div {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
text-align: left;
|
||||
padding: 40px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
@keyframes plate-fade {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
25% {
|
||||
opacity: 0;
|
||||
}
|
||||
50% {
|
||||
opacity: 1;
|
||||
}
|
||||
75% {
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
#plate > div > * + * {
|
||||
margin-left: 32px;
|
||||
margin-right: 0px;
|
||||
}
|
||||
|
||||
@media (max-width: 700px), (max-height: 820px) {
|
||||
#plate > div {
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#plate > div > * + * {
|
||||
margin-left: 0px;
|
||||
margin-right: 0px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
#game-title {
|
||||
margin-bottom: 0px;
|
||||
width: 900px;
|
||||
max-width: 98%;
|
||||
}
|
||||
|
||||
@media (min-width: 700px) and (max-height: 820px) {
|
||||
#game-title {
|
||||
width: 900px;
|
||||
max-width: 98%;
|
||||
max-height: 35vh;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-height: 600px) {
|
||||
#game-title {
|
||||
width: 500px;
|
||||
}
|
||||
}
|
||||
|
||||
.game-title-animation {
|
||||
animation: 2s zoomInDown;
|
||||
}
|
||||
|
||||
/* https://github.com/animate-css/animate.css/blob/master/animate.css */
|
||||
@keyframes zoomInDown {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: scale3d(0.1, 0.1, 0.1) translate3d(0, -1000px, 0);
|
||||
animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19);
|
||||
}
|
||||
|
||||
60% {
|
||||
opacity: 1;
|
||||
transform: scale3d(0.475, 0.475, 0.475) translate3d(0, 60px, 0);
|
||||
animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1);
|
||||
}
|
||||
}
|
||||
|
||||
#options {
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
max-width: 90%;
|
||||
}
|
||||
|
||||
#logo {
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
#menu {
|
||||
width: 400px;
|
||||
max-width: 100%;
|
||||
margin-bottom: 64px;
|
||||
padding: 32px;
|
||||
}
|
||||
|
||||
#menu label {
|
||||
margin-left: -1em;
|
||||
padding-top: 0.5em;
|
||||
padding-bottom: 0.5em;
|
||||
}
|
||||
|
||||
.modal > * {
|
||||
max-width: 90%;
|
||||
width: 400px;
|
||||
margin: 50px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media (max-width: 700px), (max-height: 820px) {
|
||||
.modal > * {
|
||||
margin: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
#instructions div > p {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
#settings div > p {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
#credits ul {
|
||||
list-style: "> ";
|
||||
padding-left: 2em;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
#credits ul li {
|
||||
margin: 0.5em 0px;
|
||||
}
|
||||
|
||||
.sprite {
|
||||
display: block;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
image-rendering: optimizeSpeed;
|
||||
image-rendering: crisp-edges;
|
||||
image-rendering: -moz-crisp-edges;
|
||||
image-rendering: -o-crisp-edges;
|
||||
image-rendering: -webkit-optimize-contrast;
|
||||
-ms-interpolation-mode: nearest-neighbor;
|
||||
image-rendering: pixelated;
|
||||
transform: scale(8);
|
||||
background-image: url("colored_tilemap_packed.png");
|
||||
margin: 80px auto;
|
||||
}
|
||||
|
||||
.free {
|
||||
position: absolute;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
|
||||
.tomb {
|
||||
background-position: -72px -56px;
|
||||
}
|
||||
|
||||
.ghost {
|
||||
background-position: -72px -8px;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
.empty {
|
||||
background-position: -8px -48px;
|
||||
}
|
||||
|
||||
.float-up {
|
||||
animation: float-up 2s linear forwards;
|
||||
}
|
||||
|
||||
@keyframes float-up {
|
||||
from {
|
||||
transform: scale(1) translate(0px, 0px);
|
||||
opacity: 1;
|
||||
}
|
||||
to {
|
||||
transform: scale(3) translate(0px, -20px);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.grow-fade {
|
||||
animation: grow-fade 2s linear;
|
||||
}
|
||||
|
||||
@keyframes grow-fade {
|
||||
from {
|
||||
transform: translate(0px, 0px) scale(8);
|
||||
opacity: 0.5;
|
||||
}
|
||||
to {
|
||||
transform: translate(0px, 0px) scale(16);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
#play {
|
||||
width: 400px;
|
||||
max-width: 90%;
|
||||
}
|
||||
|
||||
#win {
|
||||
background: url(01coin.gif);
|
||||
background-size: 20%;
|
||||
}
|
||||
|
||||
/*** HUD ***/
|
||||
|
||||
#hud {
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
width: 600px;
|
||||
max-width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-evenly;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
#message {
|
||||
position: absolute;
|
||||
top: 24px;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#message .hit {
|
||||
color: #C01256;
|
||||
}
|
||||
|
||||
#message .miss {
|
||||
color: #FFB570;
|
||||
}
|
||||
|
||||
#inventory {
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
left: 0px;
|
||||
}
|
||||
|
||||
#inventory .sprite {
|
||||
transform: scale(3);
|
||||
display: inline-block;
|
||||
margin: 1em 2em 1em 1em;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
#inventory li {
|
||||
margin: 1em 0px;
|
||||
}
|
||||
|
||||
#inventory ul {
|
||||
list-style-type: none;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
#inventory > div {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (max-width: 750px) {
|
||||
#inventory {
|
||||
bottom: 72px;
|
||||
}
|
||||
|
||||
#hud {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
#arrows {
|
||||
display: none;
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
bottom: 0px;
|
||||
}
|
||||
|
||||
#arrows > * {
|
||||
float: left;
|
||||
font-size: 16px;
|
||||
bottom: 0px;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
#arrows > * > span {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#btn-left {
|
||||
position: absolute;
|
||||
right: 7em;
|
||||
}
|
||||
|
||||
#btn-right {
|
||||
position: absolute;
|
||||
right: 0em;
|
||||
}
|
||||
|
||||
#btn-up {
|
||||
transform: rotate(90deg);
|
||||
position: absolute;
|
||||
right: 3.5em;
|
||||
margin-bottom: 3.75em;
|
||||
}
|
||||
|
||||
#btn-down {
|
||||
transform: rotate(90deg);
|
||||
position: absolute;
|
||||
right: 3.5em;
|
||||
}
|
||||
|
||||
#btn-skip {
|
||||
position: absolute;
|
||||
right: 0em;
|
||||
margin-bottom: 3.75em;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
#arrows > * {
|
||||
bottom: 72px;
|
||||
}
|
||||
}
|
||||
|
||||
/*** CSS animations ***/
|
||||
|
||||
.fade-in {
|
||||
animation: fade-in 0.8s;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@keyframes fade-in {
|
||||
from{opacity:0} to{opacity:1}
|
||||
}
|
||||
|
||||
.hide {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.show {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.fade-out {
|
||||
display: flex;
|
||||
opacity: 1;
|
||||
animation: fade-out 3s forwards;
|
||||
}
|
||||
|
||||
@keyframes fade-out {
|
||||
from{opacity:1; display: flex;} 50%{opacity:1; display: flex;} to{opacity:0; display: none;}
|
||||
}
|
||||
476
views/game.css
Normal file
476
views/game.css
Normal file
|
|
@ -0,0 +1,476 @@
|
|||
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P');
|
||||
|
||||
.game-title-text {
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
touch-action: manipulation;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-webkit-touch-callout: none;
|
||||
}
|
||||
|
||||
html {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Press Start 2P', cursive;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0px auto;
|
||||
font-size: 1.5em;
|
||||
background-color: #222323;
|
||||
color: #eee;
|
||||
}
|
||||
|
||||
@media (max-width: 700px), (max-height: 820px) {
|
||||
body {
|
||||
font-size: 0.75em;
|
||||
}
|
||||
}
|
||||
|
||||
canvas {
|
||||
image-rendering: optimizeSpeed;
|
||||
image-rendering: crisp-edges;
|
||||
image-rendering: -moz-crisp-edges;
|
||||
image-rendering: -o-crisp-edges;
|
||||
image-rendering: -webkit-optimize-contrast;
|
||||
-ms-interpolation-mode: nearest-neighbor;
|
||||
image-rendering: pixelated;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #e77;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #faa;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/*** NES.css overrides ***/
|
||||
|
||||
.nes-container.is-rounded.is-dark {
|
||||
border-image-slice: 9 9 9 9 fill;
|
||||
border-image-source: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAphgAAKYYBIuzfjAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAABgSURBVEiJY2AYBQQAIzGK/v///x+rZkZGgvqZSHURqYDmFrDgkkAOFikpZYJqcAXX0A8iFG8REyy4wLNndxGGIgXX0A+iUQsIApxlCTEpClfKQQbDOIiQwcgurkcBQQAARlMedugABy8AAAAASUVORK5CYII=');
|
||||
background-color: transparent;
|
||||
border-image-repeat: stretch;
|
||||
}
|
||||
|
||||
.nes-container.is-fake-rounded.is-dark::after {
|
||||
background: none;
|
||||
}
|
||||
|
||||
/*** screens ***/
|
||||
|
||||
.screen {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
position: absolute;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.modal {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
bottom: 0px;
|
||||
right: 0px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #222323;
|
||||
}
|
||||
|
||||
#title {
|
||||
background-image: url(bg.png);
|
||||
background-size: cover;
|
||||
animation: 20s para infinite ease;
|
||||
}
|
||||
|
||||
@keyframes para {
|
||||
0% {
|
||||
background-position: 0px 0%;
|
||||
}
|
||||
50% {
|
||||
background-position: 0px 80px;
|
||||
}
|
||||
100% {
|
||||
background-position: 0px 0px;
|
||||
}
|
||||
}
|
||||
|
||||
#plate {
|
||||
display: flex;
|
||||
animation: 2s plate-fade;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
#plate > div {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
text-align: left;
|
||||
padding: 40px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
@keyframes plate-fade {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
25% {
|
||||
opacity: 0;
|
||||
}
|
||||
50% {
|
||||
opacity: 1;
|
||||
}
|
||||
75% {
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
#plate > div > * + * {
|
||||
margin-left: 32px;
|
||||
margin-right: 0px;
|
||||
}
|
||||
|
||||
@media (max-width: 700px), (max-height: 820px) {
|
||||
#plate > div {
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#plate > div > * + * {
|
||||
margin-left: 0px;
|
||||
margin-right: 0px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
#game-title {
|
||||
margin-bottom: 0px;
|
||||
width: 900px;
|
||||
max-width: 98%;
|
||||
}
|
||||
|
||||
@media (min-width: 700px) and (max-height: 820px) {
|
||||
#game-title {
|
||||
width: 900px;
|
||||
max-width: 98%;
|
||||
max-height: 35vh;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-height: 600px) {
|
||||
#game-title {
|
||||
width: 500px;
|
||||
}
|
||||
}
|
||||
|
||||
.game-title-animation {
|
||||
animation: 2s zoomInDown;
|
||||
}
|
||||
|
||||
/* https://github.com/animate-css/animate.css/blob/master/animate.css */
|
||||
@keyframes zoomInDown {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: scale3d(0.1, 0.1, 0.1) translate3d(0, -1000px, 0);
|
||||
animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19);
|
||||
}
|
||||
|
||||
60% {
|
||||
opacity: 1;
|
||||
transform: scale3d(0.475, 0.475, 0.475) translate3d(0, 60px, 0);
|
||||
animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1);
|
||||
}
|
||||
}
|
||||
|
||||
#options {
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
max-width: 90%;
|
||||
}
|
||||
|
||||
#logo {
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
#menu {
|
||||
width: 400px;
|
||||
max-width: 100%;
|
||||
margin-bottom: 64px;
|
||||
padding: 32px;
|
||||
}
|
||||
|
||||
#menu label {
|
||||
margin-left: -1em;
|
||||
padding-top: 0.5em;
|
||||
padding-bottom: 0.5em;
|
||||
}
|
||||
|
||||
.modal > * {
|
||||
max-width: 90%;
|
||||
width: 400px;
|
||||
margin: 50px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media (max-width: 700px), (max-height: 820px) {
|
||||
.modal > * {
|
||||
margin: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
#instructions div > p {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
#settings div > p {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
#credits ul {
|
||||
list-style: "> ";
|
||||
padding-left: 2em;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
#credits ul li {
|
||||
margin: 0.5em 0px;
|
||||
}
|
||||
|
||||
.sprite {
|
||||
display: block;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
image-rendering: optimizeSpeed;
|
||||
image-rendering: crisp-edges;
|
||||
image-rendering: -moz-crisp-edges;
|
||||
image-rendering: -o-crisp-edges;
|
||||
image-rendering: -webkit-optimize-contrast;
|
||||
-ms-interpolation-mode: nearest-neighbor;
|
||||
image-rendering: pixelated;
|
||||
transform: scale(8);
|
||||
background-image: url("colored_tilemap_packed.png");
|
||||
margin: 80px auto;
|
||||
}
|
||||
|
||||
.free {
|
||||
position: absolute;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
|
||||
.tomb {
|
||||
background-position: -72px -56px;
|
||||
}
|
||||
|
||||
.ghost {
|
||||
background-position: -72px -8px;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
.empty {
|
||||
background-position: -8px -48px;
|
||||
}
|
||||
|
||||
.float-up {
|
||||
animation: float-up 2s linear forwards;
|
||||
}
|
||||
|
||||
@keyframes float-up {
|
||||
from {
|
||||
transform: scale(1) translate(0px, 0px);
|
||||
opacity: 1;
|
||||
}
|
||||
to {
|
||||
transform: scale(3) translate(0px, -20px);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.grow-fade {
|
||||
animation: grow-fade 2s linear;
|
||||
}
|
||||
|
||||
@keyframes grow-fade {
|
||||
from {
|
||||
transform: translate(0px, 0px) scale(8);
|
||||
opacity: 0.5;
|
||||
}
|
||||
to {
|
||||
transform: translate(0px, 0px) scale(16);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
#play {
|
||||
width: 400px;
|
||||
max-width: 90%;
|
||||
}
|
||||
|
||||
#win {
|
||||
background: url(01coin.gif);
|
||||
background-size: 20%;
|
||||
}
|
||||
|
||||
/*** HUD ***/
|
||||
|
||||
#hud {
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
width: 600px;
|
||||
max-width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-evenly;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
#message {
|
||||
position: absolute;
|
||||
top: 24px;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#message .hit {
|
||||
color: #C01256;
|
||||
}
|
||||
|
||||
#message .miss {
|
||||
color: #FFB570;
|
||||
}
|
||||
|
||||
#inventory {
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
left: 0px;
|
||||
}
|
||||
|
||||
#inventory .sprite {
|
||||
transform: scale(3);
|
||||
display: inline-block;
|
||||
margin: 1em 2em 1em 1em;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
#inventory li {
|
||||
margin: 1em 0px;
|
||||
}
|
||||
|
||||
#inventory ul {
|
||||
list-style-type: none;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
#inventory > div {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (max-width: 750px) {
|
||||
#inventory {
|
||||
bottom: 72px;
|
||||
}
|
||||
|
||||
#hud {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
#arrows {
|
||||
display: none;
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
bottom: 0px;
|
||||
}
|
||||
|
||||
#arrows > * {
|
||||
float: left;
|
||||
font-size: 16px;
|
||||
bottom: 0px;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
#arrows > * > span {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#btn-left {
|
||||
position: absolute;
|
||||
right: 7em;
|
||||
}
|
||||
|
||||
#btn-right {
|
||||
position: absolute;
|
||||
right: 0em;
|
||||
}
|
||||
|
||||
#btn-up {
|
||||
transform: rotate(90deg);
|
||||
position: absolute;
|
||||
right: 3.5em;
|
||||
margin-bottom: 3.75em;
|
||||
}
|
||||
|
||||
#btn-down {
|
||||
transform: rotate(90deg);
|
||||
position: absolute;
|
||||
right: 3.5em;
|
||||
}
|
||||
|
||||
#btn-skip {
|
||||
position: absolute;
|
||||
right: 0em;
|
||||
margin-bottom: 3.75em;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
#arrows > * {
|
||||
bottom: 72px;
|
||||
}
|
||||
}
|
||||
|
||||
/*** CSS animations ***/
|
||||
|
||||
.fade-in {
|
||||
animation: fade-in 0.8s;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@keyframes fade-in {
|
||||
from{opacity:0} to{opacity:1}
|
||||
}
|
||||
|
||||
.hide {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.show {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.fade-out {
|
||||
display: flex;
|
||||
opacity: 1;
|
||||
animation: fade-out 3s forwards;
|
||||
}
|
||||
|
||||
@keyframes fade-out {
|
||||
from{opacity:1; display: flex;} 50%{opacity:1; display: flex;} to{opacity:0; display: none;}
|
||||
}
|
||||
947
views/game.js
Normal file
947
views/game.js
Normal file
|
|
@ -0,0 +1,947 @@
|
|||
(function (w) {
|
||||
let count = 1;
|
||||
// game title
|
||||
const gametitle = "The Math Wizard";
|
||||
|
||||
/*****************
|
||||
*** resources ***
|
||||
*****************/
|
||||
|
||||
// This tileset is from kenney.nl
|
||||
// It's the "microrogue" tileset
|
||||
|
||||
const tileSet = document.createElement("img");
|
||||
tileSet.src = "colored_tilemap_packed.png";
|
||||
|
||||
const tileOptions = {
|
||||
layout: "tile",
|
||||
bg: "transparent",
|
||||
tileWidth: 8,
|
||||
tileHeight: 8,
|
||||
tileSet: tileSet,
|
||||
tileMap: {
|
||||
"@": [40, 0], // player
|
||||
".": [32, 32], // floor
|
||||
"M": [88, 0], // monster
|
||||
"*": [72, 24], // treasure chest
|
||||
"g": [64, 40], // gold
|
||||
"x": [56, 32], // axe
|
||||
"p": [56, 64], // potion
|
||||
"a": [40, 32], // tree 1
|
||||
"b": [32, 40], // tree 2
|
||||
"c": [40, 40], // tree 3
|
||||
"d": [48, 40], // tree 4
|
||||
"e": [56, 40], // tree 5
|
||||
"T": [72, 56], // tombstone
|
||||
"╔": [0, 72], // room corner
|
||||
"╗": [24, 72], // room corner
|
||||
"╝": [72, 72], // room corner
|
||||
"╚": [48, 72], // room corner
|
||||
"═": [8, 72], // room edge
|
||||
"║": [32, 72], // room edge
|
||||
"o": [40, 72], // room corner
|
||||
"D": [16, 16], //Door to win
|
||||
"s": [32, 24], //stairs to next Stage
|
||||
},
|
||||
width: 25,
|
||||
height: 40,
|
||||
};
|
||||
|
||||
const usePointer = true;
|
||||
const useArrows = true;
|
||||
const touchOffsetY = -20; // move the center by this much
|
||||
const scaleMobile = 4; // scale mobile screens by this much
|
||||
const scaleMonitor = 6; // scale computer screens by this much
|
||||
const turnLengthMS = 200; // shortest time between turns
|
||||
|
||||
// these map tiles are walkable
|
||||
const walkable = [".", "*", "g", "D", "s"];
|
||||
|
||||
// these map tiles should not be replaced by room edges
|
||||
const noreplace = walkable.concat(["M", "╔", "╗", "╚", "╝", "═", "║"]);
|
||||
|
||||
// These sound effects are generated using sfxr.me
|
||||
|
||||
const sfx = {
|
||||
rubber:
|
||||
"5EoyNVaezhPnpFZjpkcJkF8FNCioPncLoztbSHU4u9wDQ8W3P7puffRWvGMnrLRdHa61kGcwhZK3RdoDRitmtwn4JjrQsZCZBmDQgkP5uGUGk863wbpRi1xdA",
|
||||
step: "34T6PkwiBPcxMGrK7aegATo5WTMWoP17BTc6pwXbwqRvndwRjGYXx6rG758rLSU5suu35ZTkRCs1K2NAqyrTZbiJUHQmra9qvbBrSdbBbJ7JvmyBFVDo6eiVD",
|
||||
choice:
|
||||
"34T6PkzXyyB6jHiwFztCFWEWsogkzrhzAH3FH2d97BCuFhqmZgfuXG3xtz8YYSKMzn95yyX8xZXJyesKmpcjpEL3dPP5h2e8mt5MmhExAksyqZyqgavBgsWMd",
|
||||
hide: "34T6PkzXyyB6jHiwFztCFWEniygA1GJtjsQuGxcd38JLDquhRqTB28dQgigseMjQSjSY14Z3aBmAtzz9KWcJZ2o9S1oCcgqQY4dxTAXikS7qCs3QJ3KuWJUyD",
|
||||
empty:
|
||||
"111112RrwhZ2Q7NGcdAP21KUHHKNQa3AhmK4Xea8mbiXfzkxr9aX41M8XYt5xYaaLeo9iZdUKUVL3u2N6XASue2wPv2wCCDy6W6TeFiUjk3dXSzFcBY7kTAM",
|
||||
hit: "34T6Pks4nddGzchAFWpSTRAKitwuQsfX8bfzRpJx5eDR7NSqxeeLMEkLjcuwvTCDS1ve7amXBg4eipzDdgKWoYnJBsQVESZh2X1DFV2GWybY5bAihB2EdHsbd",
|
||||
miss: "8R25jogvbp3Qy6A4GTPxRP4aT2SywwsAgoJ2pKmxUFMExgNashjgd311MnmZ2ThwrPQz71LA53QCfFmYQLHaXo6SocUv4zcfNAU5SFocZnoQSDCovnjpioNz3",
|
||||
win: "34T6Pkv34QJsqDqEa8aV4iwF2LnASMc3683oFUPKZic6kVUHvwjUQi6rz8qNRUHRs34cu37P5iQzz2AzipW3DHMoG5h4BZgDmZnyLhsXgPKsq2r4Fb2eBFVuR",
|
||||
lose: "7BMHBGHKKnn7bgcmGprqiBmpuRaTytcd4JS9eRNDzUTRuQy8BTBzs5g8XzS7rrp4C9cNeSaqAtWR9qdvXvtnWVTmTC8GXgDuCXD2KyHJNXzfUahbZrce8ibuy",
|
||||
kill: "7BMHBGKMhg8NZkxKcJxNfTWXKtMPiZVNsLR4aPEAghCSpz5ZxpjS5k4j4ZQpJ65UZnHSr4R2d7ALCHJe41pAS2ZPjauM7SveudhDGAxw2dhXpiNwEhG8xUYkX",
|
||||
};
|
||||
|
||||
for (let s in sfx) {
|
||||
sfx[s] = new SoundEffect(sfx[s]).generate().getAudio();
|
||||
}
|
||||
|
||||
const keyMap = {
|
||||
38: 0,
|
||||
33: 1,
|
||||
39: 2,
|
||||
34: 3,
|
||||
40: 4,
|
||||
35: 5,
|
||||
37: 6,
|
||||
36: 7,
|
||||
};
|
||||
|
||||
/*****************
|
||||
*** game code ***
|
||||
*****************/
|
||||
|
||||
// based on the original tutorial by Ondřej Žára
|
||||
// www.roguebasin.com/index.php?title=Rot.js_tutorial,_part_1
|
||||
|
||||
const Game = {
|
||||
// this is the ROT.js display handler
|
||||
display: null,
|
||||
// this is our map data
|
||||
map: {},
|
||||
// map of all items
|
||||
items: {},
|
||||
// reference to the ROT.js engine which
|
||||
// manages stuff like scheduling
|
||||
engine: null,
|
||||
// schedules events in the game for ROT.js
|
||||
scheduler: null,
|
||||
// reference to the player object
|
||||
player: null,
|
||||
// reference to the game monsters array
|
||||
monsters: null,
|
||||
door: null,
|
||||
// arrow handler
|
||||
lastArrow: null, // arrow keys held
|
||||
arrowInterval: null, // arrow key repeat
|
||||
arrowListener: null, // registered listener for arrow event
|
||||
// clean up this game instance
|
||||
cleanup: cleanup,
|
||||
playerAllowedToMove: true,
|
||||
};
|
||||
|
||||
// this gets called by the menu system
|
||||
// to launch the actual game
|
||||
function init(game) {
|
||||
game.map = {};
|
||||
game.items = {};
|
||||
// first create a ROT.js display manager
|
||||
game.display = new ROT.Display(tileOptions);
|
||||
resetCanvas(game.display.getContainer());
|
||||
|
||||
generateMap(game, count);
|
||||
|
||||
// let ROT.js schedule the player and monster entities
|
||||
game.scheduler = new ROT.Scheduler.Simple();
|
||||
game.scheduler.add(game.player, true);
|
||||
game.monsters.map((m) => game.scheduler.add(m, true));
|
||||
|
||||
// render the stats hud at the bottom of the screen
|
||||
renderStats(game.player.stats);
|
||||
|
||||
// kick everything off
|
||||
game.engine = new ROT.Engine(game.scheduler);
|
||||
game.engine.start();
|
||||
count = 1;
|
||||
}
|
||||
|
||||
function nextStage(game, stage, stats) {
|
||||
game.map = {};
|
||||
game.items = {};
|
||||
game.display = new ROT.Display(tileOptions);
|
||||
resetCanvas(game.display.getContainer());
|
||||
if (game.engine) {
|
||||
game.scheduler.clear();
|
||||
game.scheduler = null;
|
||||
game.monsters = null;
|
||||
game.door = null;
|
||||
game.stairs = null;
|
||||
};
|
||||
generateMap(game, stage);
|
||||
|
||||
// let ROT.js schedule the player and monster entities
|
||||
game.scheduler = new ROT.Scheduler.Simple();
|
||||
game.scheduler.add(game.player, true);
|
||||
game.monsters.map((m) => game.scheduler.add(m, true));
|
||||
|
||||
// render the stats hud at the bottom of the screen
|
||||
game.player.stats = stats;
|
||||
renderStats(game.player.stats);
|
||||
|
||||
// kick everything off
|
||||
game.engine = new ROT.Engine(game.scheduler);
|
||||
game.engine.start();;
|
||||
}
|
||||
|
||||
// this gets called at the end of the game when we want
|
||||
// to exit back out and clean everything up to display
|
||||
// the menu and get ready for next round
|
||||
function destroy(game) {
|
||||
// remove all listening event handlers
|
||||
removeListeners(game);
|
||||
|
||||
// tear everything down
|
||||
if (game.engine) {
|
||||
game.engine.lock();
|
||||
game.display = null;
|
||||
game.map = {};
|
||||
game.items = {};
|
||||
game.engine = null;
|
||||
game.scheduler.clear();
|
||||
game.scheduler = null;
|
||||
game.player = null;
|
||||
game.monsters = null;
|
||||
game.door = null;
|
||||
game.stairs = null;
|
||||
}
|
||||
|
||||
// hide the toast message
|
||||
hideToast(true);
|
||||
// close out the game screen and show the title
|
||||
showScreen("title");
|
||||
}
|
||||
|
||||
// this generates the game map
|
||||
function generateMap(game, stage) {
|
||||
const digger = new ROT.Map.Digger(tileOptions.width, tileOptions.height);
|
||||
// list of floor tiles that can be walked on
|
||||
const freeCells = [];
|
||||
// list of non-floor tiles that can't be traversed
|
||||
const zeroCells = [];
|
||||
|
||||
const digCallback = function (x, y, value) {
|
||||
const key = x + "," + y;
|
||||
if (value) {
|
||||
zeroCells.push(key);
|
||||
} else {
|
||||
game.map[key] = ".";
|
||||
freeCells.push(key);
|
||||
}
|
||||
};
|
||||
digger.create(digCallback.bind(game));
|
||||
|
||||
generateItems(game, freeCells);
|
||||
generateScenery(game.map, zeroCells);
|
||||
generateRooms(game.map, digger);
|
||||
|
||||
game.player = createBeing(makePlayer, freeCells);
|
||||
game.monsters = []
|
||||
for ( var i= 0; i<= stage; i++) {
|
||||
game.monsters.push(createBeing(makeMonster, freeCells));
|
||||
}
|
||||
|
||||
// draw the map and items
|
||||
for (let key in game.map) {
|
||||
drawTile(game, key);
|
||||
}
|
||||
|
||||
rescale(game.player._x, game.player._y, game);
|
||||
}
|
||||
|
||||
function generateItems(game, freeCells) {
|
||||
for (let i = 0; i < 15; i++) {
|
||||
const key = takeFreeCell(freeCells);
|
||||
if (!i) {
|
||||
if(count < 5) {
|
||||
game.stairs = key;
|
||||
game.items[key] = "s";
|
||||
} else {
|
||||
game.door = key;
|
||||
game.items[key] = "D";
|
||||
}
|
||||
} else {
|
||||
game.items[key] = ROT.RNG.getItem(["g"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function takeFreeCell(freeCells) {
|
||||
const index = Math.floor(ROT.RNG.getUniform() * freeCells.length);
|
||||
const key = freeCells.splice(index, 1)[0];
|
||||
return key;
|
||||
}
|
||||
|
||||
function posFromKey(key) {
|
||||
const parts = key.split(",");
|
||||
const x = parseInt(parts[0]);
|
||||
const y = parseInt(parts[1]);
|
||||
return [x, y];
|
||||
}
|
||||
|
||||
function generateScenery(map, freeCells) {
|
||||
for (let i = 0; i < 100; i++) {
|
||||
if (freeCells.length) {
|
||||
const key = takeFreeCell(freeCells);
|
||||
map[key] = ROT.RNG.getItem("abcde");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function generateRooms(map, mapgen) {
|
||||
const rooms = mapgen.getRooms();
|
||||
for (let rm = 0; rm < rooms.length; rm++) {
|
||||
const room = rooms[rm];
|
||||
|
||||
const l = room.getLeft() - 1;
|
||||
const r = room.getRight() + 1;
|
||||
const t = room.getTop() - 1;
|
||||
const b = room.getBottom() + 1;
|
||||
|
||||
map[l + "," + t] = "╔";
|
||||
map[r + "," + t] = "╗";
|
||||
map[l + "," + b] = "╚";
|
||||
map[r + "," + b] = "╝";
|
||||
|
||||
for (let i = room.getLeft(); i <= room.getRight(); i++) {
|
||||
const j = i + "," + t;
|
||||
const k = i + "," + b;
|
||||
if (noreplace.indexOf(map[j]) == -1) {
|
||||
map[j] = "═";
|
||||
}
|
||||
if (noreplace.indexOf(map[k]) == -1) {
|
||||
map[k] = "═";
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = room.getTop(); i <= room.getBottom(); i++) {
|
||||
const j = l + "," + i;
|
||||
const k = r + "," + i;
|
||||
if (noreplace.indexOf(map[j]) == -1) {
|
||||
map[j] = "║";
|
||||
}
|
||||
if (noreplace.indexOf(map[k]) == -1) {
|
||||
map[k] = "║";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function drawTile(game, key, ignore) {
|
||||
const map = game.map;
|
||||
if (map[key]) {
|
||||
const parts = posFromKey(key);
|
||||
const monster = monsterAt(parts[0], parts[1]);
|
||||
const player = playerAt(parts[0], parts[1]);
|
||||
const display = game.display;
|
||||
const items = game.items;
|
||||
const draw = [map[key], items[key]];
|
||||
draw.push(monster && monster != ignore ? monster.character : null);
|
||||
draw.push(player && player != ignore ? player.character : null);
|
||||
display.draw(
|
||||
parts[0],
|
||||
parts[1],
|
||||
draw.filter((i) => i)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// both the player and monster initial position is set
|
||||
function createBeing(what, freeCells) {
|
||||
const key = takeFreeCell(freeCells);
|
||||
const pos = posFromKey(key);
|
||||
const being = what(pos[0], pos[1]);
|
||||
return being;
|
||||
}
|
||||
|
||||
/******************
|
||||
*** the player ***
|
||||
******************/
|
||||
|
||||
// creates a player object with position, and stats
|
||||
function makePlayer(x, y) {
|
||||
return {
|
||||
// player's position
|
||||
_x: x,
|
||||
_y: y,
|
||||
character: "@",
|
||||
name: "you",
|
||||
// the player's stats
|
||||
stats: { hp: 10, xp: 0, gold: 0 },
|
||||
// the ROT.js scheduler calls this method when it is time
|
||||
// for the player to act
|
||||
act: () => {
|
||||
Game.engine.lock();
|
||||
if (!Game["arrowListener"]) {
|
||||
document.addEventListener("arrow", arrowEventHandler);
|
||||
Game.arrowListener = true;
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// this method gets called by the `movePlayer` function
|
||||
function checkItem(entity) {
|
||||
const key = entity._x + "," + entity._y;
|
||||
if (key == Game.door) {
|
||||
if(count < 5) {
|
||||
nextStage(Game, ++count, Game.player.stats);
|
||||
} else {
|
||||
win();
|
||||
}
|
||||
} else if (key == Game.stairs) {
|
||||
nextStage(Game, ++count, Game.player.stats);
|
||||
}else if (Game.items[key] == "g") {
|
||||
Game.player.stats.gold += 1;
|
||||
renderStats(Game.player.stats);
|
||||
toast("You found gold!");
|
||||
sfx["win"].play();
|
||||
delete Game.items[key];
|
||||
}
|
||||
drawTile(Game, key);
|
||||
}
|
||||
|
||||
function movePlayer(dir) {
|
||||
const p = Game.player;
|
||||
return movePlayerTo(p._x + dir[0], p._y + dir[1]);
|
||||
}
|
||||
|
||||
function movePlayerTo(x, y) {
|
||||
const p = Game.player;
|
||||
|
||||
const newKey = x + "," + y;
|
||||
if (walkable.indexOf(Game.map[newKey]) == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
// check if we've hit the monster
|
||||
const hitMonster = monsterAt(x, y);
|
||||
if (hitMonster) {
|
||||
//combat(p, hitMonster);
|
||||
setTimeout(function () {
|
||||
Game.engine.unlock();
|
||||
}, 250);
|
||||
} else {
|
||||
hideToast();
|
||||
|
||||
drawTile(Game, p._x + "," + p._y, p);
|
||||
|
||||
// update the player's coordinates
|
||||
p._x = x;
|
||||
p._y = y;
|
||||
|
||||
// re-draw the player
|
||||
for (let key in Game.map) {
|
||||
drawTile(Game, key);
|
||||
}
|
||||
// re-locate the game screen to center the player
|
||||
rescale(x, y, Game);
|
||||
window.removeEventListener("arrow", arrowEventHandler);
|
||||
Game.engine.unlock();
|
||||
sfx["step"].play();
|
||||
// check if the player stepped on an item
|
||||
checkItem(p);
|
||||
}
|
||||
}
|
||||
|
||||
/*******************
|
||||
*** The monster ***
|
||||
*******************/
|
||||
|
||||
// basic ROT.js entity with position and stats
|
||||
function makeMonster(x, y) {
|
||||
return {
|
||||
// monster position
|
||||
_x: x,
|
||||
_y: y,
|
||||
character: "M",
|
||||
name: "Orc",
|
||||
stats: { hp: 3 },
|
||||
// called by the ROT.js scheduler
|
||||
act: monsterAct,
|
||||
};
|
||||
}
|
||||
|
||||
function monsterAct() {
|
||||
const m = this;
|
||||
const p = Game.player;
|
||||
const map = Game.map;
|
||||
const display = Game.display;
|
||||
|
||||
const passableCallback = function (x, y) {
|
||||
return walkable.indexOf(map[x + "," + y]) != -1;
|
||||
};
|
||||
const astar = new ROT.Path.AStar(p._x, p._y, passableCallback, {
|
||||
topology: 4,
|
||||
});
|
||||
const path = [];
|
||||
const pathCallback = function (x, y) {
|
||||
path.push([x, y]);
|
||||
};
|
||||
astar.compute(m._x, m._y, pathCallback);
|
||||
|
||||
path.shift();
|
||||
if (path.length <= 1) {
|
||||
Game.playerAllowedToMove = false;
|
||||
Game.engine.lock();
|
||||
combat(m, p);
|
||||
} else {
|
||||
drawTile(Game, m._x + "," + m._y, m);
|
||||
m._x = path[0][0];
|
||||
m._y = path[0][1];
|
||||
drawTile(Game, m._x + "," + m._y);
|
||||
}
|
||||
}
|
||||
|
||||
function monsterAt(x, y) {
|
||||
if (Game.monsters && Game.monsters.length) {
|
||||
for (let mi = 0; mi < Game.monsters.length; mi++) {
|
||||
const m = Game.monsters[mi];
|
||||
if (m && m._x == x && m._y == y) {
|
||||
return m;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function playerAt(x, y) {
|
||||
return Game.player && Game.player._x == x && Game.player._y == y
|
||||
? Game.player
|
||||
: null;
|
||||
}
|
||||
|
||||
// if the monster is dead remove it from the game
|
||||
function checkDeath(m) {
|
||||
if (m.stats.hp <= 0) {
|
||||
if (m == Game.player) {
|
||||
toast("You died!");
|
||||
lose();
|
||||
} else {
|
||||
const key = m._x + "," + m._y;
|
||||
removeMonster(m);
|
||||
sfx["kill"].play();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// remove a monster from the game
|
||||
function removeMonster(m) {
|
||||
const key = m._x + "," + m._y;
|
||||
Game.scheduler.remove(m);
|
||||
Game.monsters = Game.monsters.filter((mx) => mx != m);
|
||||
drawTile(Game, key);
|
||||
}
|
||||
|
||||
/******************************
|
||||
*** combat/win/lose events ***
|
||||
******************************/
|
||||
// this is how the player fights a monster
|
||||
function checkSolution(solution, answer, hitter, receiver) {
|
||||
console.log("Click: " + solution + " Antwort: " + answer);
|
||||
if (solution == answer) {
|
||||
hitter.stats.hp -= 1;
|
||||
sfx["hit"].play();
|
||||
if (checkDeath(hitter)) {
|
||||
Game.player.stats.xp += 1;
|
||||
showScreen("game");
|
||||
Game.playerAllowedToMove = true;
|
||||
Game.engine.unlock();
|
||||
} else {
|
||||
combat(hitter, receiver);
|
||||
}
|
||||
checkDeath(hitter);
|
||||
} else {
|
||||
sfx["miss"].play();
|
||||
//showScreen("game");
|
||||
//Game.playerAllowedToMove = true;
|
||||
//Game.engine.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
function setupButtons(answerValue, hitter, receiver) {
|
||||
const randomValue = (min, max) =>
|
||||
Math.floor(Math.random() * (max - min)) + min;
|
||||
let randomVar = randomValue(1, 4);
|
||||
if (randomVar == 1) {
|
||||
document.getElementById("answer1").innerHTML = `${answerValue}`;
|
||||
document.getElementById("answer2").innerHTML = `${
|
||||
answerValue + randomValue(1, 4)
|
||||
}`;
|
||||
document.getElementById("answer3").innerHTML = `${
|
||||
answerValue - randomValue(1, 4)
|
||||
}`;
|
||||
} else if (randomVar == 2) {
|
||||
document.getElementById("answer1").innerHTML = `${
|
||||
answerValue + randomValue(1, 4)
|
||||
}`;
|
||||
document.getElementById("answer2").innerHTML = `${answerValue}`;
|
||||
document.getElementById("answer3").innerHTML = `${
|
||||
answerValue - randomValue(1, 4)
|
||||
}`;
|
||||
} else {
|
||||
document.getElementById("answer1").innerHTML = `${
|
||||
answerValue - randomValue(1, 4)
|
||||
}`;
|
||||
document.getElementById("answer2").innerHTML = `${
|
||||
answerValue + randomValue(1, 4)
|
||||
}`;
|
||||
document.getElementById("answer3").innerHTML = `${answerValue}`;
|
||||
}
|
||||
document.getElementById("answer1").addEventListener("click", async() => {
|
||||
checkSolution(document.getElementById("answer1").innerText, answerValue, hitter, receiver);
|
||||
}, {once: true});
|
||||
document.getElementById("answer2").addEventListener("click", async() => {
|
||||
checkSolution(document.getElementById("answer2").innerText, answerValue, hitter, receiver);
|
||||
}, {once: true});
|
||||
document.getElementById("answer3").addEventListener("click", async() => {
|
||||
checkSolution(document.getElementById("answer3").innerText, answerValue, hitter, receiver);
|
||||
}, {once: true});
|
||||
}
|
||||
|
||||
function combat(hitter, receiver) {
|
||||
const randomValue = (min, max) =>
|
||||
Math.floor(Math.random() * (max - min)) + min;
|
||||
let [num1, num2] = [randomValue(1, 10), randomValue(1, 10)];
|
||||
const answerValue = eval(`${num1} * ${num2}`);
|
||||
document.getElementById("question").innerHTML = `${num1} * ${num2} = ? `;
|
||||
setupButtons(answerValue, hitter, receiver);
|
||||
showScreen("combat");
|
||||
checkDeath(receiver);
|
||||
renderStats(Game.player.stats);
|
||||
}
|
||||
|
||||
// this gets called when the player wins the game
|
||||
function win() {
|
||||
Game.engine.lock();
|
||||
for (let i = 0; i < 5; i++) {
|
||||
setTimeout(function () {
|
||||
sfx["win"].play();
|
||||
}, 100 * i);
|
||||
}
|
||||
// set our stats for the end screen
|
||||
setEndScreenValues(Game.player.stats.xp, Game.player.stats.gold);
|
||||
// tear down the game
|
||||
destroy(Game);
|
||||
showScreen("win");
|
||||
}
|
||||
|
||||
// this gets called when the player loses the game
|
||||
function lose() {
|
||||
Game.engine.lock();
|
||||
// change the player into a tombstone tile
|
||||
const p = Game.player;
|
||||
p.character = "T";
|
||||
drawTile(Game, p._x + "," + p._y);
|
||||
const ghost = createGhost([p._x, p._y]);
|
||||
removeListeners(Game);
|
||||
sfx["lose"].play();
|
||||
setTimeout(function () {
|
||||
setEndScreenValues(Game.player.stats.xp, Game.player.stats.gold);
|
||||
// tear down the game
|
||||
destroy(Game);
|
||||
showScreen("lose");
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
/************************************
|
||||
*** graphics, UI & browser utils ***
|
||||
************************************/
|
||||
|
||||
const clickevt = !!("ontouchstart" in window) ? "touchstart" : "click";
|
||||
|
||||
const $ = document.querySelector.bind(document);
|
||||
const $$ = document.querySelectorAll.bind(document);
|
||||
NodeList.prototype.forEach = Array.prototype.forEach;
|
||||
|
||||
// this code resets the ROT.js display canvas
|
||||
function resetCanvas(el) {
|
||||
$("#canvas").innerHTML = "";
|
||||
$("#canvas").appendChild(el);
|
||||
window.onkeydown = keyHandler;
|
||||
window.onkeyup = arrowStop;
|
||||
if (useArrows) {
|
||||
document.ontouchend = arrowStop;
|
||||
}
|
||||
showScreen("game");
|
||||
}
|
||||
|
||||
function rescale(x, y, game) {
|
||||
const c = $("canvas");
|
||||
const scale = window.innerWidth < 600 ? scaleMobile : scaleMonitor;
|
||||
const offset = game.touchScreen ? touchOffsetY : 0;
|
||||
const tw =
|
||||
x * -tileOptions.tileWidth +
|
||||
(tileOptions.width * tileOptions.tileWidth) / 2 +
|
||||
-4;
|
||||
const th =
|
||||
y * -tileOptions.tileHeight +
|
||||
(tileOptions.height * tileOptions.tileHeight) / 2 +
|
||||
offset;
|
||||
if (canvas) {
|
||||
canvas.style.transition = "transform 0.5s ease-out 0s";
|
||||
if (game.display) {
|
||||
game.display
|
||||
.getContainer()
|
||||
.getContext("2d").imageSmoothingEnabled = false;
|
||||
}
|
||||
canvas.style.transform =
|
||||
"scale(" +
|
||||
scale +
|
||||
") " +
|
||||
"translate3d(" +
|
||||
Math.floor(tw) +
|
||||
"px," +
|
||||
Math.floor(th) +
|
||||
"px,0px)";
|
||||
}
|
||||
}
|
||||
|
||||
function removeListeners(game) {
|
||||
if (game.engine) {
|
||||
game.lastArrow = null;
|
||||
clearInterval(game.arrowInterval);
|
||||
game.arrowInterval = null;
|
||||
game.engine.lock();
|
||||
game.scheduler.clear();
|
||||
window.removeEventListener("arrow", arrowEventHandler);
|
||||
game.arrowListener = false;
|
||||
window.onkeydown = null;
|
||||
window.onkeyup = null;
|
||||
}
|
||||
}
|
||||
|
||||
// hides all screens and shows the requested screen
|
||||
function showScreen(which, ev) {
|
||||
ev && ev.preventDefault();
|
||||
const el = $("#" + which);
|
||||
const actionbutton = $("#" + which + ">.action");
|
||||
document.querySelectorAll(".screen").forEach(function (s) {
|
||||
s.classList.remove("show");
|
||||
s.classList.add("hide");
|
||||
});
|
||||
el.classList.remove("hide");
|
||||
el.classList.remove("show");
|
||||
void el.offsetHeight; // trigger CSS reflow
|
||||
el.classList.add("show");
|
||||
if (actionbutton) {
|
||||
actionbutton.focus();
|
||||
}
|
||||
}
|
||||
|
||||
// set the end-screen message
|
||||
function setEndScreenValues(xp, gold) {
|
||||
$$(".xp-stat").forEach((el) => (el.textContent = Math.floor(xp)));
|
||||
$$(".gold-stat").forEach((el) => (el.textContent = gold));
|
||||
}
|
||||
|
||||
// updates the stats listed at the bottom of the screen
|
||||
function renderStats(stats) {
|
||||
const st = $("#hud");
|
||||
st.innerHTML = "";
|
||||
for (let s in stats) {
|
||||
attach(st, el("span", {}, [s.toUpperCase() + ": " + stats[s]]));
|
||||
}
|
||||
}
|
||||
|
||||
// creates the ghost sprite when the player dies
|
||||
function createGhost(pos) {
|
||||
const tw = tileOptions.tileWidth;
|
||||
const th = tileOptions.tileHeight;
|
||||
const left = "left:" + pos[0] * tw + "px;";
|
||||
const top = "top:" + pos[1] * th + "px;";
|
||||
const ghost = el("div", {
|
||||
className: "sprite ghost free float-up",
|
||||
style: left + top,
|
||||
});
|
||||
ghost.onanimationend = function () {
|
||||
rmel(ghost);
|
||||
};
|
||||
return attach($("#canvas"), ghost);
|
||||
}
|
||||
|
||||
function battleMessage(messages) {
|
||||
const components = messages.reduce(function (msgs, m) {
|
||||
return msgs
|
||||
.concat(
|
||||
m.split(" ").map(function (p) {
|
||||
const match = p.match(/hit|miss/);
|
||||
return el("span", { className: match ? match[0] : "" }, [p, " "]);
|
||||
})
|
||||
)
|
||||
.concat(el("br", {}));
|
||||
}, []);
|
||||
return el("span", {}, components);
|
||||
}
|
||||
|
||||
function toast(message) {
|
||||
const m = $("#message");
|
||||
if (
|
||||
Game.scheduler._current == Game.player ||
|
||||
m.className.indexOf("show") == -1
|
||||
) {
|
||||
m.innerHTML = "";
|
||||
}
|
||||
m.classList.remove("fade-out");
|
||||
m.classList.add("show");
|
||||
if (typeof message == "string") {
|
||||
m.appendChild(el("span", {}, [message]));
|
||||
} else {
|
||||
m.appendChild(message);
|
||||
}
|
||||
}
|
||||
|
||||
function hideToast(instant) {
|
||||
const m = $("#message");
|
||||
if (instant) {
|
||||
m.classList.remove("show");
|
||||
m.classList.remove("fade-out");
|
||||
m.innerHTML = "";
|
||||
} else if (m.className.match("show")) {
|
||||
m.classList.remove("show");
|
||||
m.classList.add("fade-out");
|
||||
m.onanimationend = function () {
|
||||
m.classList.remove("fade-out");
|
||||
m.innerHTML = "";
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// create an HTML element
|
||||
function el(tag, attrs, children) {
|
||||
const node = document.createElement(tag);
|
||||
for (a in attrs) {
|
||||
node[a] = attrs[a];
|
||||
}
|
||||
(children || []).forEach(function (c) {
|
||||
if (typeof c == "string") {
|
||||
node.appendChild(document.createTextNode(c));
|
||||
} else {
|
||||
attach(node, c);
|
||||
}
|
||||
});
|
||||
return node;
|
||||
}
|
||||
|
||||
// add an HTML element to a parent node
|
||||
function attach(node, el) {
|
||||
node.appendChild(el);
|
||||
return el;
|
||||
}
|
||||
|
||||
// remove an element from the dom
|
||||
function rmel(node) {
|
||||
node.parentNode.removeChild(node);
|
||||
}
|
||||
|
||||
/*************************
|
||||
*** UI event handlers ***
|
||||
*************************/
|
||||
|
||||
function keyHandler(ev) {
|
||||
const code = ev.keyCode;
|
||||
if (code == 187 || code == 189) {
|
||||
ev.preventDefault();
|
||||
return;
|
||||
}
|
||||
if (code == 70 && ev.altKey && ev.ctrlKey && ev.shiftKey) {
|
||||
document.body.requestFullscreen();
|
||||
console.log("Full screen pressed.");
|
||||
return;
|
||||
}
|
||||
if (code == 73) {
|
||||
toggleInventory(ev, true);
|
||||
return;
|
||||
}
|
||||
if (code == 190) {
|
||||
Game.engine.unlock();
|
||||
return;
|
||||
} // skip turn
|
||||
if (!(code in keyMap)) {
|
||||
return;
|
||||
}
|
||||
const dir = ROT.DIRS[8][keyMap[code]];
|
||||
if (Game.display) {
|
||||
ev.preventDefault();
|
||||
}
|
||||
if(Game.playerAllowedToMove) {
|
||||
arrowStart(dir);
|
||||
}
|
||||
}
|
||||
|
||||
function arrowStart(dir) {
|
||||
const last = Game.lastArrow;
|
||||
Game.lastArrow = dir;
|
||||
if (!last) {
|
||||
document.dispatchEvent(new Event("arrow"));
|
||||
if (Game.arrowInterval) {
|
||||
clearInterval(Game.arrowInterval);
|
||||
}
|
||||
Game.arrowInterval = setInterval(function () {
|
||||
document.dispatchEvent(new Event("arrow"));
|
||||
}, turnLengthMS);
|
||||
}
|
||||
}
|
||||
|
||||
function arrowStop(ev) {
|
||||
clearInterval(Game.arrowInterval);
|
||||
Game.arrowInterval = null;
|
||||
Game.lastArrow = null;
|
||||
}
|
||||
|
||||
function arrowEventHandler() {
|
||||
if (Game.lastArrow) {
|
||||
movePlayer(Game.lastArrow);
|
||||
} else {
|
||||
arrowStop();
|
||||
}
|
||||
}
|
||||
|
||||
function startGame(ev) {
|
||||
showScreen("game");
|
||||
sfx["rubber"].play();
|
||||
init(Game);
|
||||
}
|
||||
|
||||
function handleMenuChange(which, ev) {
|
||||
ev.preventDefault();
|
||||
const choice = which.getAttribute("value");
|
||||
showScreen(choice);
|
||||
sfx["choice"].play();
|
||||
}
|
||||
|
||||
function hideModal(ev) {
|
||||
ev.preventDefault();
|
||||
showScreen("title");
|
||||
sfx["hide"].play();
|
||||
}
|
||||
|
||||
function cleanup() {
|
||||
destroy(Game);
|
||||
$("#play").removeEventListener(clickevt, startGame);
|
||||
}
|
||||
|
||||
/***************
|
||||
*** Startup ***
|
||||
***************/
|
||||
|
||||
// this code is called at load time and sets the game title
|
||||
document.querySelectorAll(".game-title-text").forEach(function (t) {
|
||||
t.textContent = gametitle;
|
||||
});
|
||||
|
||||
// listen for the start game button
|
||||
$("#play").addEventListener(clickevt, startGame);
|
||||
|
||||
if (w["rbb"]) {
|
||||
w["rbb"].cleanup();
|
||||
} else {
|
||||
$("#plate").addEventListener(
|
||||
"animationend",
|
||||
showScreen.bind(null, "title")
|
||||
);
|
||||
document.querySelectorAll("#options #menu input").forEach(function (el) {
|
||||
el.addEventListener("click", handleMenuChange.bind(null, el));
|
||||
});
|
||||
document.querySelectorAll(".modal button.action").forEach(function (el) {
|
||||
el.addEventListener(clickevt, hideModal);
|
||||
});
|
||||
}
|
||||
|
||||
w["rbb"] = Game;
|
||||
})(window);
|
||||
1583
views/game.view.php
Normal file
1583
views/game.view.php
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1,14 +1,96 @@
|
|||
<?php require ('partials/head.php') ?>
|
||||
<?php require ('partials/nav.php') ?>
|
||||
<?php require ('partials/banner.php') ?>
|
||||
<main>
|
||||
<div class="mx-auto max-w-7xl py-6 sm:px-6 lg:px-8">
|
||||
<!-- Replace with your content -->
|
||||
<p>Hello and Welcome.</p>
|
||||
<div class="px-4 py-6 sm:px-0">
|
||||
<div class="h-96 rounded-lg border-4 border-dashed border-gray-200"></div>
|
||||
<div class="isolate bg-white my-10">
|
||||
<main>
|
||||
<div class="relative px-6 lg:px-8 min-h-screen">
|
||||
<div class="mx-auto max-w-3xl pt-20 pb-32 sm:pt-48 sm:pb-40">
|
||||
<div>
|
||||
<div class="hidden sm:mb-8 sm:flex sm:justify-center">
|
||||
|
||||
</div>
|
||||
<div>
|
||||
<h1 class="text-4xl font-bold tracking-tight sm:text-center sm:text-6xl">Automatisiere spielerisch das 1x1</h1>
|
||||
<p class="mt-6 text-lg leading-8 text-gray-600 sm:text-center">Zeige was du kannst und kämpfe dich durch denn Dungeon.</p>
|
||||
<div class="mt-8 flex gap-x-4 sm:justify-center">
|
||||
<a href="/login" class="<?= urlIs("/login") ?> nes-btn is-primary">
|
||||
Los geht's
|
||||
<span class="text-indigo-200" aria-hidden="true">→</span>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="absolute inset-x-0 top-[calc(100%-13rem)] -z-10 transform-gpu overflow-hidden blur-3xl sm:top-[calc(100%-30rem)]">
|
||||
<svg class="relative left-[calc(50%+3rem)] h-[21.1875rem] max-w-none -translate-x-1/2 sm:left-[calc(50%+36rem)] sm:h-[42.375rem]" viewBox="0 0 1155 678" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill="url(#ecb5b0c9-546c-4772-8c71-4d3f06d544bc)" fill-opacity=".3" d="M317.219 518.975L203.852 678 0 438.341l317.219 80.634 204.172-286.402c1.307 132.337 45.083 346.658 209.733 145.248C936.936 126.058 882.053-94.234 1031.02 41.331c119.18 108.451 130.68 295.337 121.53 375.223L855 299l21.173 362.054-558.954-142.079z" />
|
||||
<defs>
|
||||
<linearGradient id="ecb5b0c9-546c-4772-8c71-4d3f06d544bc" x1="1155.49" x2="-78.208" y1=".177" y2="474.645" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#9089FC"></stop>
|
||||
<stop offset="1" stop-color="#FF80B5"></stop>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /End replace -->
|
||||
</div>
|
||||
<div class="bg-indigo-500 h-48">
|
||||
|
||||
</div>
|
||||
<div class="bg-white py-24 sm:py-32 lg:py-40">
|
||||
<div class="mx-auto max-w-7xl px-6 lg:px-8 bg-gray-50">
|
||||
<div class="sm:text-center">
|
||||
<p class="mt-2 text-3xl font-bold tracking-tight text-gray-900 sm:text-4xl">Der bessere Weg das 1x1 zu automatisieren.</p>
|
||||
</div>
|
||||
|
||||
<div class="mt-20 max-w-lg sm:mx-auto md:max-w-none">
|
||||
<div class="grid grid-cols-1 gap-y-16 md:grid-cols-2 md:gap-x-12 md:gap-y-16">
|
||||
<div class="relative flex flex-col gap-6 sm:flex-row md:flex-col lg:flex-row">
|
||||
<div class="flex h-12 w-12 items-center justify-center rounded-xl bg-indigo-500 text-white sm:shrink-0">
|
||||
<!-- Heroicon name: outline/globe-alt -->
|
||||
<svg class="h-8 w-8" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 21a9.004 9.004 0 008.716-6.747M12 21a9.004 9.004 0 01-8.716-6.747M12 21c2.485 0 4.5-4.03 4.5-9S14.485 3 12 3m0 18c-2.485 0-4.5-4.03-4.5-9S9.515 3 12 3m0 0a8.997 8.997 0 017.843 4.582M12 3a8.997 8.997 0 00-7.843 4.582m15.686 0A11.953 11.953 0 0112 10.5c-2.998 0-5.74-1.1-7.843-2.918m15.686 0A8.959 8.959 0 0121 12c0 .778-.099 1.533-.284 2.253m0 0A17.919 17.919 0 0112 16.5c-3.162 0-6.133-.815-8.716-2.247m0 0A9.015 9.015 0 013 12c0-1.605.42-3.113 1.157-4.418" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="sm:min-w-0 sm:flex-1">
|
||||
<p class="text-lg font-semibold leading-8 text-gray-900">Immer neue Level</p>
|
||||
<p class="mt-2 text-base leading-7 text-gray-600">Durch zufällig generierte Level wird dir nie langweilig.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="relative flex flex-col gap-6 sm:flex-row md:flex-col lg:flex-row">
|
||||
<div class="flex h-12 w-12 items-center justify-center rounded-xl bg-indigo-500 text-white sm:shrink-0">
|
||||
<!-- Heroicon name: outline/scale -->
|
||||
<svg class="h-8 w-8" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 3v17.25m0 0c-1.472 0-2.882.265-4.185.75M12 20.25c1.472 0 2.882.265 4.185.75M18.75 4.97A48.416 48.416 0 0012 4.5c-2.291 0-4.545.16-6.75.47m13.5 0c1.01.143 2.01.317 3 .52m-3-.52l2.62 10.726c.122.499-.106 1.028-.589 1.202a5.988 5.988 0 01-2.031.352 5.988 5.988 0 01-2.031-.352c-.483-.174-.711-.703-.59-1.202L18.75 4.971zm-16.5.52c.99-.203 1.99-.377 3-.52m0 0l2.62 10.726c.122.499-.106 1.028-.589 1.202a5.989 5.989 0 01-2.031.352 5.989 5.989 0 01-2.031-.352c-.483-.174-.711-.703-.59-1.202L5.25 4.971z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="sm:min-w-0 sm:flex-1">
|
||||
<p class="text-lg font-semibold leading-8 text-gray-900">Angepasste Schwierigkeit</p>
|
||||
<p class="mt-2 text-base leading-7 text-gray-600">Die Aufgabe passen sich deinen aktuellen Fähigkeiten an.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-indigo-500 h-48">
|
||||
</div>
|
||||
<div class="bg-gray-50">
|
||||
<div class="mx-auto max-w-7xl py-12 px-6 lg:flex lg:items-center lg:justify-between lg:py-16 lg:px-8">
|
||||
<h2 class="text-3xl font-bold tracking-tight text-gray-900 sm:text-4xl">
|
||||
<span class="block">Bereit los zu legen?</span>
|
||||
<span class="block text-indigo-600">Fange noch heute an zu lernen.</span>
|
||||
</h2>
|
||||
<div class="mt-8 flex lg:mt-0 lg:flex-shrink-0">
|
||||
<div class="inline-flex rounded-md shadow">
|
||||
<a href="/login" class="<?= urlIs("/login") ?> nes-btn is-primary">Los geht's</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<?php require ('partials/footer.php') ?>
|
||||
69
views/login.view.php
Normal file
69
views/login.view.php
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
<?php require ('partials/head.php') ?>
|
||||
<?php require ('partials/nav.php') ?>
|
||||
<!--
|
||||
This example requires some changes to your config:
|
||||
|
||||
```
|
||||
// tailwind.config.js
|
||||
module.exports = {
|
||||
// ...
|
||||
plugins: [
|
||||
// ...
|
||||
require('@tailwindcss/forms'),
|
||||
],
|
||||
}
|
||||
```
|
||||
-->
|
||||
<!--
|
||||
This example requires updating your template:
|
||||
|
||||
```
|
||||
<html class="h-full bg-gray-50">
|
||||
<body class="h-full">
|
||||
```
|
||||
-->
|
||||
<div class="flex min-h-full items-center justify-center py-12 px-4 sm:px-6 lg:px-8">
|
||||
<div class="w-full max-w-md space-y-8 nes-container">
|
||||
<div>
|
||||
<h2 class="mt-6 text-center text-3xl font-bold tracking-tight text-gray-900">Melde dich mit deinem Konto an.</h2>
|
||||
<p class="mt-2 text-center text-sm text-gray-600">
|
||||
Oder
|
||||
<a href="#" class="font-medium text-indigo-600 hover:text-indigo-500">registriere dich noch Heute</a>
|
||||
</p>
|
||||
</div>
|
||||
<form class="mt-8 space-y-6" action="#" method="POST">
|
||||
<input type="hidden" name="remember" value="true">
|
||||
<div class="-space-y-px rounded-md shadow-sm">
|
||||
<div class="nes-field">
|
||||
<label for="email-address" class="sr-only">Email address</label>
|
||||
<input id="email-address" name="email" type="email" autocomplete="email" required class="nes-input relative block w-full appearance-none rounded-none rounded-t-md border border-gray-300 px-3 py-2 text-gray-900 placeholder-gray-500 focus:z-10 focus:border-indigo-500 focus:outline-none focus:ring-indigo-500 sm:text-sm" placeholder="Email address">
|
||||
</div>
|
||||
<div class="nes-field">
|
||||
<label for="password" class="sr-only">Password</label>
|
||||
<input id="password" name="password" type="password" autocomplete="current-password" required class="nes-input relative block w-full appearance-none rounded-none rounded-b-md border border-gray-300 px-3 py-2 text-gray-900 placeholder-gray-500 focus:z-10 focus:border-indigo-500 focus:outline-none focus:ring-indigo-500 sm:text-sm" placeholder="Password">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center">
|
||||
<input id="remember-me" name="remember-me" type="checkbox" class="h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500">
|
||||
<label for="remember-me" class="ml-2 block text-sm text-gray-900">Angemeldet bleiben</label>
|
||||
</div>
|
||||
|
||||
<div class="text-sm">
|
||||
<a href="#" class="font-medium text-indigo-600 hover:text-indigo-500">Passwort vergessen?</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button type="submit" class="nes-btn is-primary">
|
||||
<span class="absolute inset-y-0 left-0 flex items-center pl-3">
|
||||
</span>
|
||||
Anmelden
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php require ('partials/footer.php') ?>
|
||||
|
|
@ -4,8 +4,12 @@
|
|||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<script src="https://cdn.tailwindcss.com?plugins=forms"></script>
|
||||
<title>Demo</title>
|
||||
<script src="https://cdn.tailwindcss.com?plugins=forms"></script>
|
||||
<link href="https://cdn.jsdelivr.net/npm/nes.css@2.3.0/css/nes.min.css" rel="stylesheet">
|
||||
<title>The Math Wizard</title>
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P');
|
||||
</style>
|
||||
</head>
|
||||
<body class="h-full">
|
||||
<div class="min-h-full">
|
||||
|
|
@ -1,132 +1,93 @@
|
|||
<nav class="bg-gray-800">
|
||||
<div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||
<div class="flex h-16 items-center justify-between">
|
||||
<div class="flex items-center">
|
||||
<div class="flex-shrink-0">
|
||||
<img class="h-8 w-8" src="https://tailwindui.com/img/logos/mark.svg?color=indigo&shade=500" alt="Your Company">
|
||||
</div>
|
||||
<div class="hidden md:block">
|
||||
<div class="ml-10 flex items-baseline space-x-4">
|
||||
<!-- Current: "bg-gray-900 text-white", Default: "text-gray-300 hover:bg-gray-700 hover:text-white" -->
|
||||
<a href="/" class="<?= urlIs('/') ? 'bg-gray-900 text-white' : 'text-gray-300' ?> hover:bg-gray-700 hover:text-white px-3 py-2 rounded-md text-sm font-medium">Home</a>
|
||||
|
||||
<a href="/about" class="<?= urlIs('/about') ? 'bg-gray-900 text-white' : 'text-gray-300' ?> hover:bg-gray-700 hover:text-white px-3 py-2 rounded-md text-sm font-medium" >About</a>
|
||||
|
||||
<a href="/contact" class="<?= urlIs('/contact') ? 'bg-gray-900 text-white' : 'text-gray-300' ?> hover:bg-gray-700 hover:text-white px-3 py-2 rounded-md text-sm font-medium">Contact</a>
|
||||
|
||||
<a href="/learn" class="<?= urlIs('/learn') ? 'bg-gray-900 text-white' : 'text-gray-300' ?> hover:bg-gray-700 hover:text-white px-3 py-2 rounded-md text-sm font-medium">Learn</a>
|
||||
|
||||
<a href="/mathe" class="<?= urlIs('/mathe') ? 'bg-gray-900 text-white' : 'text-gray-300' ?> hover:bg-gray-700 hover:text-white px-3 py-2 rounded-md text-sm font-medium">Mathe</a>
|
||||
|
||||
<a href="/notes" class="<?= urlIs('/notes') ? 'bg-gray-900 text-white' : 'text-gray-300' ?> hover:bg-gray-700 hover:text-white px-3 py-2 rounded-md text-sm font-medium">Notes</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="isolate bg-white">
|
||||
<div class="absolute inset-x-0 top-[-10rem] -z-10 transform-gpu overflow-hidden blur-3xl sm:top-[-20rem]">
|
||||
<svg class="relative left-[calc(50%-11rem)] -z-10 h-[21.1875rem] max-w-none -translate-x-1/2 rotate-[30deg] sm:left-[calc(50%-30rem)] sm:h-[42.375rem]" viewBox="0 0 1155 678" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill="url(#45de2b6b-92d5-4d68-a6a0-9b9b2abad533)" fill-opacity=".3" d="M317.219 518.975L203.852 678 0 438.341l317.219 80.634 204.172-286.402c1.307 132.337 45.083 346.658 209.733 145.248C936.936 126.058 882.053-94.234 1031.02 41.331c119.18 108.451 130.68 295.337 121.53 375.223L855 299l21.173 362.054-558.954-142.079z" />
|
||||
<defs>
|
||||
<linearGradient id="45de2b6b-92d5-4d68-a6a0-9b9b2abad533" x1="1155.49" x2="-78.208" y1=".177" y2="474.645" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#9089FC"></stop>
|
||||
<stop offset="1" stop-color="#FF80B5"></stop>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="px-6 pt-6 lg:px-8 mb-5">
|
||||
<div>
|
||||
<nav class="flex h-9 items-center justify-between" aria-label="Global">
|
||||
<div class="flex lg:min-w-0 lg:flex-1" aria-label="Global">
|
||||
<a href="#" class="-m-1.5 p-1.5">
|
||||
<span class="sr-only">Your Company</span>
|
||||
<img class="h-8" src="./../../images/icon.png" alt="">
|
||||
</a>
|
||||
</div>
|
||||
<div class="hidden md:block">
|
||||
<div class="ml-4 flex items-center md:ml-6">
|
||||
<button type="button" class="rounded-full bg-gray-800 p-1 text-gray-400 hover:text-white focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-800">
|
||||
<span class="sr-only">View notifications</span>
|
||||
<!-- Heroicon name: outline/bell -->
|
||||
<svg class="h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M14.857 17.082a23.848 23.848 0 005.454-1.31A8.967 8.967 0 0118 9.75v-.7V9A6 6 0 006 9v.75a8.967 8.967 0 01-2.312 6.022c1.733.64 3.56 1.085 5.455 1.31m5.714 0a24.255 24.255 0 01-5.714 0m5.714 0a3 3 0 11-5.714 0" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<!-- Profile dropdown -->
|
||||
<div class="relative ml-3">
|
||||
<div>
|
||||
<button type="button" class="flex max-w-xs items-center rounded-full bg-gray-800 text-sm focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-800" id="user-menu-button" aria-expanded="false" aria-haspopup="true">
|
||||
<span class="sr-only">Open user menu</span>
|
||||
<img class="h-8 w-8 rounded-full" src="https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80" alt="">
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!--
|
||||
Dropdown menu, show/hide based on menu state.
|
||||
|
||||
Entering: "transition ease-out duration-100"
|
||||
From: "transform opacity-0 scale-95"
|
||||
To: "transform opacity-100 scale-100"
|
||||
Leaving: "transition ease-in duration-75"
|
||||
From: "transform opacity-100 scale-100"
|
||||
To: "transform opacity-0 scale-95"
|
||||
-->
|
||||
<!--
|
||||
<div class="absolute right-0 z-10 mt-2 w-48 origin-top-right rounded-md bg-white py-1 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none" role="menu" aria-orientation="vertical" aria-labelledby="user-menu-button" tabindex="-1">
|
||||
Active: "bg-gray-100", Not Active: ""
|
||||
<a href="#" class="block px-4 py-2 text-sm text-gray-700" role="menuitem" tabindex="-1" id="user-menu-item-0">Your Profile</a>
|
||||
|
||||
<a href="#" class="block px-4 py-2 text-sm text-gray-700" role="menuitem" tabindex="-1" id="user-menu-item-1">Settings</a>
|
||||
|
||||
<a href="#" class="block px-4 py-2 text-sm text-gray-700" role="menuitem" tabindex="-1" id="user-menu-item-2">Sign out</a>
|
||||
</div>
|
||||
-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="-mr-2 flex md:hidden">
|
||||
<!-- Mobile menu button -->
|
||||
<button type="button" class="inline-flex items-center justify-center rounded-md bg-gray-800 p-2 text-gray-400 hover:bg-gray-700 hover:text-white focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-800" aria-controls="mobile-menu" aria-expanded="false">
|
||||
<div class="flex lg:hidden">
|
||||
<button type="button" class="-m-2.5 inline-flex items-center justify-center rounded-md p-2.5 text-gray-700">
|
||||
<span class="sr-only">Open main menu</span>
|
||||
<!--
|
||||
Heroicon name: outline/bars-3
|
||||
|
||||
Menu open: "hidden", Menu closed: "block"
|
||||
-->
|
||||
<svg class="block h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
|
||||
<!-- Heroicon name: outline/bars-3 -->
|
||||
<svg class="h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" />
|
||||
</svg>
|
||||
<!--
|
||||
Heroicon name: outline/x-mark
|
||||
|
||||
Menu open: "block", Menu closed: "hidden"
|
||||
-->
|
||||
<svg class="hidden h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="hidden lg:flex lg:min-w-0 lg:flex-1 lg:justify-center lg:gap-x-12">
|
||||
<a href="/" class="<?= urlIs('/') ?> font-semibold text-gray-900 hover:text-gray-900">Home</a>
|
||||
|
||||
<a href="/about" class="<?= urlIs('/about') ?>font-semibold text-gray-900 hover:text-gray-900">About</a>
|
||||
|
||||
<a href="/contact" class="<?= urlIs('/contact') ?>font-semibold text-gray-900 hover:text-gray-900">Contact</a>
|
||||
|
||||
<a href="/learn" class="<?= urlIs('/learn') ?>font-semibold text-gray-900 hover:text-gray-900">Learn</a>
|
||||
<a href="/mathe" class="<?= urlIs('/mathe') ?>font-semibold text-gray-900 hover:text-gray-900">Mathe</a>
|
||||
<a href="/game" class="<?= urlIs('/game') ?>font-semibold text-gray-900 hover:text-gray-900">Game</a>
|
||||
</div>
|
||||
<div class="hidden lg:flex lg:min-w-0 lg:flex-1 lg:justify-end">
|
||||
<a href="/login" class="<?= urlIs('/login') ?> nes-btn">Log in</a>
|
||||
<a href="/register" class="<?= urlIs('/register') ?>nes-btn">Registrieren</a>
|
||||
</div>
|
||||
</nav>
|
||||
<!-- Mobile menu, show/hide based on menu open state. -->
|
||||
<div role="dialog" aria-modal="true">
|
||||
<div focus="true" class="fixed inset-0 z-10 overflow-y-auto bg-white px-6 py-6 lg:hidden">
|
||||
<div class="flex h-9 items-center justify-between">
|
||||
<div class="flex">
|
||||
<a href="#" class="-m-1.5 p-1.5">
|
||||
<span class="sr-only">Your Company</span>
|
||||
<img class="h-8" src="./../../images/icon.png" alt="">
|
||||
</a>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<button type="button" class="-m-2.5 inline-flex items-center justify-center rounded-md p-2.5 text-gray-700">
|
||||
<span class="sr-only">Close menu</span>
|
||||
<!-- Heroicon name: outline/x-mark -->
|
||||
<svg class="h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-6 flow-root">
|
||||
<div class="-my-6 divide-y divide-gray-500/10">
|
||||
<div class="space-y-2 py-6">
|
||||
<a href="/" class="<?= urlIs('/') ?> -mx-3 block rounded-lg py-2 px-3 text-base font-semibold leading-7 text-gray-900 hover:bg-gray-400/10">Home</a>
|
||||
|
||||
<a href="/about" class="<?= urlIs('/about') ?>-mx-3 block rounded-lg py-2 px-3 text-base font-semibold leading-7 text-gray-900 hover:bg-gray-400/10">About</a>
|
||||
|
||||
<a href="/contact" class="<?= urlIs('/contact') ?>-mx-3 block rounded-lg py-2 px-3 text-base font-semibold leading-7 text-gray-900 hover:bg-gray-400/10">Contact</a>
|
||||
|
||||
<a href="/learn" class="<?= urlIs('/learn') ?> -mx-3 block rounded-lg py-2 px-3 text-base font-semibold leading-7 text-gray-900 hover:bg-gray-400/10">Learn</a>
|
||||
<a href="/mathe" class="<?= urlIs('/mathe') ?> -mx-3 block rounded-lg py-2 px-3 text-base font-semibold leading-7 text-gray-900 hover:bg-gray-400/10">Learn</a>
|
||||
<a href="/game" class="<?= urlIs('/game') ?> -mx-3 block rounded-lg py-2 px-3 text-base font-semibold leading-7 text-gray-900 hover:bg-gray-400/10">Learn</a>
|
||||
</div>
|
||||
<div class="py-6">
|
||||
<a href="/login" class="-mx-3 block rounded-lg py-2.5 px-3 text-base font-semibold leading-6 text-gray-900 hover:bg-gray-400/10">Log in</a>
|
||||
<a href="/register" class="-mx-3 block rounded-lg py-2.5 px-3 text-base font-semibold leading-6 text-gray-900 hover:bg-gray-400/10">Registrieren</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Mobile menu, show/hide based on menu state. -->
|
||||
<div class="md:hidden" id="mobile-menu">
|
||||
<div class="space-y-1 px-2 pt-2 pb-3 sm:px-3">
|
||||
<!-- Current: "bg-gray-900 text-white", Default: "text-gray-300 hover:bg-gray-700 hover:text-white" -->
|
||||
<a href="#" class="bg-gray-900 text-white block px-3 py-2 rounded-md text-base font-medium" aria-current="page">Dashboard</a>
|
||||
|
||||
<a href="#" class="text-gray-300 hover:bg-gray-700 hover:text-white block px-3 py-2 rounded-md text-base font-medium">Team</a>
|
||||
|
||||
<a href="#" class="text-gray-300 hover:bg-gray-700 hover:text-white block px-3 py-2 rounded-md text-base font-medium">Projects</a>
|
||||
|
||||
<a href="#" class="text-gray-300 hover:bg-gray-700 hover:text-white block px-3 py-2 rounded-md text-base font-medium">Calendar</a>
|
||||
|
||||
<a href="#" class="text-gray-300 hover:bg-gray-700 hover:text-white block px-3 py-2 rounded-md text-base font-medium">Reports</a>
|
||||
</div>
|
||||
<div class="border-t border-gray-700 pt-4 pb-3">
|
||||
<div class="flex items-center px-5">
|
||||
<div class="flex-shrink-0">
|
||||
<img class="h-10 w-10 rounded-full" src="https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80" alt="">
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<div class="text-base font-medium leading-none text-white">Tom Cook</div>
|
||||
<div class="text-sm font-medium leading-none text-gray-400">tom@example.com</div>
|
||||
</div>
|
||||
<button type="button" class="ml-auto flex-shrink-0 rounded-full bg-gray-800 p-1 text-gray-400 hover:text-white focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-800">
|
||||
<span class="sr-only">View notifications</span>
|
||||
<!-- Heroicon name: outline/bell -->
|
||||
<svg class="h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M14.857 17.082a23.848 23.848 0 005.454-1.31A8.967 8.967 0 0118 9.75v-.7V9A6 6 0 006 9v.75a8.967 8.967 0 01-2.312 6.022c1.733.64 3.56 1.085 5.455 1.31m5.714 0a24.255 24.255 0 01-5.714 0m5.714 0a3 3 0 11-5.714 0" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="mt-3 space-y-1 px-2">
|
||||
<a href="#" class="block rounded-md px-3 py-2 text-base font-medium text-gray-400 hover:bg-gray-700 hover:text-white">Your Profile</a>
|
||||
|
||||
<a href="#" class="block rounded-md px-3 py-2 text-base font-medium text-gray-400 hover:bg-gray-700 hover:text-white">Settings</a>
|
||||
|
||||
<a href="#" class="block rounded-md px-3 py-2 text-base font-medium text-gray-400 hover:bg-gray-700 hover:text-white">Sign out</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
Loading…
Add table
Add a link
Reference in a new issue