From: Carl Worth <cworth@cworth.org> Date: Sun, 7 Jun 2020 18:56:35 +0000 (-0700) Subject: Initial framework for an Empathy game X-Git-Url: https://git.cworth.org/git?a=commitdiff_plain;h=53c8c6ba09301563293750e1010ca1ac8dd05590;p=lmno.games Initial framework for an Empathy game This starts with the generic code, (grabbed from Scribe), for displaying the game INFO, (with a button for copying the link), and the list of players that are in the game. It also provides a form for submitting a category, but that is still inert for now. --- diff --git a/empathy/.gitignore b/empathy/.gitignore new file mode 100644 index 0000000..053da87 --- /dev/null +++ b/empathy/.gitignore @@ -0,0 +1 @@ +empathy.js diff --git a/empathy/Makefile b/empathy/Makefile new file mode 100644 index 0000000..9f07401 --- /dev/null +++ b/empathy/Makefile @@ -0,0 +1,12 @@ +# Defer all targets up to the upper-level +# +# This requires two recipes. The first to cover the case of no +# explicit target specifed (so when invoked as "make" we call "make" +# at the upper-level) and then a .DEFAULT recipe to pass any explicit +# target up as well, (so that an invocation of "make foo" results in a +# call to "make foo" above. +all: + $(MAKE) -C .. + +.DEFAULT: + $(MAKE) -C .. $@ diff --git a/empathy/empathy.css b/empathy/empathy.css new file mode 100644 index 0000000..e8a317e --- /dev/null +++ b/empathy/empathy.css @@ -0,0 +1 @@ +/* Nothing to see here yet. */ diff --git a/empathy/empathy.jsx b/empathy/empathy.jsx new file mode 100644 index 0000000..9ebe9f3 --- /dev/null +++ b/empathy/empathy.jsx @@ -0,0 +1,217 @@ +function undisplay(element) { + element.style.display="none"; +} + +function add_message(severity, message) { + message = `<div class="message ${severity}" onclick="undisplay(this)"> +<span class="hide-button" onclick="undisplay(this.parentElement)">×</span> +${message} +</div>`; + const message_area = document.getElementById('message-area'); + message_area.insertAdjacentHTML('beforeend', message); +} + +/********************************************************* + * Handling server-sent event stream * + *********************************************************/ + +const events = new EventSource("events"); + +events.onerror = function(event) { + if (event.target.readyState === EventSource.CLOSED) { + setTimeout(() => { + add_message("danger", "Connection to server lost."); + }, 1000); + } +}; + +events.addEventListener("game-info", event => { + const info = JSON.parse(event.data); + + window.game.set_game_info(info); +}); + +events.addEventListener("player-info", event => { + const info = JSON.parse(event.data); + + window.game.set_player_info(info); +}); + +events.addEventListener("player-enter", event => { + const info = JSON.parse(event.data); + + window.game.set_other_player_info(info); +}); + +events.addEventListener("player-update", event => { + const info = JSON.parse(event.data); + + if (info.id === window.game.state.player_info.id) + window.game.set_player_info(info); + else + window.game.set_other_player_info(info); +}); + +/********************************************************* + * Game and supporting classes * + *********************************************************/ + +function copy_to_clipboard(id) +{ + const tmp = document.createElement("input"); + tmp.setAttribute("value", document.getElementById(id).innerHTML); + document.body.appendChild(tmp); + tmp.select(); + document.execCommand("copy"); + document.body.removeChild(tmp); +} + +function GameInfo(props) { + if (! props.id) + return null; + + return ( + <div className="game-info"> + <span className="game-id">{props.id}</span> + {" "} + Share this link to invite friends:{" "} + <span id="game-share-url">{props.url}</span> + {" "} + <button + className="inline" + onClick={() => copy_to_clipboard('game-share-url')} + >Copy Link</button> + </div> + ); +} + +function PlayerInfo(props) { + if (! props.player.id) + return null; + + return ( + <div className="player-info"> + <span className="players-header">Players: </span> + {props.player.name} + {props.other_players.map(other => ( + <span key={other.id}> + {", "} + {other.name} + </span> + ))} + </div> + ); +} + +function fetch_method_json(method, api = '', data = {}) { + const response = fetch(api, { + method: method, + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(data) + }); + return response; +} + +function fetch_post_json(api = '', data = {}) { + return fetch_method_json('POST', api, data); +} + +async function fetch_put_json(api = '', data = {}) { + return fetch_method_json('PUT', api, data); +} + +function CategoryRequest(props) { + return ( + <div className="category-request"> + <h2>Submit a Category</h2> + <p> + Suggest a category to play with your friends. Don't forget to + include the number of items for each person to submit. + </p> + + <form> + <div className="form-field large"> + <input + type="text" + id="category" + placeholder="6 things at the beach" + required pattern=".*[0-9]+.*" + title="Category must contain a number" + > + </input> + </div> + + <div className="form-field large"> + <button type="submit"> + Send + </button> + </div> + + </form> + </div> + ); +} + +class Game extends React.Component { + constructor(props) { + super(props); + this.state = { + game_info: {}, + player_info: {}, + other_players: [], + }; + } + + set_game_info(info) { + this.setState({ + game_info: info + }); + } + + set_player_info(info) { + this.setState({ + player_info: info + }); + } + + set_other_player_info(info) { + const other_players_copy = [...this.state.other_players]; + const idx = other_players_copy.findIndex(o => o.id === info.id); + if (idx >= 0) { + other_players_copy[idx] = info; + } else { + other_players_copy.push(info); + } + this.setState({ + other_players: other_players_copy + }); + } + + render() { + const state = this.state; + + return [ + <GameInfo + key="game-info" + id={state.game_info.id} + url={state.game_info.url} + />, + <PlayerInfo + key="player-info" + game={this} + player={state.player_info} + other_players={state.other_players} + />, + <p key="spacer"></p>, + <CategoryRequest + key="category-request" + /> + ]; + } +} + +ReactDOM.render(<Game + ref={(me) => window.game = me} + />, document.getElementById("empathy")); diff --git a/empathy/index.html b/empathy/index.html new file mode 100644 index 0000000..d250dc8 --- /dev/null +++ b/empathy/index.html @@ -0,0 +1,36 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8"/> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + + <title>The Game of Empires</title> + + <link rel="stylesheet" href="/reset.css" type="text/css" /> + <link rel="stylesheet" href="/style.css" type="text/css" /> + </head> + <body> + + <script src="/lmno.js"></script> + + <div id="page"> + + <h1>The Game of Empathy</h1> + + <p> + You don't need to be right, you just need to agree with your + friends. + </p> + + <div id="message-area"> + </div> + + <form onsubmit="lmno_new('empathy'); return false;"> + <button type="submit"> + Host a new game + </button> + </form> + + </div> + </body> +</html>