diff --git a/src/async.js b/src/async.js new file mode 100644 index 0000000..61dc206 --- /dev/null +++ b/src/async.js @@ -0,0 +1,55 @@ +function Async() { + return { + bind: bind, + run: run, + sequence: sequence, + wait: wait, + wrap: wrap + }; + + function bind(m, f) { + return function(g) { + m(function(x) { + f(x)(g); + }); + } + } + + function run() { + var m; + if(arguments.length == 1) { + m = arguments[0]; + } else { + m = sequence.apply(null, arguments); + } + m(function() {}); + } + + function sequence() { + var steps = arguments; + var i = 0; + return function(f) { + var step = function() { + if(i < steps.length) { + steps[i++](step); + } else { + f(); + } + } + step(); + }; + } + + function wait(delay) { + return function(f) { + setTimeout(f, delay); + }; + } + + function wrap(x) { + return function(f) { + f(x); + }; + } + +} diff --git a/src/automaton.js b/src/automaton.js index 62d0426..eb63869 100644 --- a/src/automaton.js +++ b/src/automaton.js @@ -1,12 +1,125 @@ -function Automaton(messaging, screen, session) { - var startMenu = ['New Game', 'Settings']; - if(session.hasSavedGame()) { - startMenu.unshift('Continue'); +function Automaton(async, buttons, dom, messaging, screen, session) { + var menus = { + start: { + entries: [ + {label: 'New Game', action: newGame}, + {label: 'Settings', action: function() { console.log("Not implemented yet"); }} + ], + cancel: intro + }, + name: { + }, + skin: { + } + }; + var game = session.getGame(); + if(game != undefined) { + menus.start.entries.unshift( + {label: 'Continue', action: function() { messaging.send({tag: 'Resume', game: game}); }} + ); } - screen.menu(startMenu); - messaging.addEventListener(['Init'], function(game) { - console.log(game); - }); - //messaging.send({tag: 'NewGame'}); + return { + run: run + } + + //messaging.addEventListener(['Init'], function(game) { + // console.log(game); + //}); + + function animation(frames) { + var frameCounter = 0; + var scheduled; + var step = function() { + if(frameCounter < frames.length) { + scheduled = null; + frames[frameCounter].action(); + if(frames[frameCounter].delay != undefined) { + scheduled = setTimeout(step, frames[frameCounter].delay); + } + frameCounter++ + } + }; + return { + run: step, + skip: function() { if(scheduled != undefined) { clearTimeout(scheduled); } } + }; + } + + function text(message) { + var characters = message.split(''); + var frames = []; + var ready = false; + var append = function(c) { return function() { screen.appendText(c); }; }; + for(var i = 0; i < characters.length; i++) { + frames.push({action: append(characters[i]), delay: 50}); + } + frames.push({action: function() { ready = true; screen.markAsRead(); }, delay: null}); + var remote = animation(frames); + return function(f) { + buttons.push({ + 'A': function() { + if(ready) { + f(); + } else { + remote.skip(); + screen.text(message); + ready = true; + } + } + }); + screen.text(''); + remote.run(); + }; + } + + function menu(config) { + var cursor = 0; + var m = screen.menu(config.entries); + var sync = function() { screen.select(cursor); }; + var mapping = { + 'Up': function() { cursor = Math.max(0, cursor - 1); sync(); }, + 'Down': function() { cursor = Math.min(config.entries.length - 1, cursor + 1); sync(); }, + 'A': function() { config.entries[cursor].action(); } + }; + if(config.cancel != undefined) { + mapping['B'] = function() { buttons.pop(); screen.clear(m); config.cancel(); } + } + buttons.push(mapping); + } + + function run() { + intro(); + } + + function intro() { + var title = dom.make('p', {textContent: "P O K E M O N", class: 'title'}); + var subtitle = dom.make('p', {textContent: "< press start >", class: 'subtitle'}); + var remote = animation([ + {action: function() {}, delay: 500}, + {action: function() { screen.show(title); }, delay: 1000}, + {action: function() { screen.show(subtitle); }, delay: null} + ]); + buttons.map({ + 'B': function() { remote.skip(); intro() }, + 'Start': function() { + remote.skip(); + screen.frame(2); + screen.clear(); + menu(menus.start); + } + }); + screen.frame(1); + screen.clear(); + remote.run(); + } + + function newGame() { + screen.frame(3); + screen.clear(); + async.run( + text("Bonjour ! Bienvenue dans le monde merveilleux des pokémons !"), + text("Pour certains, les pokemons sont des amis. Pour d'autres, ils sont une ressource. "), + ); + } } diff --git a/src/buttons.js b/src/buttons.js new file mode 100644 index 0000000..1545c75 --- /dev/null +++ b/src/buttons.js @@ -0,0 +1,31 @@ +function Buttons(session) { + var mapping; + var mappingsStack = []; + var layout = session.getLayout(); + + document.addEventListener('keydown', function(event) { + var button = layout[event.key]; + if(button != undefined && mapping[button] != undefined) { + mapping[button](); + } + }); + + return { + map: map, + pop: pop, + push: push + }; + + function map(newMapping) { + mapping = newMapping; + } + + function pop() { + mappingsStack.pop(); + } + + function push(newMapping) { + mappingsStack.push(newMapping); + map(newMapping); + } +} diff --git a/src/index.html b/src/index.html index 6ac2f1f..9809b39 100644 --- a/src/index.html +++ b/src/index.html @@ -5,8 +5,10 @@ Pokemon Neige + + diff --git a/src/main.js b/src/main.js index 94a1fe7..2f981a4 100644 --- a/src/main.js +++ b/src/main.js @@ -1,9 +1,12 @@ window.addEventListener('load', function() { messaging = Messaging(); + async = Async(); dom = Dom(); screen = Screen(dom); session = Session(); - automaton = Automaton(messaging, screen, session); + buttons = Buttons(session); + automaton = Automaton(async, buttons, dom, messaging, screen, session); messaging.start(); + automaton.run(); }); diff --git a/src/screen.css b/src/screen.css index 29bfd4b..c7e17a7 100644 --- a/src/screen.css +++ b/src/screen.css @@ -1,3 +1,9 @@ +body { + background: #000; + font-family: mono; + line-height: 1.5em; +} + #screen { position: absolute; width: 400px; @@ -6,4 +12,60 @@ top: 50%; left: 50%; margin: -150px 0 0 -200px; + background: #fff; +} + +#screen .framed { + border: 3px double #000; + border-radius: 0.5em; +} + +#screen .text { + position: absolute; + bottom: 0; + left: 0; + right: 0; + height: 4.4em; +} + +#screen .text p { + margin: 0; + padding: 0.5em; +} + +ul.menu { + list-style: none; + padding: 0 0 0 1em; + margin: 0; +} + +ul.menu .selected { + background: #000; + color: #fff; +} + +/* 1st frame */ +#screen.frame1 * { + position: absolute; +} + +#screen.frame1 p { + width: 100%; + text-align: center; + margin: 0; + transform: translateY(-50%); +} + +#screen.frame1 .title { + font-size: 2em; + top: 50%; +} + +#screen.frame1 .subtitle { + top: 75%; +} + +/* 2nd frame */ +#screen.frame2 ul { + width: 7em; } diff --git a/src/screen.js b/src/screen.js index ee5ea58..bb48959 100644 --- a/src/screen.js +++ b/src/screen.js @@ -2,18 +2,74 @@ function Screen(dom) { var root = document.getElementById('screen'); return { - menu: menu + appendText: appendText, + clear: clear, + frame: frame, + markAsRead: markAsRead, + menu: menu, + select: select, + show: show, + text: text }; + function appendText(message) { + getTextZone().children[0].textContent += message; + } + + function clear(elem) { + if(elem != undefined) { + root.removeChild(elem); + } else { + dom.clear(root); + } + } + + function frame(n) { + root.className = "frame" + n; + } + + function getTextZone() { + var textZone = root.getElementsByClassName('text')[0]; + if(textZone == undefined) { + textZone = dom.make('div', {class: ['text', 'framed']}, [ + dom.make('p') + ]); + root.appendChild(textZone); + } + return textZone + } + + function markAsRead() { + getTextZone().classList.add('read'); + } + function menu(entries) { var domEntries = []; for(var i = 0; i < entries.length; i++) { domEntries.push(dom.make('li', { class: i == 0 ? 'selected' : [], - textContent: entries[i] + textContent: entries[i].label }, [])); } - var ul = dom.make('ul', {}, domEntries); + var ul = dom.make('ul', {class: ['menu', 'framed']}, domEntries); root.appendChild(ul); + return ul; + } + + function select(cursor) { + var ul = root.getElementsByClassName('menu')[0]; + ul.getElementsByClassName('selected')[0].className = ''; + ul.children[cursor].className = 'selected'; + } + + function show(elem) { + root.appendChild(elem); + } + + function text(message) { + getTextZone().children[0].textContent = message; + if(message.length > 0) { + markAsRead(); + } } } diff --git a/src/session.js b/src/session.js index 80435ea..0f96547 100644 --- a/src/session.js +++ b/src/session.js @@ -1,13 +1,15 @@ function Session() { var game = JSON.parse(localStorage.getItem('game')); + var layout = loadLayout(); return { - hasSavedGame: hasSavedGame, + getLayout: getLayout, + getGame: getGame, save : save }; - function hasSavedGame() { - return game != undefined; + function getGame() { + return game; } function update(state) { @@ -15,6 +17,28 @@ function Session() { } function save() { - localStorage.setItem('game', game); + localStorage.setItem('game', JSON.stringify(game)); + } + + function loadLayout() { + var saved = JSON.parse(localStorage.getItem('layout')); + if(saved == undefined) { + saved = { + 'a': 'A', + 'b': 'B', + 'ArrowLeft': 'Left', + 'ArrowRight': 'Right', + 'ArrowUp': 'Up', + 'ArrowDown': 'Down', + 'Enter': 'Start', + ' ': 'Select' + }; + localStorage.setItem('layout', JSON.stringify(saved)); + } + return saved; + } + + function getLayout() { + return layout; } }