Import code from hanafuda-webapp and adapt it for SJW
This commit is contained in:
commit
f625b9954f
21 changed files with 1120 additions and 0 deletions
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
game.js
|
||||
index.js
|
||||
skin.css
|
17
Makefile
Normal file
17
Makefile
Normal file
|
@ -0,0 +1,17 @@
|
|||
PACKAGES=unitJS
|
||||
TARGETS=index.js game.js skin.css
|
||||
|
||||
.PHONY: mrproper
|
||||
|
||||
all: $(TARGETS)
|
||||
|
||||
%.js: js/
|
||||
sjw -o $@ $(PACKAGES:%=-I %) -m Main.$(@:%.js=%) $^
|
||||
|
||||
skin.css: skin/
|
||||
cat $^*.css > $@
|
||||
|
||||
rebuild: mrproper all
|
||||
|
||||
mrproper:
|
||||
rm -f $(TARGETS)
|
39
game.html
Normal file
39
game.html
Normal file
|
@ -0,0 +1,39 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>KoiKoi</title>
|
||||
<script src="game.js"></script>
|
||||
<link rel="stylesheet" href="skin.css" type="text/css"/>
|
||||
</head>
|
||||
<body>
|
||||
<div id="game">
|
||||
<div id="them">
|
||||
<ul class="hand"></ul>
|
||||
<ul class="kasu"></ul>
|
||||
<ul class="tane"></ul>
|
||||
<ul class="tan"></ul>
|
||||
<ul class="hikari"></ul>
|
||||
<span class="score"></span>
|
||||
</div>
|
||||
<div id="table">
|
||||
<ul id="deck">
|
||||
<li class="card init" id="rest"></li>
|
||||
</ul>
|
||||
<ul id="river"></ul>
|
||||
<ul id="status"></ul>
|
||||
</div>
|
||||
<div id="you">
|
||||
<ul class="hand"></ul>
|
||||
<ul class="kasu"></ul>
|
||||
<ul class="tane"></ul>
|
||||
<ul class="tan"></ul>
|
||||
<ul class="hikari"></ul>
|
||||
<span class="score"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div id="dialog">
|
||||
</div>
|
||||
<p id="error"></p>
|
||||
</body>
|
||||
</html>
|
41
index.html
Normal file
41
index.html
Normal file
|
@ -0,0 +1,41 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>KoiKoi</title>
|
||||
<script src="index.js"></script>
|
||||
<link rel="stylesheet" href="skin.css" type="text/css"/>
|
||||
</head>
|
||||
<body>
|
||||
<div id="reception" class="on">
|
||||
<h1>KoiKoi</h1>
|
||||
<form id="login">
|
||||
<input type="submit" name="submitButton" hidden disabled/>
|
||||
<p id="join">
|
||||
<label for="you"></label><input type="text" name="you"/>
|
||||
<input type="submit" name="join" disabled/>
|
||||
</p>
|
||||
</form>
|
||||
</div>
|
||||
<div id="hall">
|
||||
<form id="room">
|
||||
<input type="submit" name="submitButton" hidden disabled/>
|
||||
<p id="invite">
|
||||
<label for="them"></label><input type="text" name="them"/>
|
||||
<input type="submit" name="invite" disabled/>
|
||||
</p>
|
||||
<div class="listSelector" id="players">
|
||||
<span class="message"></span>
|
||||
<ul></ul>
|
||||
</div>
|
||||
</form>
|
||||
<div class="listSelector" id="games">
|
||||
<span class="message"></span>
|
||||
<ul></ul>
|
||||
</div>
|
||||
</div>
|
||||
<div id="dialog">
|
||||
</div>
|
||||
<p id="error"></p>
|
||||
</body>
|
||||
</html>
|
45
js/GUI/ConnectedForm.js
Normal file
45
js/GUI/ConnectedForm.js
Normal file
|
@ -0,0 +1,45 @@
|
|||
import Messaging;
|
||||
|
||||
var connectedForms = {};
|
||||
|
||||
Messaging.addEventListener('open', refreshForms);
|
||||
Messaging.addEventListener('close', refreshForms);
|
||||
|
||||
return {
|
||||
get: get
|
||||
};
|
||||
|
||||
function ConnectedForm(root) {
|
||||
var submits = root.querySelectorAll('[type=submit]');
|
||||
var enabled = false
|
||||
|
||||
return {
|
||||
enable: enable,
|
||||
refresh: refresh,
|
||||
root: root
|
||||
}
|
||||
|
||||
function enable(setEnabled) {
|
||||
enabled = setEnabled || undefined == setEnabled;
|
||||
refresh();
|
||||
}
|
||||
|
||||
function refresh() {
|
||||
submits.forEach(function(button) {
|
||||
button.disabled = !(Messaging.isOn() && enabled);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function get(formId) {
|
||||
if(connectedForms[formId] == undefined) {
|
||||
connectedForms[formId] = ConnectedForm(document.getElementById(formId));
|
||||
}
|
||||
return connectedForms[formId];
|
||||
}
|
||||
|
||||
function refreshForms() {
|
||||
for(var key in connectedForms) {
|
||||
connectedForms[key].refresh();
|
||||
}
|
||||
}
|
24
js/GUI/ListSelector.js
Normal file
24
js/GUI/ListSelector.js
Normal file
|
@ -0,0 +1,24 @@
|
|||
import * as Dom from UnitJS.Dom;
|
||||
|
||||
function ListSelector(domId, lineOfElement) {
|
||||
var root = document.getElementById(domId);
|
||||
var message = root.getElementsByClassName('message')[0];
|
||||
var list = root.getElementsByTagName('ul')[0];
|
||||
|
||||
return {
|
||||
message: message,
|
||||
refresh: refresh
|
||||
};
|
||||
|
||||
function refresh(sortedElements) {
|
||||
Dom.clear(list);
|
||||
message.textContent = '';
|
||||
sortedElements.forEach(function(element) {
|
||||
list.appendChild(lineOfElement(element));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
make: ListSelector
|
||||
};
|
49
js/GUI/Screen.js
Normal file
49
js/GUI/Screen.js
Normal file
|
@ -0,0 +1,49 @@
|
|||
import * as Dom from UnitJS.Dom;
|
||||
import I18n;
|
||||
|
||||
var current = document.querySelector("body > div.on");
|
||||
var errorBox = document.getElementById('error');
|
||||
errorBox.addEventListener('click', function() {
|
||||
errorBox.className = "";
|
||||
});
|
||||
|
||||
return {
|
||||
error: error,
|
||||
dialog: dialog,
|
||||
select: select
|
||||
};
|
||||
|
||||
function select(name) {
|
||||
current.className = "";
|
||||
current = document.getElementById(name);
|
||||
current.className = "on";
|
||||
}
|
||||
|
||||
function closeAndRun(dialog, action) {
|
||||
return function() {
|
||||
dialog.className = '';
|
||||
Dom.clear(dialog);
|
||||
action();
|
||||
};
|
||||
}
|
||||
|
||||
function dialog(config) {
|
||||
var layer = document.getElementById('dialog');
|
||||
var dialog = Dom.make('div', {});
|
||||
dialog.appendChild(Dom.make('p', {textContent: config.text}));
|
||||
var answers = Dom.make('p', {class: 'answers'});
|
||||
for(var i in config.answers) {
|
||||
answers.appendChild(Dom.make('button', {
|
||||
textContent: I18n.get(config.answers[i].label),
|
||||
onClick: closeAndRun(layer, config.answers[i].action)
|
||||
}));
|
||||
}
|
||||
dialog.appendChild(answers);
|
||||
layer.appendChild(dialog);
|
||||
layer.className = "on";
|
||||
}
|
||||
|
||||
function error(message) {
|
||||
errorBox.textContent = message;
|
||||
errorBox.className = "on";
|
||||
}
|
140
js/GUI/Screen/Hall.js
Normal file
140
js/GUI/Screen/Hall.js
Normal file
|
@ -0,0 +1,140 @@
|
|||
import * as Dom from UnitJS.Dom;
|
||||
import I18n;
|
||||
import * as ConnectedForm from GUI.ConnectedForm;
|
||||
import * as ListSelector from GUI.ListSelector;
|
||||
import Messaging;
|
||||
import room as players from Room;
|
||||
import Session;
|
||||
import StatusHandler;
|
||||
import Table;
|
||||
|
||||
var room = ConnectedForm.get('room');
|
||||
var form = room.root;
|
||||
|
||||
var playersList = ListSelector.make('players', showPlayer);
|
||||
var games = Table.make(game, 'date');
|
||||
var gamesList = ListSelector.make('games', showGame);
|
||||
var them = null;
|
||||
|
||||
return {
|
||||
init: init
|
||||
};
|
||||
|
||||
function init() {
|
||||
initDOMEvents();
|
||||
initMessageHandlers();
|
||||
}
|
||||
|
||||
function initDOMEvents() {
|
||||
form.addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
Messaging.send({tag: "Invitation", to: them});
|
||||
});
|
||||
form.them.addEventListener("input", function() {refreshPlayers();});
|
||||
}
|
||||
|
||||
function initMessageHandlers() {
|
||||
Messaging.addEventListener(["Okaeri"], function(o) {
|
||||
refresh();
|
||||
});
|
||||
Messaging.addEventListener(["Welcome"], function(o) {
|
||||
refresh();
|
||||
});
|
||||
Messaging.addEventListener(["LogIn"], function(o) {
|
||||
if(!Session.is(o.from)) {
|
||||
refresh();
|
||||
}
|
||||
});
|
||||
Messaging.addEventListener(["LogOut"], function(o) {
|
||||
refresh();
|
||||
});
|
||||
Messaging.addEventListener(["Relay", "Invitation"], function(o) {
|
||||
var from = players.get(o.from);
|
||||
// invitations should come only from known players, in doubt say «no»
|
||||
if(from != undefined && from.name) {
|
||||
StatusHandler.set("🎴");
|
||||
games.insert(o.from, from.name);
|
||||
refreshGames();
|
||||
} else {
|
||||
Messaging.send({tag: "Answer", accept: false});
|
||||
}
|
||||
});
|
||||
Messaging.addEventListener(["Relay", "Answer"], function(o) {
|
||||
games.remove(o.from);
|
||||
refreshGames();
|
||||
/*
|
||||
if(o.message.accept) {
|
||||
modules.screen.select("game");
|
||||
}
|
||||
*/
|
||||
});
|
||||
Messaging.addEventListener(["Game"], function(o) {
|
||||
});
|
||||
}
|
||||
|
||||
function showPlayer(player) {
|
||||
return Dom.make('li', {
|
||||
textContent: player.name,
|
||||
onClick: function() {form.them.value = player.name; refreshPlayers();},
|
||||
class: 'player'
|
||||
});
|
||||
}
|
||||
|
||||
function game(key, vs) {
|
||||
return {
|
||||
key: key,
|
||||
vs: vs,
|
||||
date: Date.now()
|
||||
};
|
||||
}
|
||||
|
||||
function showGame(game) {
|
||||
return Dom.make('li', {}, [
|
||||
Dom.make('button', {
|
||||
textContent: I18n.get('accept'),
|
||||
onClick: function() {
|
||||
Messaging.send({tag: "Answer", accept: true, to: game.key});
|
||||
}
|
||||
}),
|
||||
Dom.make('span', {textContent: 'A game vs. ' + game.vs}),
|
||||
]);
|
||||
}
|
||||
|
||||
function refresh() {
|
||||
refreshPlayers();
|
||||
refreshGames();
|
||||
}
|
||||
|
||||
function refreshPlayers() {
|
||||
var name = form.them.value;
|
||||
them = null;
|
||||
var filtered = players.getAll(
|
||||
function(player) {return player.name.match(name);}
|
||||
);
|
||||
playersList.refresh(filtered);
|
||||
var exact = filtered.find(exactMatch(name));
|
||||
if(exact != undefined) {
|
||||
them = exact.key;
|
||||
} else if(filtered.length == 1) {
|
||||
them = filtered[0].key;
|
||||
} else if(filtered.length == 0) {
|
||||
playersList.message.textContent = I18n.get(
|
||||
name.length > 0 ? "notFound" : "alone"
|
||||
);
|
||||
}
|
||||
room.enable(them != undefined);
|
||||
}
|
||||
|
||||
function exactMatch(name) {
|
||||
return function(player) {
|
||||
return player.name === name;
|
||||
};
|
||||
}
|
||||
|
||||
function refreshGames() {
|
||||
var sortedGames = games.getAll();
|
||||
gamesList.refresh(sortedGames);
|
||||
if(sortedGames.length < 1) {
|
||||
gamesList.message.textContent = I18n.get('noGames');
|
||||
}
|
||||
}
|
45
js/GUI/Screen/Login.js
Normal file
45
js/GUI/Screen/Login.js
Normal file
|
@ -0,0 +1,45 @@
|
|||
import I18n;
|
||||
import GUI.ConnectedForm;
|
||||
import select from GUI.Screen;
|
||||
import Messaging;
|
||||
import Session;
|
||||
import Save;
|
||||
|
||||
var login = GUI.ConnectedForm.get('login');
|
||||
var form = login.root;
|
||||
|
||||
return {
|
||||
init: init
|
||||
};
|
||||
|
||||
function init() {
|
||||
initDOM();
|
||||
initMessageHandlers();
|
||||
var name = Save.get('player.name');
|
||||
if(name != undefined && name.length > 0) {
|
||||
form.you.value = name;
|
||||
login.enable();
|
||||
}
|
||||
}
|
||||
|
||||
function initDOM() {
|
||||
form.getElementsByTagName('label')[0].textContent = I18n.get('pickName');
|
||||
form.join.value = I18n.get('join');
|
||||
form.addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
Session.start(form.you.value);
|
||||
});
|
||||
form.you.addEventListener("input", validate);
|
||||
}
|
||||
|
||||
function initMessageHandlers() {
|
||||
Messaging.addEventListener(["LogIn"], function(o) {
|
||||
if(Session.is(o.from)) {
|
||||
select('hall');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function validate(e) {
|
||||
login.enable(e.target.value != "");
|
||||
}
|
32
js/I18n.js
Normal file
32
js/I18n.js
Normal file
|
@ -0,0 +1,32 @@
|
|||
import Translations;
|
||||
|
||||
var language = chooseLanguage();
|
||||
|
||||
return {
|
||||
get: get
|
||||
};
|
||||
|
||||
function chooseLanguage() {
|
||||
var userPreference = navigator.language || navigator.userLanguage;
|
||||
if(userPreference != undefined) {
|
||||
if(Translations[userPreference] != undefined) {
|
||||
return userPreference;
|
||||
}
|
||||
var lang = userPreference.replace(/-.*/, '');
|
||||
for(var key in Translations) {
|
||||
if(key.replace(/-.*/, '') == lang) {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(Translations['en-US'] != undefined) {
|
||||
return 'en-US';
|
||||
}
|
||||
for(var key in Translations) {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
||||
function get(textId) {
|
||||
return Translations[language][textId] || ('TRANSLATE "'+textId+'" !!');
|
||||
}
|
127
js/Messaging.js
Normal file
127
js/Messaging.js
Normal file
|
@ -0,0 +1,127 @@
|
|||
import error as popError from GUI.Screen;
|
||||
|
||||
var wsLocation = window.location.origin.replace(/^http/, 'ws') + '/play/';
|
||||
var ws;
|
||||
var debug = getParameters().debug;
|
||||
var doLog = debug != undefined && debug.match(/^(?:1|t(?:rue)?|v(?:rai)?)$/i);
|
||||
var on = false;
|
||||
var s = 1000; /* ms */
|
||||
var keepAlivePeriod = 20;
|
||||
var reconnectDelay = 1;
|
||||
var routes = {callbacks: [], children: {}};
|
||||
var wsHandlers = {
|
||||
open: [function() {on = true; reconnectDelay = 1}, ping],
|
||||
close: [function() {on = false;}, reconnect]
|
||||
};
|
||||
|
||||
init();
|
||||
|
||||
return {
|
||||
addEventListener: addEventListener,
|
||||
isOn: isOn,
|
||||
send: send
|
||||
};
|
||||
|
||||
function get(obj, path, write) {
|
||||
write = write || false;
|
||||
if(path.length < 1) {
|
||||
return obj;
|
||||
} else {
|
||||
if(obj.children[path[0]] == undefined && write) {
|
||||
obj.children[path[0]] = {callbacks: [], children: {}};
|
||||
}
|
||||
if(obj.children[path[0]] != undefined) {
|
||||
return get(obj.children[path[0]], path.slice(1), write);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getParameters() {
|
||||
var o = {};
|
||||
window.location.search.substr(1).split('&').forEach(function(s) {
|
||||
var t = s.split('=');
|
||||
o[t[0]] = t[1];
|
||||
});
|
||||
return o;
|
||||
}
|
||||
|
||||
function addEventListener(path, callback) {
|
||||
if(Array.isArray(path)) {
|
||||
var route = get(routes, path, true);
|
||||
route.callbacks.push(callback);
|
||||
} else {
|
||||
if(wsHandlers[path] != undefined) {
|
||||
wsHandlers[path].push(callback);
|
||||
} else {
|
||||
log('Unsupported websocket event "' + path + '"');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function messageListener(event) {
|
||||
var o = JSON.parse(event.data);
|
||||
var path = [];
|
||||
var tmp = o;
|
||||
while(tmp != undefined && tmp.tag != undefined) {
|
||||
path.push(tmp.tag);
|
||||
tmp = tmp.message;
|
||||
}
|
||||
var route = get(routes, path);
|
||||
if(route != undefined && route.callbacks != undefined) {
|
||||
route.callbacks.forEach(function(f) {f(o);});
|
||||
} else {
|
||||
console.log("No route found for " + event.data);
|
||||
}
|
||||
o.direction = 'client < server';
|
||||
log(o);
|
||||
};
|
||||
|
||||
function send(o) {
|
||||
ws.send(JSON.stringify(o));
|
||||
o.direction = 'client > server';
|
||||
log(o);
|
||||
}
|
||||
|
||||
function log(message) {
|
||||
if(doLog) {
|
||||
console.log(message);
|
||||
}
|
||||
}
|
||||
|
||||
function init() {
|
||||
connect();
|
||||
addEventListener(["Pong"], ping);
|
||||
addEventListener(["Error"], function(o) {popError(o.error);});
|
||||
}
|
||||
|
||||
function connect() {
|
||||
ws = new WebSocket(window.location.origin.replace(/^http/, 'ws') + '/play/');
|
||||
ws.addEventListener('message', messageListener);
|
||||
ws.addEventListener('open', function(e) {
|
||||
wsHandlers.open.forEach(function(handler) {handler(e);});
|
||||
});
|
||||
ws.addEventListener('close', function(e) {
|
||||
wsHandlers.close.forEach(function(handler) {handler(e);});
|
||||
});
|
||||
}
|
||||
|
||||
function reconnect() {
|
||||
setTimeout(connect, reconnectDelay * s);
|
||||
if(reconnectDelay < 16) {
|
||||
reconnectDelay *= 2;
|
||||
}
|
||||
}
|
||||
|
||||
function isOn() {
|
||||
return on;
|
||||
}
|
||||
|
||||
function ping() {
|
||||
setTimeout(function() {
|
||||
if(isOn()) {
|
||||
send({tag: "Ping"});
|
||||
}
|
||||
}, keepAlivePeriod * s);
|
||||
}
|
34
js/Room.js
Normal file
34
js/Room.js
Normal file
|
@ -0,0 +1,34 @@
|
|||
import Messaging;
|
||||
import Session;
|
||||
import Table;
|
||||
|
||||
var room = Table.make(player, 'name');
|
||||
initMessageHandlers();
|
||||
|
||||
return {
|
||||
room: room
|
||||
};
|
||||
|
||||
function player(key, name) {
|
||||
return {
|
||||
key: key,
|
||||
name: name
|
||||
};
|
||||
}
|
||||
|
||||
function initMessageHandlers() {
|
||||
Messaging.addEventListener(["Okaeri"], function(o) {
|
||||
room.insertAll(o.room);
|
||||
});
|
||||
Messaging.addEventListener(["Welcome"], function(o) {
|
||||
room.insertAll(o.room);
|
||||
});
|
||||
Messaging.addEventListener(["LogIn"], function(o) {
|
||||
if(!Session.is(o.from)) {
|
||||
room.insert(o.from, o.as);
|
||||
}
|
||||
});
|
||||
Messaging.addEventListener(["LogOut"], function(o) {
|
||||
room.remove(o.from);
|
||||
});
|
||||
}
|
46
js/Save.js
Normal file
46
js/Save.js
Normal file
|
@ -0,0 +1,46 @@
|
|||
var save = JSON.parse(localStorage.getItem('save')) || {};
|
||||
|
||||
return {
|
||||
get: get,
|
||||
set: set
|
||||
};
|
||||
|
||||
function move(coordinates) {
|
||||
if(coordinates.path.length == 1) {
|
||||
return coordinates;
|
||||
} else {
|
||||
var newFocus = coordinates.focus[coordinates.path[0]];
|
||||
if (newFocus != undefined) {
|
||||
var newCoordinates = {path: coordinates.path.slice(1), focus: newFocus};
|
||||
return move(newCoordinates);
|
||||
} else {
|
||||
return coordinates;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function get(key) {
|
||||
if(key != undefined) {
|
||||
var outputCoordinates = move({path: key.split('.'), focus: save});
|
||||
if(outputCoordinates.focus != undefined && outputCoordinates.path.length == 1) {
|
||||
return outputCoordinates.focus[outputCoordinates.path[0]]
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function set(key, value) {
|
||||
if(key != undefined) {
|
||||
var outputCoordinates = move({path: key.split('.'), focus: save});
|
||||
while(outputCoordinates.path.length > 1) {
|
||||
outputCoordinates.focus[outputCoordinates.path[0]] = {};
|
||||
outputCoordinates.focus = outputCoordinates.focus[outputCoordinates.path[0]];
|
||||
outputCoordinates.path = outputCoordinates.path.slice(1);
|
||||
}
|
||||
outputCoordinates.focus[outputCoordinates.path[0]] = value;
|
||||
} else {
|
||||
save = value;
|
||||
}
|
||||
localStorage.setItem('save', JSON.stringify(save));
|
||||
}
|
49
js/Session.js
Normal file
49
js/Session.js
Normal file
|
@ -0,0 +1,49 @@
|
|||
import Messaging;
|
||||
import Save;
|
||||
|
||||
var key = null;
|
||||
var playerKey = null;
|
||||
var name = null;
|
||||
var loggedIn = false;
|
||||
|
||||
Messaging.addEventListener(["Welcome"], function(o) {
|
||||
playerKey = o.key;
|
||||
Save.set('player.id', o.key);
|
||||
});
|
||||
|
||||
Messaging.addEventListener(["LogIn"], function(o) {
|
||||
if(is(o.from)) {
|
||||
name = o.as;
|
||||
loggedIn = true;
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
is: is,
|
||||
getKey: getKey,
|
||||
isLoggedIn: isLoggedIn,
|
||||
start: start
|
||||
};
|
||||
|
||||
function is(somePlayerKey) {
|
||||
return playerKey == somePlayerKey;
|
||||
}
|
||||
|
||||
function getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
function isLoggedIn() {
|
||||
return loggedIn;
|
||||
}
|
||||
|
||||
function start(name) {
|
||||
var myID = Save.get('player.id');
|
||||
if(myID != undefined) {
|
||||
Messaging.send({tag: 'Tadaima', myID: myID, name: name});
|
||||
playerKey = myID;
|
||||
} else {
|
||||
Messaging.send({tag: 'Hello', name: name});
|
||||
}
|
||||
Save.set('player.name', name);
|
||||
}
|
17
js/StatusHandler.js
Normal file
17
js/StatusHandler.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
var baseTitle = document.title;
|
||||
window.addEventListener('focus', reset);
|
||||
|
||||
return {
|
||||
reset: reset,
|
||||
set: set
|
||||
};
|
||||
|
||||
function reset() {
|
||||
document.title = baseTitle;
|
||||
}
|
||||
|
||||
function set(newStatus) {
|
||||
if(!document.hasFocus()) {
|
||||
document.title = newStatus + " - " + baseTitle;
|
||||
}
|
||||
}
|
42
js/Table.js
Normal file
42
js/Table.js
Normal file
|
@ -0,0 +1,42 @@
|
|||
import of from UnitJS.Fun;
|
||||
import {compare, of, proj} from UnitJS.Fun;
|
||||
|
||||
function Table(itemMaker, sortCriterion) {
|
||||
var items = {};
|
||||
return {
|
||||
get: get,
|
||||
getAll: getAll,
|
||||
insert: insert,
|
||||
insertAll: insertAll,
|
||||
remove: remove
|
||||
};
|
||||
|
||||
function get(key) {
|
||||
return items[key];
|
||||
}
|
||||
|
||||
function getAll(criterion) {
|
||||
return Object.keys(items)
|
||||
.map(of(items))
|
||||
.filter(criterion || function() {return true;})
|
||||
.sort(compare(proj(sortCriterion)));
|
||||
}
|
||||
|
||||
function insert(key, value) {
|
||||
items[key] = itemMaker(key, value);
|
||||
}
|
||||
|
||||
function insertAll(itemsByKey) {
|
||||
for(var key in itemsByKey) {
|
||||
insert(key, itemsByKey[key]);
|
||||
}
|
||||
}
|
||||
|
||||
function remove(key) {
|
||||
delete items[key];
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
make: Table
|
||||
}
|
90
js/Translations.js
Normal file
90
js/Translations.js
Normal file
|
@ -0,0 +1,90 @@
|
|||
return {
|
||||
'en-US': {
|
||||
BushClover: "bush clover",
|
||||
Cherry: "cherry",
|
||||
Chrysanthemum: "chrysanthemum",
|
||||
Iris: "iris",
|
||||
Maple: "maple",
|
||||
Paulownia: "paulownias",
|
||||
Peony: "peony",
|
||||
Pine: "pine",
|
||||
Plum: "plum",
|
||||
SusukiGrass: "susuki grass",
|
||||
Willow: "willow",
|
||||
Wisteria: "wisteria",
|
||||
accept: "Accept",
|
||||
alone: "No one to play with yet ! Wait a little",
|
||||
decline: "Decline",
|
||||
endRound: "End the round",
|
||||
endGame: "Return to main menu",
|
||||
join: "Join",
|
||||
invite: "Invite",
|
||||
invited: function(name) {
|
||||
return name + " has invited you to a game";
|
||||
},
|
||||
koikoi: "KoiKoi !!",
|
||||
leave: "Leave",
|
||||
lost: "You lost the game",
|
||||
monthFlower: function(flower) {
|
||||
return "This month's flower is the " + flower;
|
||||
},
|
||||
noGames: "No games being played",
|
||||
notFound: "No one goes by that name",
|
||||
pickName: "Pick a name you like",
|
||||
playing: function(name) {
|
||||
return name + " is playing";
|
||||
},
|
||||
ok: "Ok",
|
||||
startGameWith: "Start a game with",
|
||||
theyScored: function(name) {
|
||||
return name + " scored";
|
||||
},
|
||||
won: "You won !",
|
||||
yourTurn: "Your turn",
|
||||
youScored: "You scored ! Do you want to get your points and end the round or KoiKoi ?"
|
||||
},
|
||||
'fr-FR': {
|
||||
BushClover: "lespédézas",
|
||||
Cherry: "cerisiers",
|
||||
Chrysanthemum: "chrysanthèmes",
|
||||
Iris: "iris",
|
||||
Maple: "érables",
|
||||
Paulownia: "paulownias",
|
||||
Peony: "pivoines",
|
||||
Pine: "pins",
|
||||
Plum: "prunus",
|
||||
SusukiGrass: "herbes susukis",
|
||||
Willow: "saules",
|
||||
Wisteria: "glycines",
|
||||
accept: "Accepter",
|
||||
alone: "Personne pour jouer pour l'instant ! Attendez un peu",
|
||||
decline: "Refuser",
|
||||
endRound: "Finir la manche",
|
||||
endGame: "Retourner au menu principal",
|
||||
join: "Entrer",
|
||||
invite: "Inviter",
|
||||
invited: function(name) {
|
||||
return name + " vous propose une partie";
|
||||
},
|
||||
koikoi: "KoiKoi !!",
|
||||
leave: "Partir",
|
||||
lost: "Vous avez perdu",
|
||||
monthFlower: function(flower) {
|
||||
return "C'est le mois des " + flower;
|
||||
},
|
||||
noGames: "Aucune partie en cours",
|
||||
notFound: "Personne ne s'appelle comme ça",
|
||||
pickName: "Choisissez votre nom",
|
||||
playing: function(name) {
|
||||
return "C'est à " + name;
|
||||
},
|
||||
ok: "Ok",
|
||||
startGameWith: "Commencer une partie avec",
|
||||
theyScored: function(name) {
|
||||
return name + " a marqué";
|
||||
},
|
||||
won: "Vous avez gagné !",
|
||||
yourTurn: "À vous",
|
||||
youScored: "Vous avez marqué ! Voulez-vous empocher vos gains et terminer la manche ou faire KoiKoi ?"
|
||||
}
|
||||
}
|
BIN
skin/cards.jpg
Normal file
BIN
skin/cards.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 711 KiB |
201
skin/game.css
Normal file
201
skin/game.css
Normal file
|
@ -0,0 +1,201 @@
|
|||
#game > div {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
#them {
|
||||
top: 0;
|
||||
bottom: 75%;
|
||||
}
|
||||
|
||||
#table {
|
||||
top: 25%;
|
||||
bottom: 25%;
|
||||
}
|
||||
|
||||
#you {
|
||||
top: 75%;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
#game .card {
|
||||
background: url("/cards.jpg") no-repeat;
|
||||
background-size: 400% 1300%;
|
||||
display: inline-block;
|
||||
border-radius: 0.5em;
|
||||
border: 1px solid #555;
|
||||
width: 4.5em;
|
||||
height: 7em;
|
||||
float: left;
|
||||
margin: 0.5em;
|
||||
background-position: 0% 100%; /* back of the card */
|
||||
}
|
||||
|
||||
#game .card.value0 {
|
||||
background-position-x: 0%;
|
||||
}
|
||||
|
||||
#game .card.value1 {
|
||||
background-position-x: 33.3%;
|
||||
}
|
||||
|
||||
#game .card.value2 {
|
||||
background-position-x: 66.7%;
|
||||
}
|
||||
|
||||
#game .card.value3 {
|
||||
background-position-x: 100%;
|
||||
}
|
||||
|
||||
#game .card.month0 {
|
||||
background-position-y: 0%;
|
||||
}
|
||||
|
||||
#game .card.month1 {
|
||||
background-position-y: 8.3%;
|
||||
}
|
||||
|
||||
#game .card.month2 {
|
||||
background-position-y: 16.7%;
|
||||
}
|
||||
|
||||
#game .card.month3 {
|
||||
background-position-y: 25%;
|
||||
}
|
||||
|
||||
#game .card.month4 {
|
||||
background-position-y: 33.3%;
|
||||
}
|
||||
|
||||
#game .card.month5 {
|
||||
background-position-y: 41.7%;
|
||||
}
|
||||
|
||||
#game .card.month6 {
|
||||
background-position-y: 50%;
|
||||
}
|
||||
|
||||
#game .card.month7 {
|
||||
background-position-y: 58.3%;
|
||||
}
|
||||
|
||||
#game .card.month8 {
|
||||
background-position-y: 66.7%;
|
||||
}
|
||||
|
||||
#game .card.month9 {
|
||||
background-position-y: 75%;
|
||||
}
|
||||
|
||||
#game .card.month10 {
|
||||
background-position-y: 83.3%;
|
||||
}
|
||||
|
||||
#game .card.month11 {
|
||||
background-position-y: 91.7%;
|
||||
}
|
||||
|
||||
#game .card.slot {
|
||||
background: none;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
#game .card.moving {
|
||||
position: relative;
|
||||
transition-property: left, top;
|
||||
transition-duration: 1s;
|
||||
}
|
||||
|
||||
#game #rest {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#rest.init, #rest.turn0 {
|
||||
box-shadow: 2px 3px 0 0 #555, 4px 6px 0 0 #555, 6px 9px 0 0 #555;
|
||||
}
|
||||
|
||||
#rest.turn2 {
|
||||
box-shadow: 2px 3px 0 0 #555, 4px 6px 0 0 #555, 5.5px 8.3px 0 0 #555;
|
||||
}
|
||||
|
||||
#rest.turn4 {
|
||||
box-shadow: 2px 3px 0 0 #555, 4px 6px 0 0 #555, 5px 7.5px 0 0 #555;
|
||||
}
|
||||
|
||||
#rest.turn6 {
|
||||
box-shadow: 2px 3px 0 0 #555, 4px 6px 0 0 #555, 4.5px 6.8px 0 0 #555;
|
||||
}
|
||||
|
||||
#rest.turn8 {
|
||||
box-shadow: 2px 3px 0 0 #555, 4px 6px 0 0 #555;
|
||||
}
|
||||
|
||||
#rest.turn10 {
|
||||
box-shadow: 2px 3px 0 0 #555, 3.5px 5.3px 0 0 #555;
|
||||
}
|
||||
|
||||
#rest.turn12 {
|
||||
box-shadow: 2px 3px 0 0 #555, 3px 4.5px 0 0 #555;
|
||||
}
|
||||
|
||||
#rest.turn14 {
|
||||
box-shadow: 2px 3px 0 0 #555, 2.5px 3.8px 0 0 #555;
|
||||
}
|
||||
|
||||
#rest.turn16 {
|
||||
box-shadow: 2px 3px 0 0 #555;
|
||||
}
|
||||
|
||||
#game #turned {
|
||||
margin: -0.1em 0 0 -4.75em;
|
||||
}
|
||||
|
||||
#river li.card.candidate, #you .hand.yourTurn li.card {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#river li.card.candidate {
|
||||
box-shadow: 0 0 0.5em 0.1em #fc0;
|
||||
}
|
||||
|
||||
#you .hand li.card.selected {
|
||||
margin-top: -1em;
|
||||
}
|
||||
|
||||
#game ul {
|
||||
display: inline-block;
|
||||
margin: 0 0 0 3.5em;
|
||||
padding: 0;
|
||||
float: right;
|
||||
position: relative;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
#game ul#deck {
|
||||
float: left;
|
||||
}
|
||||
|
||||
#game ul#river {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
margin-left: 0;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
#them .card, #you .card {
|
||||
margin-left: -3.5em;
|
||||
}
|
||||
|
||||
#game .hand {
|
||||
margin-left: 0;
|
||||
float: left;
|
||||
}
|
||||
|
||||
#game .hand .card {
|
||||
margin: 0.5em 0.1em;
|
||||
}
|
25
skin/hall.css
Normal file
25
skin/hall.css
Normal file
|
@ -0,0 +1,25 @@
|
|||
.listSelector {
|
||||
min-height: 4em;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.listSelector .message {
|
||||
display: block;
|
||||
text-align: center;
|
||||
margin: 1em;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.listSelector .message:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.listSelector ul {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.listSelector .player {
|
||||
cursor: pointer;
|
||||
}
|
54
skin/screen.css
Normal file
54
skin/screen.css
Normal file
|
@ -0,0 +1,54 @@
|
|||
body > div {
|
||||
display: none;
|
||||
}
|
||||
|
||||
body > div.on {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#dialog {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
#dialog > div {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background: #fff;
|
||||
padding: 0 1em;
|
||||
border: 1px solid #aaa;
|
||||
border-radius: 0.5em;
|
||||
}
|
||||
|
||||
#dialog p.answers {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#dialog button {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
#error {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
top: 1em;
|
||||
right: 1em;
|
||||
max-width: 20em;
|
||||
border: 1px solid #e0afac;
|
||||
padding: 1em;
|
||||
border-radius: 0.5em;
|
||||
background: bisque;
|
||||
cursor: pointer;
|
||||
margin: 0;
|
||||
display: none;
|
||||
}
|
||||
|
||||
#error.on {
|
||||
display: block;
|
||||
}
|
Loading…
Reference in a new issue