diff --git a/.eslintrc.json b/.eslintrc.json index b47d466..127bbaa 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -4,7 +4,9 @@ "es6": true }, "extends": ["eslint:recommended", "airbnb"], - "globals": {}, + "globals": { + "EventEmiter": "readonly" + }, "parserOptions": { "ecmaVersion": 2018, "sourceType": "script" @@ -15,6 +17,9 @@ "implicit-arrow-linebreak": "off", "func-names": "off", "object-curly-newline": "off", - "no-plusplus": "off" + "no-plusplus": "off", + "no-restricted-syntax": "off", + "guard-for-in": "off", + "no-continue": "off" } } diff --git a/assets/css/style.css b/assets/css/style.css index 2f29d7d..479167b 100644 --- a/assets/css/style.css +++ b/assets/css/style.css @@ -8,12 +8,35 @@ body { font-size: 20px; } +#more-break { + background-color: grey; + color: black; + visibility: hidden; +} + +#more-break::before, #more-break::after { + content: "\00a0"; +} + #hud { font-size: 25px; font-weight: bold; color: magenta; } +#hud ul { + margin: 0; + padding: 0; +} + +#hud li { + display: inline-block; +} + +#hud li:not(:first-child) { + margin-left: 5px; +} + .center { margin-left: auto; margin-right: auto; diff --git a/index.html b/index.html index 63d03cd..0fc3171 100644 --- a/index.html +++ b/index.html @@ -10,11 +10,35 @@
-
Press any key to continue...
+
+ + More +
-
HP: 100
+
+ +
+ + + + + + + + + + + + + diff --git a/main.js b/main.js index 41cd2ed..4666f3b 100644 --- a/main.js +++ b/main.js @@ -1,13 +1,20 @@ +// eslint-disable-next-line no-unused-vars const field = document.getElementById('game-field'); -const context = field.getContext('2d'); -const menuTheme = new Audio('./assets/sound/menu-theme.mp3'); -menuTheme.loop = true; -menuTheme.play(); +// eslint-disable-next-line no-undef, no-unused-vars +const messageBox = new MessageBox(); -const cover = new Image(); -cover.src = './assets/images/cover.png'; +// eslint-disable-next-line no-undef, no-unused-vars +const hud = new HUD(); -cover.onload = () => { - context.drawImage(cover, 0, 0); -}; +// eslint-disable-next-line no-undef +const director = new Director(); + +// eslint-disable-next-line no-undef, no-unused-vars +const menu = new Menu(); + +// eslint-disable-next-line no-undef +const keyboardHandler = new KeyboardHandler(); +document.addEventListener('keydown', keyboardHandler); + +director.enter(); diff --git a/src/director.js b/src/director.js new file mode 100644 index 0000000..78aead2 --- /dev/null +++ b/src/director.js @@ -0,0 +1,49 @@ +function Director() { + this.stage = null; + EventEmiter.subscribe('key:F1', () => this.startGame()); + EventEmiter.subscribe('key:F2', () => this.startBot()); + EventEmiter.subscribe('key:F8', () => this.pause()); + EventEmiter.subscribe('key:F9', () => this.exit()); +} + +Director.Stages = { + MENU: 'menu', + GAME: 'game', + BOT: 'bot', + PAUSE: 'pause', +}; + +Director.prototype.enter = function () { + this.stage = Director.Stages.MENU; + this.emitStage(); +}; + +Director.prototype.startGame = function () { + if (this.stage !== Director.Stages.MENU) return; + this.stage = Director.Stages.GAME; + this.emitStage(); +}; + +Director.prototype.startBot = function () { + if (this.stage !== Director.Stages.MENU) return; + this.stage = Director.Stages.BOT; + this.emitStage(); +}; + +Director.prototype.pause = function () { + if (this.stage !== Director.Stages.GAME + && this.stage !== Director.Stages.BOT) return; + this.stage = Director.Stages.PAUSE; + this.emitStage(); +}; + +Director.prototype.exit = function () { + if (this.stage !== Director.Stages.GAME + && this.stage !== Director.Stages.BOT) return; + this.stage = Director.Stages.MENU; + this.emitStage(); +}; + +Director.prototype.emitStage = function () { + EventEmiter.emit(`stage:${this.stage}`); +}; diff --git a/src/handlers/keyboard-handler.js b/src/handlers/keyboard-handler.js new file mode 100644 index 0000000..d7e3377 --- /dev/null +++ b/src/handlers/keyboard-handler.js @@ -0,0 +1,7 @@ +/* eslint-disable no-undef */ + +function KeyboardHandler() {} + +KeyboardHandler.prototype.handleEvent = function ({ code }) { + EventEmiter.emit(`key:${code}`); +}; diff --git a/src/hud.js b/src/hud.js new file mode 100644 index 0000000..a32a7c5 --- /dev/null +++ b/src/hud.js @@ -0,0 +1,41 @@ +// eslint-disable-next-line no-unused-vars +function HUD() { + this.indicators = { + level: 0, + hits: { + current: 0, + max: 0, + }, + strength: { + current: 0, + max: 0, + }, + gold: 0, // change to something else + armor: 0, + }; + EventEmiter.subscribe('stage:menu', () => this.hide()); + EventEmiter.subscribe('stage:pause', () => this.hide()); + EventEmiter.subscribe('stage:game', () => this.show()); + EventEmiter.subscribe('stage:bot', () => this.show()); +} + +HUD.prototype.update = function (indicators) { + // eslint-disable-next-line no-undef + deepCopy(this.indicators, indicators); + this.print(); +}; + +HUD.prototype.print = function () { + // eslint-disable-next-line no-undef + UIRenderer.write(this.indicators); +}; + +HUD.prototype.hide = function () { + // eslint-disable-next-line no-undef + UIRenderer.hide('hud'); +}; + +HUD.prototype.show = function () { + // eslint-disable-next-line no-undef + UIRenderer.show('hud'); +}; diff --git a/src/menu.js b/src/menu.js new file mode 100644 index 0000000..e9662bd --- /dev/null +++ b/src/menu.js @@ -0,0 +1,33 @@ +function Menu() { + EventEmiter.subscribe('stage:menu', () => this.showMain()); + EventEmiter.subscribe('stage:pause', () => this.showPause()); + EventEmiter.subscribe('stage:game', () => this.hide()); + EventEmiter.subscribe('stage:bot', () => this.hide()); +} + +Menu.prototype.show = function () { + this.context = field.getContext('2d'); + this.menuTheme = new Audio('./assets/sound/menu-theme.mp3'); + this.menuTheme.loop = true; + this.menuTheme.play(); + const cover = new Image(); + cover.src = './assets/images/cover.png'; + cover.onload = () => { + this.context.drawImage(cover, 0, 0); + }; +}; + +Menu.prototype.showMain = function () { + this.show(); + messageBox.write('Press F1 to play or F2 to start bot...'); +}; + +Menu.prototype.showPause = function () { + this.show(); + messageBox.write('Game paused. Press Enter to continue...'); +}; + +Menu.prototype.hide = function () { + this.context.clearRect(0, 0, 800, 600); + this.menuTheme.pause(); +}; diff --git a/src/message-box.js b/src/message-box.js new file mode 100644 index 0000000..dde5578 --- /dev/null +++ b/src/message-box.js @@ -0,0 +1,50 @@ +// eslint-disable-next-line no-unused-vars +function MessageBox() { + this.buffer = []; + EventEmiter.subscribe('key:Space', () => this.continue()); +} + +MessageBox.prototype.write = function (message) { + if (message.length <= 64) { + this.buffer.push(message); + } else { + const words = message.split(' '); + let sentence = ''; + for (let i = 0; i < words.length; i++) { + sentence += words[i]; + sentence += ' '; + if (sentence.length > 48) { + this.buffer.push(sentence); + sentence = []; + } + } + this.buffer.push(sentence); + this.showMore(); + } + this.continue(); +}; + +MessageBox.prototype.continue = function () { + if (this.buffer.length === 0) { + this.hideMore(); + this.print(''); + return; + } + const message = this.buffer.shift(); + this.print(message); +}; + +MessageBox.prototype.print = function (message) { + // eslint-disable-next-line no-undef + UIRenderer.write({ message }); +}; + +MessageBox.prototype.showMore = function () { + // eslint-disable-next-line no-undef + UIRenderer.show('more-break'); +}; + +MessageBox.prototype.hideMore = function () { + // eslint-disable-next-line no-undef + UIRenderer.hide('more-break'); +}; diff --git a/src/renderers/ui-renderer.js b/src/renderers/ui-renderer.js new file mode 100644 index 0000000..b3f1bde --- /dev/null +++ b/src/renderers/ui-renderer.js @@ -0,0 +1,31 @@ +/* eslint-disable no-console */ +function UIRenderer() {} + +UIRenderer.write = data => { + // eslint-disable-next-line no-undef + const expanded = expandObject(data); + expanded.forEach(([id, value]) => { + const element = document.getElementById(id); + if (!element) { + console.error(`element with id ${id} not found`); + return; + } + element.innerText = value; + }); +}; + +UIRenderer.show = id => { + const element = document.getElementById(id); + if (!element) { + console.error(`element with id ${id} not found`); + } + element.style.visibility = 'visible'; +}; + +UIRenderer.hide = id => { + const element = document.getElementById(id); + if (!element) { + console.error(`element with id ${id} not found`); + } + element.style.visibility = 'hidden'; +}; diff --git a/src/utils/event-emitter.js b/src/utils/event-emitter.js new file mode 100644 index 0000000..ee615d2 --- /dev/null +++ b/src/utils/event-emitter.js @@ -0,0 +1,19 @@ +/* eslint-disable no-unused-vars */ + +const EventEmiter = (() => { + const events = {}; + const subscribe = (event, callback) => { + if (!events[event]) { + events[event] = []; + } + events[event].push(callback); + }; + const emit = (event, ...args) => { + const callbacks = events[event]; + if (!callbacks) { + throw new Error(`cannot find event with name ${event}`); + } + callbacks.forEach(callback => callback(...args)); + }; + return { subscribe, emit }; +})(); diff --git a/src/utils/objects.js b/src/utils/objects.js new file mode 100644 index 0000000..9ce2680 --- /dev/null +++ b/src/utils/objects.js @@ -0,0 +1,27 @@ +// eslint-disable-next-line no-unused-vars +const expandObject = (object, prefix = '') => { + const result = []; + for (const key in object) { + const value = object[key]; + const path = prefix ? `${prefix}.${key}` : key; + if (typeof value === 'object') { + result.push(...expandObject(value, path)); + continue; + } + result.push([path, value]); + } + return result; +}; + +// eslint-disable-next-line no-unused-vars +const deepCopy = (target, source) => { + for (const key in source) { + if (target[key] === undefined) continue; + if (typeof target[key] === 'object') { + deepCopy(target[key], source[key]); + continue; + } + // eslint-disable-next-line no-param-reassign + target[key] = source[key]; + } +};