From 8d31cb5bc72b8cd5c0948639bfd745ecc5b0cba5 Mon Sep 17 00:00:00 2001 From: Patryk Hegenberg Date: Fri, 6 Jan 2023 10:11:39 +0100 Subject: [PATCH] Game Sourcecode has been added and Style was changed --- controllers/game.php | 3 + controllers/login.php | 3 + images/01coin.gif | Bin 0 -> 7652 bytes images/bg.png | Bin 0 -> 11588 bytes images/colored_tilemap_packed.png | Bin 0 -> 2382 bytes images/icon.png | Bin 0 -> 863 bytes router.php | 2 +- routes.php | 6 +- scripts/game.js | 947 +++++++++++++++++ styles/game.css | 476 +++++++++ views/game.css | 476 +++++++++ views/game.js | 947 +++++++++++++++++ views/game.view.php | 1583 +++++++++++++++++++++++++++++ views/index.view.php | 100 +- views/login.view.php | 69 ++ views/partials/head.php | 10 +- views/partials/nav.php | 203 ++-- 17 files changed, 4689 insertions(+), 136 deletions(-) create mode 100644 controllers/game.php create mode 100644 controllers/login.php create mode 100644 images/01coin.gif create mode 100644 images/bg.png create mode 100644 images/colored_tilemap_packed.png create mode 100644 images/icon.png create mode 100644 scripts/game.js create mode 100644 styles/game.css create mode 100644 views/game.css create mode 100644 views/game.js create mode 100644 views/game.view.php create mode 100644 views/login.view.php diff --git a/controllers/game.php b/controllers/game.php new file mode 100644 index 0000000..82a554a --- /dev/null +++ b/controllers/game.php @@ -0,0 +1,3 @@ +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 K?bNM-XZ{xvVji&o literal 0 HcmV?d00001 diff --git a/images/colored_tilemap_packed.png b/images/colored_tilemap_packed.png new file mode 100644 index 0000000000000000000000000000000000000000..26655326bb083df6049d94c831ba7e6051c66231 GIT binary patch literal 2382 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 literal 0 HcmV?d00001 diff --git a/router.php b/router.php index e06ba4c..0edd7f0 100644 --- a/router.php +++ b/router.php @@ -16,4 +16,4 @@ function abort($code = 404) { http_response_code($code); require "views/{$code}.php"; die(); -} \ No newline at end of file +} diff --git a/routes.php b/routes.php index 8fc0680..79d6f5e 100644 --- a/routes.php +++ b/routes.php @@ -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' -]; \ No newline at end of file + '/notes/create' => 'controllers/notes/create.php', + '/login' => 'controllers/login.php' +]; diff --git a/scripts/game.js b/scripts/game.js new file mode 100644 index 0000000..5fca6b9 --- /dev/null +++ b/scripts/game.js @@ -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); diff --git a/styles/game.css b/styles/game.css new file mode 100644 index 0000000..0e3addb --- /dev/null +++ b/styles/game.css @@ -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;} +} diff --git a/views/game.css b/views/game.css new file mode 100644 index 0000000..0e3addb --- /dev/null +++ b/views/game.css @@ -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;} +} diff --git a/views/game.js b/views/game.js new file mode 100644 index 0000000..5fca6b9 --- /dev/null +++ b/views/game.js @@ -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); diff --git a/views/game.view.php b/views/game.view.php new file mode 100644 index 0000000..e477403 --- /dev/null +++ b/views/game.view.php @@ -0,0 +1,1583 @@ + + + + The Math Wizard + + + + + + + + + + + + + + + +
+
+ +

The
Math
Wizard

+
+
+ + +
+
+ + + + + + + + Roguelike + + + + + Roguelike + + + +
+
+ +
+ +
+ + + + + + + + + + + + + + + + + +
+
+
+
+
+
<
+
<
+
>
+
>
+
.
+
+
+ + + + diff --git a/views/index.view.php b/views/index.view.php index 0b2b674..ab7091f 100644 --- a/views/index.view.php +++ b/views/index.view.php @@ -1,14 +1,96 @@ - -
-
- -

Hello and Welcome.

-
-
+
+
+
+
+
+ +
+

Automatisiere spielerisch das 1x1

+

Zeige was du kannst und kämpfe dich durch denn Dungeon.

+ +
+
+ + + + + + + + + +
+
- +
+
+ +
+
+
+
+

Der bessere Weg das 1x1 zu automatisieren.

+ +
+
+
+
+ + +
+
+

Immer neue Level

+

Durch zufällig generierte Level wird dir nie langweilig.

+
+
+ +
+
+ + +
+
+

Angepasste Schwierigkeit

+

Die Aufgabe passen sich deinen aktuellen Fähigkeiten an.

+
+
+ +
+
+
+
+
+
+
+
+

+ Bereit los zu legen? + Fange noch heute an zu lernen. +

+ +
+
+
- \ No newline at end of file +
+ + diff --git a/views/login.view.php b/views/login.view.php new file mode 100644 index 0000000..0ade09e --- /dev/null +++ b/views/login.view.php @@ -0,0 +1,69 @@ + + + + +
+
+
+

Melde dich mit deinem Konto an.

+

+ Oder + registriere dich noch Heute +

+
+
+ +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+ + +
+ +
+ +
+
+
+
+ + diff --git a/views/partials/head.php b/views/partials/head.php index 3692e6b..a6e014d 100644 --- a/views/partials/head.php +++ b/views/partials/head.php @@ -4,8 +4,12 @@ - - Demo + + +The Math Wizard + -
\ No newline at end of file +
diff --git a/views/partials/nav.php b/views/partials/nav.php index 6740088..e120d9c 100644 --- a/views/partials/nav.php +++ b/views/partials/nav.php @@ -1,132 +1,93 @@ - \ No newline at end of file