diff --git a/.eslintrc b/.eslintrc index ae31524..d05f39a 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,3 +1,6 @@ { - "extends": "hrundel/browser" + "extends": "hrundel/browser", + "parserOptions": { + "sourceType": "module" + } } diff --git a/.gitignore b/.gitignore index 552f221..0d31e5b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ node_modules/ *.log +.cache/ +dist/ \ No newline at end of file diff --git a/drawer.js b/drawer.js new file mode 100644 index 0000000..9e38fd1 --- /dev/null +++ b/drawer.js @@ -0,0 +1,40 @@ +import snap from 'snapsvg'; + +class Drawer { + drawHero() { + this.layout = snap('.hero__picture'); + this.head = this.layout.circle(50, 50, 50).attr({ fill: '#C5AFA4' }); + this.eyes = this.layout.group( + this.layout.circle(70, 30, 10), + this.layout.circle(30, 30, 10) + ); + this.eyes.attr({ fill: '#CF4D6F' }); + this.mouth = this.layout.path('M10,70C40,90,60,90,90,70').attr({ fill: '#C97282' }); + this.nose = this.layout.path('').attr({ fill: '#76818E' }); + this.nose = this.layout.group( + this.layout.circle(50, 50, 12).attr({ fill: '#ecc5c4' }), + this.layout.circle(44, 50, 3).attr({ fill: '#b85f69' }), + this.layout.circle(56, 50, 3).attr({ fill: '#b85f69' }), + ); + } + + animateDeath() { + this.stopSpeak(); + this.eyes.attr({ fill: '#000' }); + this.mouth.animate({ d: 'M10,90C40,70,60,70,90,90' }, 1000); + } + + stopSpeak() { + if (this.ears) { + this.ears.attr({ opacity: 0 }); + } + } + + startSpeak() { + this.ears = this.layout.group(this.layout.polygon([4, 30, 30, 4, 0, 0]), + this.layout.polygon([96, 30, 70, 4, 100, 0])); + this.ears.attr({ fill: '#b85f69' }); + } +} + +export default new Drawer(); diff --git a/hrunogochi.js b/hrunogochi.js new file mode 100644 index 0000000..1738b68 --- /dev/null +++ b/hrunogochi.js @@ -0,0 +1,206 @@ +import { emptyHandler, bounded } from './utils'; + +const TICK_INTERVAL = 1500; + +const phrases = [ + 'Хрю-хрю', + 'Поиграй со мной', + 'Эх...' +]; + +export default class Hrunogochi { + constructor(state) { + this._init(state); + this._onStart = emptyHandler; + this._onReset = emptyHandler; + this._onUpdate = emptyHandler; + this._onSpeak = emptyHandler; + this._onDeath = emptyHandler; + } + + _init(state) { + this._state = state || this._getInitialState(); + this._eating = false; + this._sleeping = false; + this._speaking = false; + } + + get isDead() { + const { satiety, energy, mood } = this; + + return [satiety, energy, mood].filter(value => value === 0).length > 1; + } + + get eating() { + return this._eating; + } + + set eating(value) { + this._eating = value; + } + + get sleeping() { + return this._sleeping; + } + + set sleeping(value) { + this._sleeping = value; + } + + get speaking() { + return this._speaking; + } + + set speaking(value) { + this._speaking = value; + } + + get energy() { + return this._state.energy; + } + + set energy(value) { + this._state.energy = bounded(value); + } + + get satiety() { + return this._state.satiety; + } + + set satiety(value) { + this._state.satiety = bounded(value); + } + + get mood() { + return this._state.mood; + } + + set mood(value) { + this._state.mood = bounded(value); + } + + get state() { + return this._state; + } + + _getInitialState() { + return { + satiety: 100, + energy: 90, + mood: 75 + }; + } + + _setState({ satiety, energy, mood }) { + this.satiety = satiety; + this.energy = energy; + this.mood = mood; + + this.onUpdate(); + } + + _updateState(newState) { + const mergedState = Object.assign({}, this._state, newState); + this._setState(mergedState); + } + + speak(text) { + this.onSpeak({ text }); + } + + saveState(saveAction) { + saveAction(this._state); + } + + start() { + // eslint-disable-next-line max-statements + const tick = () => { + if (this.isDead) { + this.onDeath(); + + return this.stop(); + } + + if (Math.random() > 0.75) { + const chosenPhrase = phrases[Math.floor(Math.random() * phrases.length)]; + this.speak(chosenPhrase); + } + + let { satiety, energy, mood } = this; + + if (this.speaking) { + mood += 3; + } else if (this.sleeping) { + energy += 3; + } else if (this.eating) { + satiety += 3; + } + + satiety--; + energy--; + mood--; + + this._updateState({ + satiety, + energy, + mood + }); + + this._tickId = setTimeout(tick, TICK_INTERVAL); + }; + + tick(); + this.onStart(); + } + + stop() { + clearTimeout(this._tickId); + } + + reset() { + this.onReset(); + this.stop(); + this._setState(this._getInitialState()); + this.start(); + } + + get onStart() { + return this._onStart; + } + + set onStart(handler) { + this._onStart = handler; + } + + get onUpdate() { + return this._onUpdate; + } + + set onUpdate(handler) { + this._onUpdate = handler; + } + + get onReset() { + return this._onReset; + } + + set onReset(handler) { + this._onReset = handler; + } + + get onSpeak() { + return this._onSpeak; + } + + set onSpeak(handler) { + this._onSpeak = handler; + } + + get onDeath() { + return this._onDeath; + } + + set onDeath(handler) { + this._onDeath = handler; + } +} diff --git a/index.css b/index.css index e69de29..ddadb00 100644 --- a/index.css +++ b/index.css @@ -0,0 +1,50 @@ +html, +body +{ + margin: 0; + padding: 0; + height: 100%; +} + +body +{ + display: flex; +} + +.game +{ + width: 30%; + margin: auto; +} + +.state, +.controls +{ + display: flex; +} + +.state-item, +.controls-item +{ + flex-grow: 1; + text-align: center; +} + +.controls-item +{ + padding: 5px 10px; + font-size: 15px; +} + +.hero +{ + margin: 10px; + display: flex; + flex-direction: column; + align-items: center; +} + +.hero__speech +{ + margin: 10px; +} diff --git a/index.html b/index.html index ae344b1..035ac15 100644 --- a/index.html +++ b/index.html @@ -4,7 +4,32 @@