diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..10aa6cb
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,4 @@
+language: node_js
+node_js:
+ - 6.6
+
diff --git a/commandRunner.js b/commandRunner.js
index 762e18d..35b2bad 100644
--- a/commandRunner.js
+++ b/commandRunner.js
@@ -1,99 +1,100 @@
-var EventEmitter = require("events").EventEmitter;
-var util = require("util");
+import ComponentBase from "./componentBase";
-function CommandRunner(key) {
- EventEmitter.call(this);
+export default class CommandRunner extends ComponentBase {
+ constructor(name) {
+ super();
- this.key = key;
- this.commands = [];
- this.baseSpeed = 0;
- this.angle = 0;
- this.loopTimeoutId = null;
- this.customTimeoutIds = {};
- this.backgroundTimeoutIds = {};
- this.commandFunctions = {
- rotate: (config, turn) => {
- if (config.isBuiltIn) {
- this.stopLoop();
- this.clearCustomTimeoutIds();
+ this.name = name;
+ this.commands = [];
+ this.baseSpeed = 0;
+ this.angle = 0;
+ this.loopTimeoutId = null;
+ this.customTimeoutIds = {};
+ this.backgroundTimeoutIds = {};
+ this.commandFunctions = {
+ rotate: (config, turn) => {
+ if (config.isBuiltIn) {
+ this.stopLoop();
+ this.clearCustomTimeoutIds();
+ }
+ const rotateFunction = () => {
+ this.angle = (this.angle + turn) % 360;
+ this.publish("command", name, "roll", [0, this.angle]);
+ this.customTimeoutIds.rotate = setTimeout(rotateFunction, 500);
+ };
+ rotateFunction();
+ },
+ stop: config => {
+ if (config.isBuiltIn) {
+ this.stopLoop();
+ this.clearCustomTimeoutIds();
+ }
+ this.publish("command", name, "roll", [0, this.angle]);
+ },
+ dash: (config, baseSpeed, dashTime) => {
+ if (this.backgroundTimeoutIds.dash) {
+ clearTimeout(this.backgroundTimeoutIds.dash);
+ }
+ this.baseSpeed = baseSpeed;
+ this.backgroundTimeoutIds.dash = setTimeout(() => {
+ this.baseSpeed = 0;
+ }, dashTime * 1000);
+ },
+ roll: (config, speed, degree) => {
+ this.publish("command", name, "roll", [
+ this.baseSpeed + speed,
+ (360 + degree + this.angle) % 360
+ ]);
}
- const rotateFunction = () => {
- this.angle = (this.angle + turn) % 360;
- this.emit("command", "roll", [0, this.angle]);
- this.customTimeoutIds.rotate = setTimeout(rotateFunction, 500);
- };
- rotateFunction();
- },
- stop: config => {
- if (config.isBuiltIn) {
- this.stopLoop();
- this.clearCustomTimeoutIds();
- }
- this.emit("command", "roll", [0, this.angle]);
- },
- dash: (config, baseSpeed, dashTime) => {
- if (this.backgroundTimeoutIds.dash !== null) {
- clearTimeout(this.backgroundTimeoutIds.dash);
- }
- this.baseSpeed = baseSpeed;
- this.backgroundTimeoutIds.dash = setTimeout(() => {
- this.baseSpeed = 0;
- }, dashTime * 1000);
- },
- roll: (config, speed, degree) => {
- this.emit("command", "roll", [
- this.baseSpeed + speed,
- (360 + degree + this.angle) % 360
- ]);
- }
- };
-}
-
-util.inherits(CommandRunner, EventEmitter);
+ };
+ }
-CommandRunner.prototype.setCommands = function(commands) {
- if (commands.length === 1 && commands[0].time === -1) {
- // built-in command
- this.commandFunctions[commands[0].commandName].apply(this, [{
- isBuiltIn: true
- }].concat(processArguments(commands[0].args)));
- } else {
- this.commands = commands;
- this.stopLoop();
- this.clearCustomTimeoutIds();
- this.loopMethod(0);
+ setCommands(commands) {
+ if (commands.length === 1 && commands[0].time === -1) {
+ // built-in command
+ this.commandFunctions[commands[0].commandName].apply(this, [{
+ isBuiltIn: true
+ }].concat(processArguments(commands[0].args)));
+ } else {
+ this.commands = commands;
+ this.stopLoop();
+ this.clearCustomTimeoutIds();
+ this.loopMethod(0);
+ }
}
-};
-CommandRunner.prototype.stopLoop = function() {
- if (this.loopTimeoutId !== null) {
- clearTimeout(this.loopTimeoutId);
+ stopLoop() {
+ if (this.loopTimeoutId) {
+ clearTimeout(this.loopTimeoutId);
+ this.loopTimeoutId = null;
+ }
}
-};
-CommandRunner.prototype.clearCustomTimeoutIds = function() {
- Object.keys(this.customTimeoutIds).forEach(timeoutIdName => {
- const timeoutId = this.customTimeoutIds[timeoutIdName];
- if (timeoutId !== null) {
- clearTimeout(timeoutId);
+ clearCustomTimeoutIds() {
+ for (let timeoutIdName in this.customTimeoutIds) {
+ const timeoutId = this.customTimeoutIds[timeoutIdName];
+ if (timeoutId) {
+ clearTimeout(timeoutId);
+ delete this.customTimeoutIds[timeoutIdName];
+ }
}
- });
-}
+ }
-CommandRunner.prototype.loopMethod = function(index) {
- if (this.commands.length === 0) {
- throw new Error("実行しようとしたcommandsは空でした。: " + this.key);
+ loopMethod(index) {
+ if (this.commands.length === 0) {
+ throw new Error("実行しようとしたcommandsは空でした。: " + this.name);
+ }
+ this.clearCustomTimeoutIds();
+ const currentCommand = this.commands[index];
+ this.commandFunctions[currentCommand.commandName].apply(this, [{
+ isBuiltIn: false
+ }].concat(processArguments(currentCommand.args)));
+ var nextIndex = index + 1 >= this.commands.length ? 0 : index + 1;
+ this.loopTimeoutId = setTimeout(() => {
+ this.loopMethod(nextIndex);
+ }, currentCommand.time * 1000);
}
- this.clearCustomTimeoutIds();
- const currentCommand = this.commands[index];
- this.commandFunctions[currentCommand.commandName].apply(this, [{
- isBuiltIn: false
- }].concat(processArguments(currentCommand.args)));
- var nextIndex = index + 1 >= this.commands.length ? 0 : index + 1;
- this.loopTimeoutId = setTimeout(() => {
- this.loopMethod(nextIndex);
- }, currentCommand.time * 1000);
-};
+}
// コマンドの引数にmotionSpecialDataがあった場合、それを実際の値に変更する
function processArguments(args) {
@@ -111,5 +112,3 @@ function processArguments(args) {
});
}
-module.exports = CommandRunner;
-
diff --git a/componentBase.js b/componentBase.js
new file mode 100644
index 0000000..b93a3b9
--- /dev/null
+++ b/componentBase.js
@@ -0,0 +1,24 @@
+import publisher from "./publisher";
+
+export default class ComponentBase {
+ constructor(models = {}) {
+ for (let modelName in models) {
+ this[modelName] = models[modelName];
+ }
+ }
+ publish(subjectName, ...data) {
+ publisher.publish(this, subjectName, ...data);
+ }
+ subscribeModel(subjectName, observeFunction) {
+ publisher.subscribeModel(subjectName, (author, ...data) => {
+ observeFunction.apply(this, data);
+ });
+ }
+ subscribe(subjectName, observeFunction) {
+ publisher.subscribe(subjectName, (author, ...data) => {
+ if (author !== this) {
+ observeFunction.apply(this, data);
+ }
+ });
+ }
+}
diff --git a/config.js b/config.js
index afa8ad4..1ee54a6 100644
--- a/config.js
+++ b/config.js
@@ -13,5 +13,17 @@ export default {
wsPort: 8081
},
dashboardPort: 8082,
- scoreboardPort: 8083
+ scoreboardPort: 8083,
+ defaultHp: 100,
+ damage: 10,
+ defaultColor: "white",
+ collision: {
+ meth: 0x01,
+ xt: 0x20,
+ xs: 0x20,
+ yt: 0x20,
+ ys: 0x20,
+ dead: 0x02
+ },
+ isUseNoble: false
};
diff --git a/connector.js b/connector.js
index 3e451ce..e74b792 100644
--- a/connector.js
+++ b/connector.js
@@ -1,6 +1,35 @@
-class Connector {
- constructor() {
+import ComponentBase from "./componentBase";
+
+class Connector extends ComponentBase {
+ constructor(models) {
+ super(models);
+
this.rawOrbs = {};
+
+ this.originalError = console.error;
+
+ console.error = (message) => {
+ const exec121Error = /Error: Opening (\\\\\.\\)?(.+): Unknown error code (121|1167)/.exec(message);
+ if (exec121Error) {
+ const port = exec121Error[2];
+ if (this.isConnecting(port)) {
+ this.publish("incrementError121Count");
+ if (this.appModel.error121Count < 5) {
+ this.publish("log", `Catched 121 error. Reconnecting... (${models.appModel.error121Count})`, "warning");
+ this.reconnect(port);
+ } else {
+ this.publish("resetError121Count")
+ this.publish("log", "Catched 121 error. But this is 5th try. Give up.", "warning");
+ this.giveUp(port);
+ }
+ } else {
+ this.publish("log", "Catched 121 error. But port is invalid.", "error");
+ }
+ } else {
+ this.publish("log", "Catched unknown error: \n" + message.toString(), "error");
+ }
+ this.originalError(message);
+ };
}
connect(port, rawOrb) {
if (this.isConnecting(port)) {
diff --git a/controller.js b/controller.js
index 949e995..06bdcdb 100644
--- a/controller.js
+++ b/controller.js
@@ -17,23 +17,23 @@ export default class Controller extends EventEmitter {
}
setHp(hp) {
this.hp = hp;
- if (this.client !== null) {
+ if (this.client) {
this.client.sendCustomMessage("hp", this.hp);
}
this.emit("hp", this.hp);
}
setIsOni(isOni) {
this.isOni = isOni;
- if (this.client !== null) {
+ if (this.client) {
this.client.sendCustomMessage("oni", this.isOni);
}
this.emit("oni", this.isOni);
}
setLink(orb) {
// client も持っているが、それに左右されずにするため link は別に持つ必要がある
- this.linkedOrb = orb !== null ? orb : null;
- if (this.client !== null) {
- if (orb === null) {
+ this.linkedOrb = orb;
+ if (this.client) {
+ if (!orb) {
this.client.unlink();
} else {
this.client.setLinkedOrb(orb);
@@ -43,21 +43,21 @@ export default class Controller extends EventEmitter {
}
setClient(client) {
this.client = client;
- if (this.client !== null) {
+ if (this.client) {
this.client.sendCustomMessage("hp", this.hp);
this.client.sendCustomMessage("oni", this.isOni);
this.client.sendCustomMessage("color", this.color);
- }
- if (this.linkedOrb !== null && this.client !== null) {
- // HPなどの Orb -> Client への伝達で、
- // client にも linkedOrb を入れておく必要がある。
- this.client.setLinkedOrb(this.linkedOrb);
+ if (this.linkedOrb) {
+ // HPなどの Orb -> Client への伝達で、
+ // client にも linkedOrb を入れておく必要がある。
+ this.client.setLinkedOrb(this.linkedOrb);
+ }
}
}
setColor(color) {
this.color = color;
updateColor.call(this);
- if (this.client !== null) {
+ if (this.client) {
this.client.sendCustomMessage("color", this.color);
}
}
@@ -65,15 +65,15 @@ export default class Controller extends EventEmitter {
return {
hp: this.hp,
isOni: this.isOni,
- link: this.linkedOrb !== null ? this.linkedOrb.name : null,
- key: this.client !== null ? this.client.key : null,
+ link: this.linkedOrb ? this.linkedOrb.name : null,
+ key: this.client ? this.client.key : null,
color: this.color
};
}
}
function updateColor() {
- if (this.linkedOrb !== null) {
+ if (this.linkedOrb) {
this.linkedOrb.command("color", [this.color]);
}
}
diff --git a/controllerManager.js b/controllerManager.js
new file mode 100644
index 0000000..a319d8f
--- /dev/null
+++ b/controllerManager.js
@@ -0,0 +1,149 @@
+import ComponentBase from "./componentBase";
+
+export default class ControllerManager extends ComponentBase {
+ constructor(models, defaultHp, damage) {
+ super(models);
+
+ this.defaultHp = defaultHp;
+ this.damageHp = damage;
+
+ this.subscribe("oni", this.changeIsOni);
+ this.subscribe("resetHp", this.resetHp);
+ this.subscribe("color", this.changeColor);
+ this.subscribe("addClient", this.addClient);
+ this.subscribe("addedUnknown", this.initializeUnknown);
+ this.subscribe("removeClient", this.removeClient);
+ this.subscribe("gameState", this.updateGameState);
+ this.subscribe("collision", this.damage);
+ this.subscribe("availableCommandsCount", this.updateAvailableCommandsCount);
+ this.subscribe("updateLink", this.updateLink);
+ this.subscribe("rankingState", this.updateRankingState);
+ this.subscribe("addedClient", this.initializeClient);
+ this.subscribe("addedController", this.initializeController);
+ this.subscribe("setCommands", this.setCommands);
+ this.subscribe("command", this.command);
+ }
+ changeIsOni(name, isEnabled) {
+ this.controllerModel.get(name).setIsOni(isEnabled);
+ }
+ resetHp(name) {
+ this.controllerModel.get(name).setHp(this.defaultHp);
+ }
+ changeColor(name, color) {
+ this.controllerModel.get(name).setColor(color);
+ }
+ addClient(key, client) {
+ this.controllerModel.addUnknownClient(key, client);
+ }
+ removeClient(key) {
+ if (this.controllerModel.hasInUnknownClients(key)) {
+ this.controllerModel.removeUnknownClient(key);
+ } else {
+ const name = this.controllerModel.toName(key);
+ this.controllerModel.removeClient(name);
+ }
+ }
+ updateGameState(state) {
+ for (let name in this.controllerModel.controllers) {
+ if (this.controllerModel.get(name).client) {
+ this.controllerModel.get(name).client.sendCustomMessage("gameState", state);
+ }
+ }
+ }
+ updateRankingState(state) {
+ for (let name in this.controllerModel.controllers) {
+ if (this.controllerModel.get(name).client) {
+ this.controllerModel.get(name).client.sendCustomMessage("rankingState", state);
+ }
+ }
+ };
+ updateRanking(ranking) {
+ for (let name in this.controllerModel.controllers) {
+ if (this.controllerModel.get(name).client) {
+ this.controllerModel.get(name).client.sendCustomMessage("ranking", ranking);
+ }
+ }
+ }
+ damage(orb) {
+ for (let controllerName in this.controllerModel.controllers) {
+ const controller = this.controllerModel.get(controllerName);
+ if (this.appModel.gameState === "active" && !controller.isOni &&
+ controller.client && orb.linkedClients.indexOf(controller.client.key) !== -1) {
+ controller.setHp(controller.hp - this.damageHp);
+ }
+ }
+ }
+ updateAvailableCommandsCount(count) {
+ for (let name in this.controllerModel.controllers) {
+ const client = this.controllerModel.get(name).client;
+ if (client) {
+ client.sendCustomMessage("availableCommandsCount", count);
+ } else {
+ console.warn("Tryed to update availableCommandsCount but client is null. name: " + name);
+ }
+ }
+ }
+ updateLink(controllerName, orbName) {
+ this.controllerModel.get(controllerName).setLink(
+ orbName ? this.orbModel.getOrbFromSpheroWS(orbName) : null);
+ }
+ initializeUnknown(key, client) {
+ client.on("arriveCustomMessage", (name, data, mesID) => {
+ // Nameが同じなら、clientKeyが別でもHPなどが引き継がれる、と実装するため、
+ // requestNameとuseDefinedNameを分けている。
+ // requestName ・・ 新しい名前を使う。もしその名前が既に使われていたらrejectする。
+ // useDefinedName ・・既存の名前を使う。もしその名前がなければrejectする。
+ if (name === "requestName") {
+ if (this.controllerModel.has(data)) {
+ client.sendCustomMessage("rejectName", null);
+ } else {
+ this.controllerModel.setName(key, data);
+ client.sendCustomMessage("acceptName", data);
+ }
+ } else if (name === "useDefinedName") {
+ if (!this.controllerModel.has(data)) {
+ client.sendCustomMessage("rejectName", null);
+ } else {
+ this.controllerModel.setName(key, data);
+ client.sendCustomMessage("acceptName", data);
+ }
+ }
+ });
+ }
+ initializeClient(name) {
+ const controller = this.controllerModel.get(name);
+ const client = controller.client;
+
+ client.sendCustomMessage("gameState", this.appModel.gameState);
+ client.sendCustomMessage("rankingState", this.appModel.rankingState);
+ client.sendCustomMessage("availableCommandsCount", this.appModel.availableCommandsCount);
+ client.sendCustomMessage("clientKey", client.key);
+
+ client.on("arriveCustomMessage", (messageName, data, mesID) => {
+ if (messageName === "commands") {
+ this.publish("setCommands", name, data);
+ }
+ });
+ }
+ initializeController(name) {
+ console.log(name);
+ const controller = this.controllerModel.get(name);
+ controller.on("hp", hp => {
+ this.publish("hp", name, hp);
+ });
+ }
+ setCommands(name, commands) {
+ const controller = this.controllerModel.get(name);
+ controller.commandRunner.setCommands(commands);
+ }
+ command(controllerName, commandName, args) {
+ const controller = this.controllerModel.get(controllerName);
+ if (controller.linkedOrb) {
+ if (!controller.linkedOrb.hasCommand(commandName)) {
+ throw new Error(`command : ${commandName} is not valid.`);
+ }
+ controller.linkedOrb.command(commandName, args);
+ }
+ }
+}
+
diff --git a/controllerModel.js b/controllerModel.js
deleted file mode 100644
index 71ee855..0000000
--- a/controllerModel.js
+++ /dev/null
@@ -1,80 +0,0 @@
-import Controller from "./controller";
-import CommandRunner from "./commandRunner";
-import { EventEmitter } from "events";
-
-class ControllerModel extends EventEmitter {
- constructor() {
- super();
-
- // 最初、controllerはunnamedControllerに追加される。
- // name がわかると、それが controllers に移行される。
- // こうすることで、clientが異なっても、nameが同じ場合、HPなどを共有できるようになる。
-
- // { key: Client, ... }
- this.unnamedClients = {};
- // { name: Controller, ... }
- this.controllers = {};
- }
- add(key, client) {
- this.unnamedClients[key] = client;
- this.emit("add", key, client);
- }
- setName(key, name) {
- if (typeof this.unnamedClients[key] === "undefined") {
- throw new Error("setNameしようとしたところ、keyに対するclientが見つかりませんでした。 key: " + key);
- }
- let isNewName = typeof this.controllers[name] === "undefined";
- if (isNewName) {
- this.controllers[name] = new Controller(name, new CommandRunner(key));
- } else if (this.controllers[name].client !== null) {
- throw new Error("setNameしようとしましたが、既にclientが存在します。 name: " + name);
- }
- this.controllers[name].setClient(this.unnamedClients[key]);
- delete this.unnamedClients[key];
- this.emit("named", key, name, isNewName);
- }
- removeFromUnnamedClients(key) {
- if (this.hasInUnnamedClients(key)) {
- delete this.unnamedClients[key];
- this.emit("removeUnnamed", key);
- }
- }
- removeClient(name) {
- if (this.has(name)) {
- this.controllers[name].setClient(null);
- this.emit("remove", name);
- }
- }
- hasInUnnamedClients(key) {
- return typeof this.unnamedClients[key] !== "undefined";
- }
- has(name) {
- return typeof this.controllers[name] !== "undefined";
- }
- get(name) {
- return this.controllers[name];
- }
- getAllStates() {
- const result = {};
- Object.keys(this.controllers).forEach(name => {
- result[name] = this.controllers[name].getStates();
- });
- return result;
- }
- getUnnamedKeys() {
- return Object.keys(this.unnamedClients);
- }
- toName(key) {
- const nameArray = Object.keys(this.controllers).filter(name => {
- return this.controllers[name].client !== null &&
- this.controllers[name].client.key === key;
- });
- if (nameArray.length === 1) {
- return nameArray[0];
- } else {
- return null;
- }
- }
-}
-
-export default new ControllerModel();
diff --git a/dashboard.js b/dashboard.js
index fee825c..b18055d 100644
--- a/dashboard.js
+++ b/dashboard.js
@@ -1,216 +1,174 @@
import express from "express";
import io from "socket.io";
-import { EventEmitter } from "events";
import util from "util";
-import OrbMap from "./util/orbMap";
-import controllerModel from "./controllerModel";
-
-let instance = null;
-
-function Dashboard(port) {
- EventEmitter.call(this);
-
- if (instance !== null) {
- return instance;
+import { Server as createServer } from "http";
+import socketIO from "socket.io";
+import ComponentBase from "./componentBase";
+
+const socketSubjects = [
+ "gameState",
+ "rankingState",
+ "availableCommandsCount",
+ "addOrb",
+ "removeOrb",
+ "oni",
+ "checkBattery",
+ "resetHp",
+ "pingall",
+ "color"
+];
+
+export default class Dashboard extends ComponentBase {
+ constructor(models, port) {
+ super(models);
+
+ this.app = express();
+ this.server = createServer(this.app);
+ this.io = socketIO(this.server);
+ this.io.origins(`localhost:${port}`);
+
+ this.socket = null;
+
+
+ this.app.use(express.static("dashboard"));
+ this.server.listen(port, () => {
+ console.log(`dashboard is listening on port ${port}`);
+ });
+
+ this.io.on("connection", this.initializeConnection.bind(this));
+
+ this.subscribe("addedUnknown", (key, client) => {
+ if (this.socket) {
+ this.socket.emit("addUnnamed", key);
+ }
+ });
+ this.subscribe("addedClient", (name) => {
+ if (this.socket) {
+ const controller = this.controllerModel.get(name);
+ this.socket.emit("named", controller.client.key, name, controller.getStates());
+ }
+ });
+ this.subscribe("removedUnknown", key => {
+ if (this.socket) {
+ this.socket.emit("removeUnnamed", key);
+ }
+ });
+ this.subscribe("removedClient", name => {
+ if (this.socket) {
+ this.socket.emit("removeClient", name);
+ }
+ });
+
+ this.subscribe("addedOrb", this.addOrb);
+ this.subscribe("removedOrb", this.removeOrb);
+ this.subscribe("updateBattery", this.updateBattery);
+ this.subscribe("replyPing", this.updatePingState);
+ this.subscribe("log", this.logAsClientMessage);
+ this.subscribe("streamed", this.streamed);
+ this.subscribe("updateLink", this.updateUnlinkedOrbs);
+ this.subscribe("addOrb", this.updateUnlinkedOrbs);
+ this.subscribe("hp", this.updateHp);
}
-
- this.app = express();
- this.server = require("http").Server(this.app);
- this.io = require("socket.io")(this.server);
- this.io.origins(`localhost:${port}`);
-
- this.socket = null;
-
- this.gameState = "inactive";
- this.rankingState = "hide";
- this.availableCommandsCount = 1;
-
- this.orbMap = new OrbMap();
-
- this.app.use(express.static("dashboard"));
- this.server.listen(port, () => {
- console.log(`dashboard is listening on port ${port}`);
- });
-
- this.io.on("connection", socket => {
- if (this.socket !== null) {
- socket.disconnect();
+ initializeConnection(socket) {
+ if (this.socket) {
+ this.socket.disconnect();
console.log("a dashboard rejected.");
} else {
console.log("a dashboard connected.");
this.socket = socket;
- this.log("accepted a dashboard.", "success");
+ this.logAsClientMessage("accepted a dashboard.", "success");
socket.emit(
- "defaultData",
- this.gameState,
- this.availableCommandsCount,
- controllerModel.getAllStates(),
- this.orbMap.toArray(),
- controllerModel.getUnnamedKeys());
- socket.on("gameState", state => {
- if (/^(active|inactive)$/.test(state)) {
- this.gameState = state;
- this.emit("gameState", state);
- }
- });
- socket.on("rankingState", state => {
- if (/^(show|hide)$/.test(state)) {
- this.rankingState = state;
- this.emit("rankingState", state);
- }
- });
- socket.on("availableCommandsCount", count => {
- if (count >= 1 && count <= 6) {
- this.availableCommandsCount = count;
- this.emit("availableCommandsCount", count);
- }
- });
- socket.on("link", (name, orbName) => {
- this.emit("updateLink", name, orbName);
- });
- socket.on("addOrb", (name, port) => {
- this.emit("addOrb", name, port);
- });
- socket.on("removeOrb", name => {
- this.emit("removeOrb", name);
- });
- socket.on("orbReconnect", name => {
- this.emit("reconnect", name);
+ "defaultData",
+ this.appModel.gameState,
+ this.appModel.availableCommandsCount,
+ this.controllerModel.getAllStates(),
+ this.orbModel.toArray(),
+ this.controllerModel.getUnnamedKeys());
+
+ socketSubjects.forEach(subjectName => {
+ this.socket.on(subjectName, (...data) => {
+ this.publish(subjectName, ...data);
+ });
});
- socket.on("oni", (name, enable) => {
- this.emit("oni", name, enable);
+
+ // 引数は渡さないので別の方法で結びつける
+ this.socket.on("pingAll", () => {
+ this.publishPingAll();
});
- socket.on("checkBattery", () => {
- this.emit("checkBattery");
+
+ // link -> updateLink と名前が変わるので、別の方法で結びつける
+ this.socket.on("link", (controllerName, orbName) => {
+ this.publishUpdateLink(controllerName, orbName);
});
+
+ socket.emit("updateOrbs", this.orbModel.toArray());
socket.on("disconnect", () => {
console.log("a dashboard removed.");
this.socket = null;
});
- socket.on("resetHp", name => {
- this.emit("resetHp", name);
- });
- socket.on("pingAll", () => {
- this.emit("pingAll");
- Object.keys(this.orbMap.orbs).forEach(orbName => {
- this.orbMap.setPingState(orbName, "no reply");
- });
- socket.emit("updateOrbs", this.orbMap.toArray());
- });
- socket.on("color", (name, color) => {
- this.emit("color", name, color);
- });
- }
- });
-
- controllerModel.on("add", (key, client) => {
- if (this.socket !== null) {
- this.socket.emit("addUnnamed", key);
- }
- });
- controllerModel.on("named", (key, name) => {
- if (this.socket !== null) {
- this.socket.emit("named", key, name, controllerModel.get(name).getStates());
}
- });
- controllerModel.on("removeUnnamed", key => {
- if (this.socket !== null) {
- this.socket.emit("removeUnnamed", key);
- }
- });
- controllerModel.on("remove", name => {
- if (this.socket !== null) {
- this.socket.emit("removeClient", name);
- }
- });
-
- instance = this;
- return this;
-}
-
-Dashboard.prototype.addOrb = function(name, port) {
- if (this.orbMap.has(name)) {
- throw new Error(`追加しようとしたOrbは既に存在します。 : ${name}`);
- }
- this.orbMap.set(name, {
- orbName: name,
- port,
- battery: null,
- link: "unlinked",
- pingState: "unchecked"
- });
- if (this.socket !== null) {
- this.socket.emit("updateOrbs", this.orbMap.toArray());
}
-};
-
-Dashboard.prototype.removeOrb = function(name) {
- if (!this.orbMap.has(name)) {
- throw new Error(`削除しようとしたOrbは存在しません。 : ${name}`);
+ publishPingAll() {
+ this.publish("pingAll");
}
- this.orbMap.remove(name);
- if (this.socket !== null) {
- this.socket.emit("updateOrbs", this.orbMap.toArray());
- }
-};
-
-Dashboard.prototype.updateUnlinkedOrbs = function(unlinkedOrbs) {
- const unlinkedOrbNames = Object.keys(unlinkedOrbs);
- this.orbMap.getNames().forEach(orbName => {
- this.orbMap.setLink(
- orbName,
- unlinkedOrbNames.indexOf(orbName) >= 0 ? "unlinked" : "linked");
- });
- if (this.socket !== null) {
- this.socket.emit("updateOrbs", this.orbMap.toArray());
+ addOrb(name, orb) {
+ if (this.socket) {
+ this.socket.emit("updateOrbs", this.orbModel.toArray());
+ }
}
-};
-Dashboard.prototype.updateBattery = function(orbName, batteryState) {
- const orbNameItem = this.orbMap.get(orbName);
- if (typeof orbNameItem === "undefined") {
- throw new Error("updateBattery しようとしましたが、orb が見つかりませんでした。 : " + orbName);
- }
- orbNameItem.battery = batteryState;
- if (this.socket !== null) {
- this.socket.emit("updateOrbs", this.orbMap.toArray());
+ removeOrb(name) {
+ if (this.socket) {
+ this.socket.emit("updateOrbs", this.orbModel.toArray());
+ }
}
-};
-Dashboard.prototype.updateHp = function(controllerKey, hp) {
- if (this.socket !== null) {
- this.socket.emit("hp", controllerKey, hp);
+ updateUnlinkedOrbs() {
+ const unlinkedOrbs = this.orbModel.getUnlinkedOrbs();
+ this.orbModel.getNames().forEach(orbName => {
+ this.orbModel.setLink(
+ orbName,
+ unlinkedOrbs[orbName] ? "unlinked" : "linked");
+ });
+ if (this.socket) {
+ this.socket.emit("updateOrbs", this.orbModel.toArray());
+ }
}
-};
-Dashboard.prototype.log = function(logText, logType) {
- if (this.socket !== null) {
- this.socket.emit("log", logText, logType);
+ updateBattery() {
+ if (this.socket) {
+ this.socket.emit("updateOrbs", this.orbModel.toArray());
+ }
}
-};
-
-Dashboard.prototype.updatePingState = function(orbName) {
- if (!this.orbMap.has(orbName)) {
- throw new Error("updatePingState しようとしましたが、orb が見つかりませんでした。 : " + orbName);
+ updateHp(name, hp) {
+ if (this.socket) {
+ this.socket.emit("hp", name, hp);
+ }
}
- this.orbMap.setPingState(orbName, "reply");
- if (this.socket !== null) {
- this.socket.emit("updateOrbs", this.orbMap.toArray());
+ logAsClientMessage(logText, logType) {
+ if (this.socket) {
+ this.socket.emit("log", logText, logType);
+ }
}
-};
-
-Dashboard.prototype.streamed = function(orbName, time) {
- if (this.socket !== null) {
- this.socket.emit("streamed", orbName, time);
+ updatePingState(orbName) {
+ if (!this.orbModel.has(orbName)) {
+ throw new Error("updatePingState しようとしましたが、orb が見つかりませんでした。 : " + orbName);
+ }
+ if (this.socket) {
+ this.socket.emit("updateOrbs", this.orbModel.toArray());
+ }
}
-};
-
-Dashboard.prototype.successReconnect = function(orbName) {
- if (this.socket !== null) {
- this.socket.emit("successReconnect", orbName);
+ streamed(orbName, time) {
+ if (this.socket) {
+ this.socket.emit("streamed", orbName, this.formatTime(time));
+ }
}
-};
-
-util.inherits(Dashboard, EventEmitter);
-
-module.exports = Dashboard;
+ formatTime(time) {
+ return ("0" + time.getHours()).slice(-2) + ":" +
+ ("0" + time.getMinutes()).slice(-2) + ":" +
+ ("0" + time.getSeconds()).slice(-2);
+ }
+ publishUpdateLink(controllerName, orbName) {
+ this.publish("updateLink", controllerName, orbName);
+ }
+}
diff --git a/dashboard/index.html b/dashboard/index.html
index c03ca4b..85023c0 100644
--- a/dashboard/index.html
+++ b/dashboard/index.html
@@ -59,7 +59,6 @@
| Ping Status |
Last Streamed |
Disconnect |
- Reconnect |
diff --git a/dashboard/js/controller.js b/dashboard/js/controller.js
index 86a11df..1372114 100644
--- a/dashboard/js/controller.js
+++ b/dashboard/js/controller.js
@@ -109,6 +109,6 @@ function updateOrbSelect() {
item.textContent = orbName;
this.orbSelectElement.appendChild(item);
});
- this.orbSelectElement.value = this.linkedOrb === null ? unlinkedText : this.linkedOrb;
+ this.orbSelectElement.value = this.linkedOrb || unlinkedText;
}
diff --git a/dashboard/js/controllerManager.js b/dashboard/js/controllerManager.js
index 9813d3f..b1f01f3 100644
--- a/dashboard/js/controllerManager.js
+++ b/dashboard/js/controllerManager.js
@@ -9,10 +9,9 @@ export default class ControllerManager {
this.orbNames = [];
eventPublisher.on("defaultControllers", controllers => {
- Object.keys(controllers).forEach(name => {
- this.addController(controllers[name].key !== null ? controllers[name].key : "no client",
- name, controllers[name]);
- });
+ for (let name in controllers) {
+ this.addController(controllers[name].key || "no client", name, controllers[name]);
+ }
});
eventPublisher.on("named", (key, name, controllerDetails) => {
if (this.has(name)) {
diff --git a/dashboard/js/orbManager.js b/dashboard/js/orbManager.js
index 2597e6b..dcef07a 100644
--- a/dashboard/js/orbManager.js
+++ b/dashboard/js/orbManager.js
@@ -72,16 +72,6 @@ export default class OrbManager {
eventPublisher.emit("disconnect", orbName);
});
disconnectTd.appendChild(disconnectButton);
- const reconnectTd = document.createElement("td");
- reconnectTd.classList.add("td-reconnect");
- trElement.appendChild(reconnectTd);
- const reconnectButton = document.createElement("button");
- reconnectButton.textContent = "Reconnect";
- reconnectButton.addEventListener("click", () => {
- reconnectButton.disabled = true;
- eventPublisher.emit("reconnect", orbName);
- });
- reconnectTd.appendChild(reconnectButton);
this.updateLinkForRow(orbName);
this.updateBatteryForRow(orbName);
this.updatePingStateForRow(orbName);
@@ -106,8 +96,7 @@ export default class OrbManager {
throw new Error("updateBattery しようとした Orb は存在しませんでした。 : " + orbName);
}
const batteryTd = trElement.querySelector(".td-battery");
- batteryTd.textContent =
- this.orbMap.get(orbName).battery === null ? "unchecked" : this.orbMap.get(orbName).battery;
+ batteryTd.textContent = this.orbMap.get(orbName).battery || "unchecked";
}
updatePingStateForRow(orbName) {
const trElement = this.getRow(orbName);
@@ -125,14 +114,9 @@ export default class OrbManager {
const streamTimeTd = trElement.querySelector(".td-stream-time");
streamTimeTd.textContent = time;
}
- enableReconnectButton(orbName) {
- const trElement = this.getRow(orbName);
- const reconnectButton = trElement.querySelector(".td-reconnect > button");
- reconnectButton.disabled = false;
- }
getRow(orbName) {
const trElement = document.querySelector(`[data-row-name="${orbName}"]`);
- if (trElement === null) {
+ if (!trElement) {
throw new Error("getRow しようとした Row は存在しませんでした。 : " + orbName);
}
return trElement;
diff --git a/dashboard/js/socketManager.js b/dashboard/js/socketManager.js
index ca4f149..1cb2b81 100644
--- a/dashboard/js/socketManager.js
+++ b/dashboard/js/socketManager.js
@@ -2,7 +2,7 @@ import eventPublisher from "./publisher";
let instance = null;
function SocketManager() {
- if (instance !== null) {
+ if (instance) {
return instance;
}
eventPublisher.on("gameState", this.sendGameState.bind(this));
@@ -11,7 +11,6 @@ function SocketManager() {
eventPublisher.on("link", this.sendLink.bind(this));
eventPublisher.on("addOrb", this.sendAddOrb.bind(this));
eventPublisher.on("disconnect", this.sendDisconnect.bind(this));
- eventPublisher.on("reconnect", this.sendReconnect.bind(this));
eventPublisher.on("oni", this.sendOni.bind(this));
eventPublisher.on("checkBattery", this.sendCheckBattery.bind(this));
eventPublisher.on("resetHp", this.sendResetHp.bind(this));
@@ -50,9 +49,6 @@ function SocketManager() {
this.socket.on("streamed", (orbName, time) => {
emit.call(this, "streamed", [orbName, time]);
});
- this.socket.on("successReconnect", orbName => {
- emit.call(this, "successReconnect", [orbName]);
- });
instance = this;
}
@@ -81,10 +77,6 @@ SocketManager.prototype.sendDisconnect = function(name) {
this.socket.emit("removeOrb", name);
};
-SocketManager.prototype.sendReconnect = function(name) {
- this.socket.emit("orbReconnect", name);
-};
-
SocketManager.prototype.sendOni = function(controllerName, isEnabled) {
this.socket.emit("oni", controllerName, isEnabled);
};
diff --git a/main.js b/main.js
index 31d8ab9..0b8f113 100644
--- a/main.js
+++ b/main.js
@@ -1,290 +1,54 @@
const originalError = console.error;
-let error121Count = 0;
-console.error = function(message) {
- const exec121Error = /Error: Opening (\\\\\.\\)?(.+): Unknown error code (121|1167)/.exec(message);
- if (exec121Error !== null) {
- const port = exec121Error[2];
- if (connector.isConnecting(port)) {
- error121Count++;
- if (error121Count < 5) {
- dashboard.log(`Catched 121 error. Reconnecting... (${error121Count})`, "warning");
- connector.reconnect(port);
- } else {
- error121Count = 0;
- dashboard.log("Catched 121 error. But this is 5th try. Give up.", "warning");
- connector.giveUp(port);
- }
- } else {
- dashboard.log("Catched 121 error. But port is invalid.", "error");
- }
- } else {
- dashboard.log("Catched unknown error: \n" + message.toString(), "error");
- }
- originalError(message);
-};
-
import spheroWebSocket from "sphero-websocket";
import argv from "argv";
import config from "./config";
-import VirtualSphero from "sphero-ws-virtual-plugin";
import Dashboard from "./dashboard";
import Scoreboard from "./scoreboard";
import CommandRunner from "./commandRunner";
import Controller from "./controller";
-import controllerModel from "./controllerModel";
+import controllerModel from "./model/controllerModel";
import RankingMaker from "./rankingMaker";
import Connector from "./connector";
-import eventPublisher from "./publisher";
+import publisher from "./publisher";
+import SpheroServerManager from "./spheroServerManager";
+import VirtualSpheroManager from "./virtualSpheroManager";
+import ControllerManager from "./controllerManager";
+import OrbController from "./orbController";
+import OrbModel from "./model/orbModel";
+import AppModel from "./model/appModel";
+import ControllerModel from "./model/controllerModel";
+import UUIDManager from "./uuidManager";
+
+const models = {
+ appModel: new AppModel(),
+ orbModel: new OrbModel(),
+ controllerModel: new ControllerModel()
+};
const opts = [
{ name: "test", type: "boolean" }
];
const isTestMode = argv.option(opts).run().options.test;
+models.appModel.isTestMode = isTestMode;
const spheroWS = spheroWebSocket(config.websocket, isTestMode);
-
-const virtualSphero = new VirtualSphero(config.virtualSphero.wsPort);
-
-const dashboard = new Dashboard(config.dashboardPort);
-dashboard.updateUnlinkedOrbs(spheroWS.spheroServer.getUnlinkedOrbs());
-
-const scoreboard = new Scoreboard(config.scoreboardPort);
-
-let gameState = "inactive";
-let rankingState = "hide";
-let availableCommandsCount = 1;
-
-const rankingMaker = new RankingMaker();
-
-const connector = new Connector();
-
-spheroWS.spheroServer.events.on("addClient", (key, client) => {
- controllerModel.add(key, client);
- client.on("arriveCustomMessage", (name, data, mesID) => {
- // Nameが同じなら、clientKeyが別でもHPなどが引き継がれる、と実装するため、
- // requestNameとuseDefinedNameを分けている。
- // requestName ・・ 新しい名前を使う。もしその名前が既に使われていたらrejectする。
- // useDefinedName ・・既存の名前を使う。もしその名前がなければrejectする。
- if (name === "requestName") {
- if (controllerModel.has(data)) {
- client.sendCustomMessage("rejectName", null);
- } else {
- controllerModel.setName(key, data);
- client.sendCustomMessage("acceptName", data);
- }
- } else if (name === "useDefinedName") {
- if (!controllerModel.has(data)) {
- client.sendCustomMessage("rejectName", null);
- } else {
- controllerModel.setName(key, data);
- client.sendCustomMessage("acceptName", data);
- }
- }
- });
-});
-
-spheroWS.spheroServer.events.on("removeClient", key => {
- if (controllerModel.hasInUnnamedClients(key)) {
- controllerModel.removeFromUnnamedClients(key);
- } else {
- const name = controllerModel.toName(key);
- controllerModel.removeClient(name);
- virtualSphero.removeSphero(name);
- }
-});
-
-controllerModel.on("named", (key, name, isNewName) => {
- const controller = controllerModel.get(name);
- const client = controller.client;
-
- client.sendCustomMessage("gameState", gameState);
- client.sendCustomMessage("rankingState", rankingState);
- client.sendCustomMessage("availableCommandsCount", availableCommandsCount);
- client.sendCustomMessage("clientKey", key);
-
- if (isNewName) {
- controller.commandRunner.on("command", (commandName, args) => {
- if (controller.linkedOrb !== null) {
- if (!controller.linkedOrb.hasCommand(commandName)) {
- throw new Error(`command : ${commandName} is not valid.`);
- }
- controller.linkedOrb.command(commandName, args);
- }
- virtualSphero.command(name, commandName, args);
- });
- controller.on("hp", hp => {
- dashboard.updateHp(name, hp);
- });
- }
- virtualSphero.addSphero(name);
-
- client.on("arriveCustomMessage", (messageName, data, mesID) => {
- if (messageName === "commands") {
- controller.commandRunner.setCommands(data);
- }
- });
-});
-
-const orbs = spheroWS.spheroServer.getOrb();
-Object.keys(orbs).forEach(orbName => {
- dashboard.addOrb(orbName, orbs[orbName].port);
-});
-
-spheroWS.spheroServer.events.on("addOrb", (name, orb) => {
- if (!isTestMode) {
- const rawOrb = orb.instance;
- rawOrb.color("white");
- rawOrb.detectCollisions();
- rawOrb.on("collision", () => {
- Object.keys(controllerModel.controllers).forEach(controllerName => {
- const controller = controllerModel.get(controllerName);
- if (gameState === "active" && !controller.isOni &&
- controller.client !== null &&
- orb.linkedClients.indexOf(controller.client.key) !== -1) {
- controller.setHp(controller.hp - 10);
- eventPublisher.emit("updatedHp", controller);
- }
- })
- });
- }
- dashboard.addOrb(name, orb.port);
- dashboard.updateUnlinkedOrbs(spheroWS.spheroServer.getUnlinkedOrbs());
-});
-spheroWS.spheroServer.events.on("removeOrb", name => {
- dashboard.removeOrb(name);
-});
-
-dashboard.on("gameState", state => {
- gameState = state;
- Object.keys(controllerModel.controllers).filter(key => {
- return controllerModel.get(key).client !== null;
- }).forEach(key => {
- controllerModel.get(key).client.sendCustomMessage("gameState", gameState);
- });
-});
-dashboard.on("rankingState", state => {
- const controllerKeys = Object.keys(controllerModel.controllers).filter(key => {
- return controllerModel.get(key).client !== null;
- });
- rankingState = state;
- controllerKeys.forEach(key => {
- controllerModel.get(key).client.sendCustomMessage("rankingState", state);
- });
- if (state === "show") {
- const ranking = rankingMaker.make(controllerModel.controllers);
- controllerKeys.forEach(key => {
- controllerModel.get(key).client.sendCustomMessage("ranking", ranking);
- });
- }
-});
-
-dashboard.on("availableCommandsCount", count => {
- availableCommandsCount = count;
- Object.keys(controllerModel.controllers).forEach(name => {
- const client = controllerModel.get(name).client;
- if (client !== null) {
- client.sendCustomMessage("availableCommandsCount", availableCommandsCount);
- }
- });
-});
-dashboard.on("updateLink", (controllerName, orbName) => {
- controllerModel.get(controllerName).setLink(
- orbName !== null ? spheroWS.spheroServer.getOrb(orbName) : null);
- dashboard.updateUnlinkedOrbs(spheroWS.spheroServer.getUnlinkedOrbs());
- eventPublisher.emit("updateLink", controllerName, orbName);
-});
-dashboard.on("addOrb", (name, port) => {
- const rawOrb = spheroWS.spheroServer.makeRawOrb(name, port);
- if (!isTestMode) {
- if (!connector.isConnecting(port)) {
- error121Count = 0;
- connector.connect(port, rawOrb.instance).then(() => {
- dashboard.log("connected orb.", "success");
- rawOrb.instance.configureCollisions({
- meth: 0x01,
- xt: 0x7A,
- xs: 0xFF,
- yt: 0x7A,
- ys: 0xFF,
- dead: 100
- }, () => {
- dashboard.log("configured orb.", "success");
- spheroWS.spheroServer.addOrb(rawOrb);
- rawOrb.instance.streamOdometer();
- rawOrb.instance.on("odometer", data => {
- const time = new Date();
- dashboard.streamed(
- name,
- ("0" + time.getHours()).slice(-2) + ":" +
- ("0" + time.getMinutes()).slice(-2) + ":" +
- ("0" + time.getSeconds()).slice(-2));
- });
- });
- });
- }
- } else {
- spheroWS.spheroServer.addOrb(rawOrb);
- }
-});
-dashboard.on("removeOrb", name => {
- spheroWS.spheroServer.removeOrb(name);
-});
-dashboard.on("oni", (name, enable) => {
- controllerModel.get(name).setIsOni(enable);
-});
-dashboard.on("checkBattery", () => {
- const orbs = spheroWS.spheroServer.getOrb();
- Object.keys(orbs).forEach(orbName => {
- orbs[orbName].instance.getPowerState((error, data) => {
- if (error) {
- throw new Error(error);
- } else {
- dashboard.updateBattery(orbName, data.batteryState);
- }
- });
- });
-});
-dashboard.on("resetHp", name => {
- const controller = controllerModel.get(name);
- controller.setHp(100);
- eventPublisher.emit("updatedHp", controller);
-});
-dashboard.on("pingAll", () => {
- const orbs = spheroWS.spheroServer.getOrb();
- Object.keys(orbs).forEach(orbName => {
- orbs[orbName].instance.ping((err, data) => {
- if (!err) {
- dashboard.updatePingState(orbName);
- } else {
- dashboard.log("Ping error: \n" + err.toString(), "error");
- }
- });
- });
-});
-dashboard.on("reconnect", name => {
- if (!isTestMode) {
- const orb = spheroWS.spheroServer.getOrb(name);
- if (orb !== null) {
- orb.instance.disconnect(() => {
- dashboard.log("(reconnect) disconnected.", "success");
- if (!connector.isConnecting(orb.port)) {
- error121Count = 0;
- dashboard.log("(reconnect) wait 2 seconds.", "log");
- setTimeout(() => {
- dashboard.log("(reconnect) connecting...", "log");
- connector.connect(orb.port, orb.instance).then(() => {
- dashboard.log("(reconnect) connected", "success");
- dashboard.successReconnect(name);
- });
- }, 2000);
- }
- });
- }
- }
-});
-dashboard.on("color", (name, color) => {
- controllerModel.get(name).setColor(color);
- eventPublisher.emit("color", name, color);
-});
-
+models.orbModel.setSpheroWS(spheroWS);
+
+const connector = new Connector(models);
+
+new Dashboard(models, config.dashboardPort);
+new VirtualSpheroManager(models, config.virtualSphero.wsPort);
+new Scoreboard(models, config.scoreboardPort);
+new SpheroServerManager(models, spheroWS);
+new ControllerManager(models, config.defaultHp, config.damage);
+new RankingMaker(models);
+new OrbController(models, connector, spheroWS, config.defaultColor, config.collision);
+
+if (config.isUseNoble) {
+ // noble は、非対応環境だと、import した直後にエラーが発生してしまう
+ // そのため、require を使ってこのタイミングで読み込むしかない
+ // SystemJS とか使うともっとかっこいいかも
+ const noble = require("noble");
+ new UUIDManager(models, noble);
+}
diff --git a/model/appModel.js b/model/appModel.js
new file mode 100644
index 0000000..81781b6
--- /dev/null
+++ b/model/appModel.js
@@ -0,0 +1,55 @@
+import ComponentBase from "../componentBase";
+
+export default class AppModel extends ComponentBase {
+ constructor() {
+ super();
+
+ this.gameState = "inactive";
+ this.rankingState = "hide";
+ this.availableCommandsCount = 1;
+ this.isTestMode = false;
+ this.error121Count = 0;
+ this.ranking = null;
+ this.nameAndUUIDs = {};
+
+ this.subscribeModel("gameState", this.updateGameState);
+ this.subscribeModel("rankingState", this.updateRankingState);
+ this.subscribeModel("availableCommandsCount", this.updateAvailableCommandsCount);
+ this.subscribeModel("ranking", this.updateRanking);
+ this.subscribeModel("setNameOfUUID", this.setNameOfUUID);
+ this.subscribeModel("incrementError121Count", this.incrementError121Count);
+ this.subscribeModel("resetError121Count", this.resetError121Count);
+ }
+ updateGameState(state) {
+ this.gameState = state;
+ }
+ updateRankingState(state) {
+ this.rankingState = state;
+ }
+ updateAvailableCommandsCount(count) {
+ this.availableCommandsCount = count;
+ }
+ resetError121Count() {
+ this.error121Count = 0;
+ }
+ incrementError121Count() {
+ this.error121Count++;
+ }
+ updateRanking(ranking) {
+ this.ranking = ranking;
+ }
+ setNameOfUUID(name, uuid) {
+ console.log(`name: ${name}, uuid: ${uuid}`);
+ this.nameAndUUIDs[name] = uuid;
+ }
+ containsUUID(name) {
+ return typeof this.nameAndUUIDs[name] !== "undefined";
+ }
+ getUUID(name) {
+ if (!this.containsUUID(name)) {
+ throw new Error("The name's uuid was not found. name: " + name);
+ }
+ return this.nameAndUUIDs[name];
+ }
+}
+
diff --git a/model/controllerModel.js b/model/controllerModel.js
new file mode 100644
index 0000000..eefd9f9
--- /dev/null
+++ b/model/controllerModel.js
@@ -0,0 +1,85 @@
+import Controller from "../controller";
+import CommandRunner from "../commandRunner";
+import publisher from "../publisher";
+import ComponentBase from "../componentBase";
+
+export default class ControllerModel extends ComponentBase {
+ constructor() {
+ super();
+
+ // 最初、controllerはunnamedControllerに追加される。
+ // name がわかると、それが controllers に移行される。
+ // こうすることで、clientが異なっても、nameが同じ場合、HPなどを共有できるようになる。
+
+ // { key: Client, ... }
+ this.unknownClients = {};
+ // { name: Controller, ... }
+ this.controllers = {};
+ }
+ addUnknownClient(key, client) {
+ this.unknownClients[key] = client;
+ this.publish("addedUnknown", key, client);
+ }
+ addClient(name, key) {
+ this.controllers[name].setClient(this.unknownClients[key]);
+ this.removeUnknownClient(key);
+ this.publish("addedClient", name);
+ }
+ addController(name) {
+ this.controllers[name] = new Controller(name, new CommandRunner(name));
+ this.publish("addedController", name);
+ }
+ setName(key, name) {
+ if (!this.unknownClients[key]) {
+ throw new Error("setNameしようとしたところ、keyに対するclientが見つかりませんでした。 key: " + key);
+ }
+ const isNewName = !this.controllers[name];
+ if (isNewName) {
+ this.addController(name);
+ } else if (this.controllers[name].client) {
+ throw new Error("setNameしようとしましたが、既にclientが存在します。 name: " + name);
+ }
+ this.addClient(name, key);
+ }
+ removeUnknownClient(key) {
+ if (this.hasInUnknownClients(key)) {
+ delete this.unknownClients[key];
+ this.publish("removedUnknown", key);
+ }
+ }
+ removeClient(name) {
+ if (this.has(name)) {
+ this.controllers[name].setClient(null);
+ this.publish("removedClient", name);
+ }
+ }
+ hasInUnknownClients(key) {
+ return typeof this.unknownClients[key] !== "undefined";
+ }
+ has(name) {
+ return typeof this.controllers[name] !== "undefined";
+ }
+ get(name) {
+ return this.controllers[name];
+ }
+ getAllStates() {
+ const result = {};
+ for (let name in this.controllers) {
+ result[name] = this.controllers[name].getStates();
+ }
+ return result;
+ }
+ getUnnamedKeys() {
+ return Object.keys(this.unknownClients);
+ }
+ toName(key) {
+ for (let name in this.controllers) {
+ if (this.controllers[name].client &&
+ this.controllers[name].client.key === key) {
+ return name;
+ }
+ }
+ return null;
+ }
+}
+
diff --git a/model/orbModel.js b/model/orbModel.js
new file mode 100644
index 0000000..f69d04f
--- /dev/null
+++ b/model/orbModel.js
@@ -0,0 +1,76 @@
+import ComponentBase from "../componentBase";
+
+export default class OrbModel extends ComponentBase {
+ constructor() {
+ super();
+
+ this.spheroWS = null;
+ this.orbs = {};
+ }
+ setSpheroWS(spheroWS) {
+ this.spheroWS = spheroWS;
+ }
+ add(orbName, orb) {
+ this.orbs[orbName] = orb;
+ }
+ setBattery(orbName, battery) {
+ if (this.has(orbName)) {
+ this.orbs[orbName].battery = battery;
+ }
+ }
+ setLink(orbName, link) {
+ if (this.has(orbName)) {
+ this.orbs[orbName].link = link;
+ }
+ }
+ setPingState(orbName, state) {
+ if (this.has(orbName)) {
+ this.orbs[orbName].pingState = state;
+ }
+ }
+ remove(orbName) {
+ if (this.has(orbName)) {
+ delete this.orbs[orbName];
+ }
+ }
+ getNames() {
+ return Object.keys(this.orbs);
+ }
+ get(orbName) {
+ return this.orbs[orbName];
+ }
+ getDiff(comparisonOrbMap) {
+ const getAddedItem = (before, after) => {
+ return after.filter(item => before.indexOf(item) === -1);
+ };
+ const orbNames = this.getNames();
+ const added = getAddedItem(orbNames, comparisonOrbMap.getNames());
+ const removed = getAddedItem(comparisonOrbMap.getNames(), orbNames);
+ return {
+ added,
+ removed,
+ noChanged: orbNames.filter(item => comparisonOrbMap.has(item))
+ };
+ }
+ has(orbName) {
+ return typeof this.orbs[orbName] !== "undefined";
+ }
+ toArray() {
+ return Object.keys(this.orbs).map(orbName => this.orbs[orbName]);
+ }
+
+ getUnlinkedOrbs() {
+ if (!this.spheroWS) {
+ throw new Error("spheroWS is null.");
+ }
+ return this.spheroWS.spheroServer.getUnlinkedOrbs();
+ }
+
+ getOrbFromSpheroWS(orbName) {
+ if (!this.spheroWS) {
+ throw new Error("spheroWS is null.");
+ }
+ return this.spheroWS.spheroServer.getOrb(orbName);
+ }
+}
+
diff --git a/orbController.js b/orbController.js
new file mode 100644
index 0000000..97202bd
--- /dev/null
+++ b/orbController.js
@@ -0,0 +1,148 @@
+import ComponentBase from "./componentBase";
+
+export default class OrbController extends ComponentBase {
+ constructor(models, connector, spheroWS, defaultColor, collisionConfig) {
+ super(models);
+
+ this.spheroWS = spheroWS;
+ this.connector = connector;
+ this.defaultColor = defaultColor;
+ this.collisionConfig = collisionConfig;
+
+ this.subscribeModel("addedOrb", this.addOrbToModel);
+ this.subscribeModel("removedOrb", this.removeOrbFromModel);
+ this.subscribeModel("pingAll", this.setPingStateAll);
+ this.subscribeModel("updateBattery", this.updateBattery);
+ this.subscribeModel("replyPing", this.updatePingState);
+ this.subscribeModel("checkBattery", this.checkBattery);
+ this.subscribeModel("addOrb", this.addOrb);
+ this.subscribeModel("removeOrb", this.removeOrb);
+ this.subscribeModel("pingAll", this.pingAll);
+ }
+
+ addOrbToModel(name, orb) {
+ if (this.orbModel.has(name)) {
+ throw new Error(`追加しようとしたOrbは既に存在します。 : ${name}`);
+ }
+ this.orbModel.add(name, {
+ orbName: name,
+ port: orb.port,
+ battery: null,
+ link: "unlinked",
+ pingState: "unchecked"
+ });
+ this.initializeOrb(orb);
+ }
+
+ removeOrbFromModel(name) {
+ if (!this.orbModel.has(name)) {
+ throw new Error(`削除しようとしたOrbは存在しません。 : ${name}`);
+ }
+ this.orbModel.remove(name);
+ }
+
+ setPingStateAll() {
+ for (let orbName in this.orbModel.orbs) {
+ this.orbModel.setPingState(orbName, "no reply");
+ }
+ }
+
+ updateBattery(name, batteryState) {
+ if (!this.orbModel.has(name)) {
+ throw new Error("updateBattery しようとしましたが、orb が見つかりませんでした。 : " + name);
+ }
+ this.orbModel.setBattery(name, batteryState);
+ }
+
+ updatePingState(name) {
+ if (!this.orbModel.has(name)) {
+ throw new Error("updatePingState しようとしましたが、orb が見つかりませんでした。 : " + name);
+ }
+ this.orbModel.setPingState(name, "reply");
+ }
+
+ checkBattery() {
+ const orbs = this.spheroWS.spheroServer.getOrb();
+ for (let orbName in orbs) {
+ if (!this.appModel.isTestMode) {
+ orbs[orbName].instance.getPowerState((error, data) => {
+ if (error) {
+ throw new Error(error);
+ } else {
+ this.publish("updateBattery", orbName, data.batteryState);
+ }
+ });
+ } else {
+ this.publish("updateBattery", orbName, "test-battery");
+ }
+ }
+ }
+
+ pingAll() {
+ const orbs = this.spheroWS.spheroServer.getOrb();
+ for (let orbName in orbs) {
+ orbs[orbName].instance.ping((err, data) => {
+ if (!err) {
+ this.publish("replyPing", orbName);
+ } else {
+ this.publish("log", "Ping error: \n" + err.toString(), "error");
+ }
+ });
+ }
+ }
+
+ initializeOrb(orb) {
+ if (!this.appModel.isTestMode) {
+ const rawOrb = orb.instance;
+ rawOrb.color(this.defaultColor);
+ rawOrb.on("collision", () => {
+ this.publishCollision(orb);
+ });
+ }
+ }
+
+ addOrb(name, port) {
+ if (this.appModel.containsUUID(name)) {
+ port = this.appModel.getUUID(name);
+ console.log("changed!", port);
+ }
+ const rawOrb = this.spheroWS.spheroServer.makeRawOrb(name, port);
+ if (!this.appModel.isTestMode) {
+ if (!this.connector.isConnecting(port)) {
+ this.appModel.resetError121Count();
+ this.connector.connect(port, rawOrb.instance).then(() => {
+ rawOrb.instance.setInactivityTimeout(9999999, function(err, data) {
+ if (err) {
+ throw new Error(err);
+ }
+ console.log("data: " + data);
+ });
+
+ this.publish("log", "connected orb.", "success");
+
+ rawOrb.instance.configureCollisions(this.collisionConfig, () => {
+ this.publish("log", "configured orb.", "success");
+ this.spheroWS.spheroServer.addOrb(rawOrb);
+ rawOrb.instance.streamOdometer();
+ rawOrb.instance.on("odometer", data => {
+ this.publish("streamed", name, new Date());
+ });
+ });
+ });
+ } else {
+ console.warn("Tryed to connect but a orb is connecting.");
+ }
+ } else {
+ this.spheroWS.spheroServer.addOrb(rawOrb);
+ }
+ }
+
+ removeOrb(name) {
+ console.log("removing... : " + name);
+ this.spheroWS.spheroServer.removeOrb(name);
+ }
+
+ publishCollision(orb) {
+ this.publish("collision", orb);
+ }
+}
diff --git a/package.json b/package.json
index 501ef99..37a71f5 100644
--- a/package.json
+++ b/package.json
@@ -7,7 +7,8 @@
"build": "webpack --progress --colors",
"build:watch": "webpack --progress --colors --watch",
"start": "babel-node main.js",
- "test": "babel-node main.js --test",
+ "testrun": "babel-node main.js --test",
+ "test": "mocha test/**/*.js --compilers js:babel-register",
"debug": "babel-node --debug-brk main.js"
},
"babel": {
@@ -31,10 +32,12 @@
"babel-core": "^6.10.4",
"babel-loader": "^6.2.4",
"babel-preset-es2015": "^6.9.0",
+ "babel-register": "^6.22.0",
"css-loader": "^0.23.1",
"gulp": "^3.9.1",
"minimist": "^1.2.0",
- "noble": "^1.7.0",
+ "mocha": "^3.2.0",
+ "sinon": "^1.17.7",
"style-loader": "^0.13.1",
"webpack": "^1.13.1"
},
@@ -44,7 +47,10 @@
"knockout": "^3.4.0",
"socket.io": "^1.4.8",
"sphero": "github:orbotix/sphero.js#master",
- "sphero-websocket": "^0.5.10",
"sphero-ws-virtual-plugin": "1.1.2"
+ },
+ "optionalDependencies": {
+ "noble": "^1.7.0",
+ "sphero-websocket": "^0.5.10"
}
}
diff --git a/publisher.js b/publisher.js
index b00e517..9b608b0 100644
--- a/publisher.js
+++ b/publisher.js
@@ -1,2 +1,36 @@
-import { EventEmitter } from "events";
-export default new EventEmitter();
+export class EventPublisher {
+ constructor() {
+ this.observeFunctions = {};
+ this.observeFunctionsInModel = {};
+ }
+
+ subscribe(subjectName, observeFunction) {
+ if (!this.observeFunctions[subjectName]) {
+ this.observeFunctions[subjectName] = [];
+ }
+ this.observeFunctions[subjectName].push(observeFunction);
+ }
+
+ subscribeModel(subjectName, observeFunction) {
+ if (!this.observeFunctionsInModel[subjectName]) {
+ this.observeFunctionsInModel[subjectName] = [];
+ }
+ this.observeFunctionsInModel[subjectName].push(observeFunction);
+ }
+
+ publish(author, subjectName, ...data) {
+ (this.observeFunctionsInModel[subjectName] || [])
+ .concat(this.observeFunctions[subjectName] || [])
+ .forEach(observeFunction => {
+
+ observeFunction(author, ...data);
+ });
+ }
+
+ clearObserveFunctions() {
+ this.observeFunctions = {};
+ this.observeFunctionsInModel = {};
+ }
+}
+
+export default new EventPublisher();
diff --git a/rankingMaker.js b/rankingMaker.js
index 77696f6..62942ec 100644
--- a/rankingMaker.js
+++ b/rankingMaker.js
@@ -1,13 +1,29 @@
-export default class RankingMaker {
- constructor() {
+import ComponentBase from "./componentBase";
+
+export default class RankingMaker extends ComponentBase {
+ constructor(models) {
+ super(models);
+
+ this.subscribe("rankingState", this.updateRankingState);
+ this.subscribe("updatedHp", this.make);
+ this.subscribe("updatedLink", this.make);
+ this.subscribe("updatedColor", this.make);
}
- make(controllers) {
+
+ updateRankingState(state) {
+ if (state === "show") {
+ this.make();
+ }
+ }
+
+ make() {
+ const controllers = this.controllerModel.controllers;
const controllerNames = Object.keys(controllers);
// indexが順位となっている
// [ { hp: 100, name: "xxx" }, { hp: 80, name: "xxx" }, ...]
const ranking = controllerNames.filter(name => {
// まず鬼であるものを除外する
- return controllers[name].linkedOrb !== null && !controllers[name].isOni;
+ return controllers[name].linkedOrb && !controllers[name].isOni;
}).sort((a, b) => {
// HPに基づき、降順にソートする
return controllers[b].hp - controllers[a].hp;
@@ -22,10 +38,11 @@ export default class RankingMaker {
// { name: getStates(), ... }
const onis = {};
controllerNames.filter(name => {
- return controllers[name].linkedOrb !== null && controllers[name].isOni
+ return controllers[name].linkedOrb && controllers[name].isOni;
}).forEach(name => {
onis[name] = controllers[name].getStates();
});
- return { ranking, onis };
+
+ this.publish("ranking", { ranking, onis });
}
}
diff --git a/readme.md b/readme.md
index 1514834..ba7be17 100644
--- a/readme.md
+++ b/readme.md
@@ -1,5 +1,7 @@
# onigo-server
+[](https://travis-ci.org/shundroid/onigo-server)
+

## About
diff --git a/scoreboard.js b/scoreboard.js
index 9d690c1..be37466 100644
--- a/scoreboard.js
+++ b/scoreboard.js
@@ -1,56 +1,39 @@
import express from "express";
import io from "socket.io";
-import controllerModel from "./controllerModel";
-import RankingMaker from "./rankingMaker";
-import eventPublisher from "./publisher";
-
-let scoreboardInstance = null;
-
-function Scoreboard(port) {
- if (scoreboardInstance !== null) {
- return scoreboardInstance;
+import ComponentBase from "./componentBase";
+import { Server as createServer } from "http";
+
+export default class Scoreboard extends ComponentBase {
+ constructor(models, port) {
+ super(models);
+
+ this.app = express();
+ this.server = createServer(this.app);
+ this.io = io(this.server);
+ this.io.origins(`localhost:${port}`);
+
+ this.app.use(express.static("scoreboard"));
+ this.server.listen(port, () => {
+ console.log(`score is listening on port ${port}`);
+ });
+
+ this.currentRanking = null;
+
+ this.sockets = [];
+ this.io.on("connection", socket => {
+ console.log("a scoreboard connected.");
+ this.sockets.push(socket);
+ if (this.appModel.ranking) {
+ socket.emit("data", this.appModel.ranking);
+ }
+ });
+
+ this.subscribe("ranking", this.updateRanking);
}
- scoreboardInstance = this;
-
- this.app = express();
- this.server = require("http").Server(this.app);
- this.io = require("socket.io")(this.server);
- this.io.origins(`localhost:${port}`);
-
- this.app.use(express.static("scoreboard"));
- this.server.listen(port, () => {
- console.log(`score is listening on port ${port}`);
- });
-
- this.currentRanking = null;
- this.rankingMaker = new RankingMaker();
-
- this.sockets = [];
- this.io.on("connection", socket => {
- console.log("a scoreboard connected.");
- this.sockets.push(socket);
- if (this.currentRanking !== null) {
- socket.emit("data", this.currentRanking);
- }
- });
-
- eventPublisher.on("updatedHp", () => {
- this.updateRanking();
- });
- eventPublisher.on("updateLink", () => {
- this.updateRanking();
- });
- eventPublisher.on("color", () => {
- this.updateRanking();
- });
+ updateRanking(ranking) {
+ this.sockets.forEach(socket => {
+ socket.emit("data", ranking);
+ });
+ }
}
-
-Scoreboard.prototype.updateRanking = function() {
- this.currentRanking = this.rankingMaker.make(controllerModel.controllers);
- this.sockets.forEach(socket => {
- socket.emit("data", this.currentRanking);
- });
-};
-
-module.exports = Scoreboard;
diff --git a/spheroServerManager.js b/spheroServerManager.js
new file mode 100644
index 0000000..d3ba429
--- /dev/null
+++ b/spheroServerManager.js
@@ -0,0 +1,36 @@
+import ComponentBase from "./componentBase";
+
+export default class SpheroServerManager extends ComponentBase {
+ constructor(models, spheroWS) {
+ super(models);
+
+ this.spheroWS = spheroWS;
+ this.spheroServer = this.spheroWS.spheroServer;
+
+ this.spheroServer.events.on("addClient", this.publishAddClient.bind(this));
+ this.spheroServer.events.on("removeClient", this.publishRemoveClient.bind(this));
+ this.spheroServer.events.on("addOrb", this.publishAddedOrb.bind(this));
+ this.spheroServer.events.on("removeOrb", this.publishRemovedOrb.bind(this));
+ }
+
+ publishAddClient(key, client) {
+ this.publish("addClient", key, client);
+ }
+
+ publishRemoveClient(key) {
+ this.publish("removeClient", key);
+ }
+
+ publishAddedOrb(name, orb) {
+ this.publish("addedOrb", name, orb);
+ }
+
+ publishRemovedOrb(name) {
+ this.publish("removedOrb", name);
+ }
+
+ publishCollision(orb) {
+ this.publish("collision", orb);
+ }
+
+}
diff --git a/test/commandRunner.test.js b/test/commandRunner.test.js
new file mode 100644
index 0000000..872081c
--- /dev/null
+++ b/test/commandRunner.test.js
@@ -0,0 +1,26 @@
+import assert from "assert";
+import CommandRunner from "../commandRunner";
+
+describe("CommandRunner", () => {
+ const commandRunner = new CommandRunner();
+ const timeoutDelay = 1000;
+ describe("#stopLoop()", () => {
+ const timeoutId = setTimeout(() => {}, timeoutDelay);
+ commandRunner.loopTimeoutId = timeoutId;
+ it("should set loopTimeoutId to null", () => {
+ assert.equal(commandRunner.loopTimeoutId, timeoutId);
+ commandRunner.stopLoop();
+ assert.equal(commandRunner.loopTimeoutId, null);
+ });
+ });
+ describe("clearCustomTimeoutIds()", () => {
+ const timeoutId = setTimeout(() => {}, timeoutDelay);
+ commandRunner.customTimeoutIds = { test1: timeoutId };
+ it("should delete timeoutId from customTimeoutIds", () => {
+ assert(commandRunner.customTimeoutIds["test1"]);
+ assert.equal(commandRunner.customTimeoutIds["test1"], timeoutId);
+ commandRunner.clearCustomTimeoutIds();
+ assert(!commandRunner.customTimeoutIds["test1"]);
+ });
+ });
+});
diff --git a/test/componentBase.test.js b/test/componentBase.test.js
new file mode 100644
index 0000000..aefad75
--- /dev/null
+++ b/test/componentBase.test.js
@@ -0,0 +1,55 @@
+import assert from "assert";
+import publisher from "../publisher";
+import ComponentBase from "../componentBase";
+
+describe("ComponentBase", () => {
+ publisher.clearObserveFunctions();
+ describe("#constructor()", () => {
+ const testModel = "test-model";
+ const component = new ComponentBase({ testModel });
+
+ it("should set models", () => {
+ assert(component.testModel);
+ assert.equal(component.testModel, testModel);
+ });
+ });
+ describe("#publish()", () => {
+ const component = new ComponentBase();
+ publisher.subscribe("test1", (author, data) => {
+ it("should publish author", () => {
+ assert(author === component);
+ });
+ it("should publish correct data", () => {
+ assert(data === "test-data");
+ });
+ });
+ component.publish("test1", "test-data");
+ });
+ describe("#subscribe()", () => {
+ const component = new ComponentBase();
+ component.subscribe("test2", data => {
+ it("should be called", () => {
+ assert(data === "test-data-2");
+ });
+ });
+ publisher.publish(this, "test2", "test-data-2");
+
+ let isCalled = false;
+ component.subscribe("test3", data => {
+ isCalled = data === "test-data-3";
+ });
+ publisher.publish(component, "test3", "test-data-3");
+ it("should not call function when author is same", () => {
+ assert(!isCalled);
+ });
+ });
+ describe("#subscribeModel()", () => {
+ const component = new ComponentBase();
+ component.subscribeModel("test4", data => {
+ it("should be called", () => {
+ assert(data === "test-data-4");
+ });
+ });
+ publisher.publish(this, "test4", "test-data-4");
+ });
+});
diff --git a/test/controllerManager.test.js b/test/controllerManager.test.js
new file mode 100644
index 0000000..5cdd05d
--- /dev/null
+++ b/test/controllerManager.test.js
@@ -0,0 +1,252 @@
+import assert from "assert";
+import ControllerManager from "../controllerManager";
+import ControllerModel from "../model/controllerModel";
+import AppModel from "../model/appModel";
+import publisher from "../publisher";
+import sinon from "sinon";
+import config from "../config";
+
+describe("ControllerManager", () => {
+ publisher.clearObserveFunctions();
+
+ const appModel = new AppModel();
+ const controllerModel = new ControllerModel();
+
+ const testKey = "key-test";
+ const testName = "name-test";
+
+ controllerModel.addUnknownClient(testKey, {
+ sendCustomMessage() {},
+ key: testKey,
+ on() {}
+ });
+ controllerModel.setName(testKey, testName);
+ controllerModel.get(testName).linkedOrb = {
+ command() {},
+ hasCommand() { return true; }
+ };
+
+ const controllerManager = new ControllerManager({ appModel, controllerModel }, config.defaultHp, config.damage);
+ describe("#changeIsOni", () => {
+ const changeIsOniSpy = sinon.spy(controllerManager, "changeIsOni");
+ const setIsOniSpy = sinon.spy(controllerModel.get(testName), "setIsOni");
+ controllerManager.changeIsOni(testName, true);
+
+ it("should be called", () => {
+ assert(changeIsOniSpy.withArgs(testName, true).called);
+ });
+
+ it("should call setIsOni of controller", () => {
+ assert(setIsOniSpy.withArgs(true).called);
+ });
+
+ changeIsOniSpy.restore();
+ setIsOniSpy.restore();
+ });
+
+ describe("#resetHp", () => {
+ const resetHpSpy = sinon.spy(controllerManager, "resetHp");
+ const setHpSpy = sinon.spy(controllerModel.get(testName), "setHp");
+ controllerManager.resetHp(testName);
+
+ it("should be called", () => {
+ assert(resetHpSpy.withArgs(testName).called);
+ });
+
+ it("should call setHp of controller", () => {
+ assert(setHpSpy.withArgs(config.defaultHp).called);
+ });
+
+ resetHpSpy.restore();
+ setHpSpy.restore();
+ });
+
+ describe("#changeColor", () => {
+ const changeColorSpy = sinon.spy(controllerManager, "changeColor");
+ const setColorSpy = sinon.spy(controllerModel.get(testName), "setColor");
+ controllerManager.changeColor(testName, "red");
+
+ it("should be called", () => {
+ assert(changeColorSpy.withArgs(testName, "red").called);
+ });
+
+ it("should call setColor of controller", () => {
+ assert(setColorSpy.withArgs("red").called);
+ });
+
+ changeColorSpy.restore();
+ setColorSpy.restore();
+ });
+
+ describe("#updateGameState", () => {
+ const updateGameStateSpy = sinon.spy(controllerManager, "updateGameState");
+ const sendCustomMessageSpy = sinon.spy(controllerModel.get(testName).client, "sendCustomMessage");
+ controllerManager.updateGameState("active");
+
+ it("should be called", () => {
+ assert(updateGameStateSpy.withArgs("active").called);
+ });
+
+ it("should send gameState to client", () => {
+ assert(sendCustomMessageSpy.withArgs("gameState", "active").called);
+ });
+
+ updateGameStateSpy.restore();
+ sendCustomMessageSpy.restore();
+ });
+
+ describe("#updateRankingState", () => {
+ const updateGameStateSpy = sinon.spy(controllerManager, "updateRankingState");
+ const sendCustomMessageSpy = sinon.spy(controllerModel.get(testName).client, "sendCustomMessage");
+ controllerManager.updateRankingState("show");
+
+ it("should be called", () => {
+ assert(updateGameStateSpy.withArgs("show").called);
+ });
+
+ it("should send gameState to client", () => {
+ assert(sendCustomMessageSpy.withArgs("rankingState", "show").called);
+ });
+
+ updateGameStateSpy.restore();
+ sendCustomMessageSpy.restore();
+ });
+
+ describe("#updateRanking", () => {
+ const updateGameStateSpy = sinon.spy(controllerManager, "updateRanking");
+ const sendCustomMessageSpy = sinon.spy(controllerModel.get(testName).client, "sendCustomMessage");
+ controllerManager.updateRanking("test-ranking");
+
+ it("should be called", () => {
+ assert(updateGameStateSpy.withArgs("test-ranking").called);
+ });
+
+ it("should send gameState to client", () => {
+ assert(sendCustomMessageSpy.withArgs("ranking", "test-ranking").called);
+ });
+
+ updateGameStateSpy.restore();
+ sendCustomMessageSpy.restore();
+ });
+
+ describe("#damage", () => {
+ const controller = controllerModel.get(testName);
+ controller.hp = 100;
+ controller.isOni = false;
+ appModel.gameState = "active";
+
+ const damageSpy = sinon.spy(controllerManager, "damage");
+ const setHpSpy = sinon.spy(controller, "setHp");
+ const testOrb = {
+ linkedClients: [testKey]
+ };
+ controllerManager.damage(testOrb);
+
+ it("should be called", () => {
+ assert(damageSpy.withArgs(testOrb).called);
+ });
+
+ it("should call setHp", () => {
+ assert(setHpSpy.withArgs(config.defaultHp - config.damage).called);
+ });
+
+ damageSpy.restore();
+ setHpSpy.restore();
+ });
+
+ describe("#updateAvailableCommandsCount", () => {
+ const controller = controllerModel.get(testName);
+ const updateSpy = sinon.spy(controllerManager, "updateAvailableCommandsCount");
+ const sendCustomMessageSpy = sinon.spy(controller.client, "sendCustomMessage");
+
+ controllerManager.updateAvailableCommandsCount(4);
+
+ it("should be called", () => {
+ assert(updateSpy.withArgs(4).called);
+ });
+
+ it("should call sendCustomMessage", () => {
+ assert(sendCustomMessageSpy.withArgs("availableCommandsCount", 4).called);
+ });
+
+ updateSpy.restore();
+ sendCustomMessageSpy.restore();
+ });
+
+ describe("#initializeClient", () => {
+ const controller = controllerModel.get(testName);
+ const initializeClientSpy = sinon.spy(controllerManager, "initializeClient");
+ const sendCustomMessageSpy = sinon.spy(controller.client, "sendCustomMessage");
+
+ controllerManager.initializeClient(testName);
+
+ it("should be called", () => {
+ assert(initializeClientSpy.withArgs(testName));
+ });
+
+ it("should send default datas", () => {
+ assert(sendCustomMessageSpy.withArgs("gameState", appModel.gameState).called);
+ assert(sendCustomMessageSpy.withArgs("rankingState", appModel.rankingState).called);
+ assert(sendCustomMessageSpy.withArgs("availableCommandsCount", appModel.availableCommandsCount).called);
+ assert(sendCustomMessageSpy.withArgs("clientKey", testKey).called);
+ });
+
+ initializeClientSpy.restore();
+ sendCustomMessageSpy.restore();
+ });
+
+ describe("#initializeController", () => {
+ it("should publish hp when controller emitted hp", () => {
+ const controller = controllerModel.get(testName);
+ controllerManager.initializeController(testName);
+ const testHp = 80;
+
+ const spy = sinon.spy();
+ publisher.subscribe("hp", spy);
+ controller.emit("hp", testHp);
+ assert(spy.withArgs(controllerManager, testName, testHp).calledOnce);
+ });
+ });
+
+ describe("#setCommands", () => {
+ const controller = controllerModel.get(testName);
+ const setCommandsSpy = sinon.spy(controllerManager, "setCommands");
+ const setCommandsInCommandRunnerSpy = sinon.spy(controller.commandRunner, "setCommands");
+
+ const commands = [
+ { commandName: "roll", args: [80, 80] }
+ ];
+
+ controllerManager.setCommands(testName, commands);
+
+ it("should be called", () => {
+ assert(setCommandsSpy.withArgs(testName).called);
+ });
+
+ it("should call setCommands in commandRunner", () => {
+ assert(setCommandsInCommandRunnerSpy.withArgs("commands"));
+ });
+
+ setCommandsSpy.restore();
+ setCommandsInCommandRunnerSpy.restore();
+ });
+
+ describe("#command", () => {
+ const controller = controllerModel.get(testName);
+ const commandSpy = sinon.spy(controllerManager, "command");
+ const commandInOrbSpy = sinon.spy(controller.linkedOrb, "command");
+
+ controllerManager.command(testName, "roll", "args");
+
+ it("should be called", () => {
+ assert(commandSpy.withArgs(testName, "roll", "args").called);
+ });
+
+ it("should call setCommands in commandRunner", () => {
+ assert(commandInOrbSpy.withArgs("roll", "args"));
+ });
+
+ commandSpy.restore();
+ commandInOrbSpy.restore();
+ });
+});
diff --git a/test/dashboard.test.js b/test/dashboard.test.js
new file mode 100644
index 0000000..afe8260
--- /dev/null
+++ b/test/dashboard.test.js
@@ -0,0 +1,96 @@
+import assert from "assert";
+import Dashboard from "../dashboard";
+import publisher from "../publisher";
+import AppModel from "../model/appModel";
+import ControllerModel from "../model/controllerModel";
+import OrbModel from "../model/orbModel";
+import sinon from "sinon";
+import { EventEmitter } from "events";
+
+describe("Dashboard", function() {
+ let controllerModel;
+ let dashboard;
+
+ before(done => {
+ controllerModel = new ControllerModel();
+ dashboard = new Dashboard({
+ appModel: new AppModel(),
+ controllerModel,
+ orbModel: new OrbModel()
+ }, 8082);
+ dashboard.initializeConnection(new EventEmitter());
+ done();
+ });
+
+ beforeEach(done => {
+ publisher.clearObserveFunctions();
+ done();
+ });
+
+ afterEach(done => {
+ dashboard.server.close(() => {
+ done();
+ });
+ });
+
+ describe("#initializeConnection()", function() {
+ it("should register listener to the socket", () => {
+ const spy = sinon.spy();
+ const activeState = "active";
+ publisher.subscribe("gameState", spy);
+ dashboard.socket.emit("gameState", activeState);
+ assert(spy.withArgs(dashboard, activeState).calledOnce);
+ });
+ it("should register publishPingAll", () => {
+ const spy = sinon.spy(dashboard, "publishPingAll");
+ dashboard.socket.emit("pingAll");
+ assert(spy.calledOnce);
+ });
+ it("should register publishUpdateLink", () => {
+ const spy = sinon.spy(dashboard, "publishUpdateLink");
+ const testControllerName = "controller-name";
+ const testOrbName = "orb-name";
+ dashboard.socket.emit("link", testControllerName, testOrbName);
+ assert(spy.withArgs(testControllerName, testOrbName).calledOnce);
+ });
+ });
+
+ describe("#publishPingAll()", () => {
+ it("should publish pingAll to eventPublisher", () => {
+ const spy = sinon.spy();
+ publisher.subscribe("pingAll", spy);
+ dashboard.publishPingAll();
+ assert(spy.withArgs(dashboard).calledOnce);
+ });
+ });
+
+ describe("#publishUpdateLink", () => {
+ it("should publish updateLink to eventPublisher", () => {
+ const spy = sinon.spy();
+ const testControllerName = "controllerName";
+ const testOrbName = "orbName";
+ publisher.subscribe("updateLink", spy);
+ dashboard.publishUpdateLink(testControllerName, testOrbName);
+ assert(spy.withArgs(dashboard, testControllerName, testOrbName));
+ });
+ });
+
+ describe("#formatTime()", () => {
+ it("should format correctly", () => {
+ const date = new Date();
+ const formattedTime = dashboard.formatTime(date);
+ const nums = formattedTime.split(":");
+
+ assert.equal(nums.length, 3);
+
+ assert.equal(nums[0].length, 2);
+ assert.equal(parseInt(nums[0]), date.getHours());
+
+ assert.equal(nums[1].length, 2);
+ assert.equal(parseInt(nums[1]), date.getMinutes());
+
+ assert.equal(nums[2].length, 2);
+ assert.equal(parseInt(nums[2]), date.getSeconds());
+ });
+ });
+});
diff --git a/test/model/appModel.test.js b/test/model/appModel.test.js
new file mode 100644
index 0000000..b6842ed
--- /dev/null
+++ b/test/model/appModel.test.js
@@ -0,0 +1,79 @@
+import assert from "assert";
+import AppModel from "../../model/appModel";
+
+describe("AppModel", () => {
+ let appModel;
+ beforeEach(done => {
+ appModel = new AppModel();
+ done();
+ });
+ describe("#constructor()", () => {
+ it("should initialize gameState", () => {
+ assert.equal(appModel.gameState, "inactive");
+ });
+ it("should initialize rankingState", () => {
+ assert.equal(appModel.rankingState, "hide");
+ });
+ it("should initialize availableCommandsCount", () => {
+ assert.equal(appModel.availableCommandsCount, 1);
+ });
+ it("should initialize nameAndUUIDs", () => {
+ assert.deepEqual(appModel.nameAndUUIDs, {});
+ });
+ });
+ describe("#updateGameState()", () => {
+ it("should update gameState", () => {
+ appModel.updateGameState("active");
+ assert.equal(appModel.gameState, "active");
+ });
+ });
+ describe("#updateRankingState()", () => {
+ it("should update rankingState", () => {
+ appModel.updateRankingState("show");
+ assert.equal(appModel.rankingState, "show");
+ });
+ });
+ describe("#updateAvailableCommandsCount", () => {
+ it("should update availableCommandsCount", () => {
+ appModel.updateAvailableCommandsCount(6);
+ assert.equal(appModel.availableCommandsCount, 6);
+ });
+ });
+ describe("#updateRanking", () => {
+ it("should update ranking", () => {
+ const ranking = "test-ranking";
+ appModel.updateRanking(ranking);
+ assert.equal(appModel.ranking, ranking);
+ });
+ });
+ describe("#setNameOfUUID", () => {
+ it("should set the name of uuid", () => {
+ const testName = "test-name";
+ const testUUID = "test-uuid";
+ appModel.setNameOfUUID(testName, testUUID);
+ assert(appModel.nameAndUUIDs[testName]);
+ assert.equal(appModel.nameAndUUIDs[testName], testUUID);
+ });
+ });
+ describe("#containsUUID", () => {
+ it("should return exist of name", () => {
+ const testName = "test-name";
+ const testUUID = "test-uuid";
+ appModel.nameAndUUIDs[testName] = testUUID;
+ assert(appModel.containsUUID(testName));
+ });
+ });
+ describe("#getUUID", () => {
+ it("should return uuid of the name", () => {
+ const testName = "test-name";
+ const testUUID = "test-uuid";
+ appModel.nameAndUUIDs[testName] = testUUID;
+ assert(appModel.getUUID(testName), testUUID);
+ });
+ it("should throw an error if the name isn't found", () => {
+ assert.throws(() => {
+ appModel.getUUID(testName);
+ }, Error);
+ });
+ });
+});
diff --git a/test/model/controllerModel.test.js b/test/model/controllerModel.test.js
new file mode 100644
index 0000000..880878c
--- /dev/null
+++ b/test/model/controllerModel.test.js
@@ -0,0 +1,34 @@
+import assert from "assert";
+import publisher from "../../publisher";
+import ControllerModel from "../../model/controllerModel";
+
+describe("ControllerModel", () => {
+ let controllerModel;
+ beforeEach(() => {
+ publisher.clearObserveFunctions();
+ controllerModel = new ControllerModel();
+ });
+ describe("#constructor", () => {
+ it("should initialize controllers", () => {
+ assert.deepEqual(controllerModel.controllers, {});
+ });
+ });
+ describe("#toName", () => {
+ it("should return name of the key", () => {
+ const testKey = "hoge";
+ const testName = "testController3";
+ controllerModel.controllers = {
+ testController1: {
+ client: { key: "invalid" }
+ },
+ testController2: {
+ }
+ };
+ controllerModel.controllers[testName] = {
+ client: { key: testKey }
+ };
+ const name = controllerModel.toName(testKey);
+ assert.equal(name, testName);
+ });
+ });
+});
diff --git a/test/orbController.test.js b/test/orbController.test.js
new file mode 100644
index 0000000..5d111e3
--- /dev/null
+++ b/test/orbController.test.js
@@ -0,0 +1,58 @@
+import assert from "assert";
+import AppModel from "../model/appModel";
+import OrbModel from "../model/orbModel";
+import OrbController from "../orbController";
+
+describe("OrbController", () => {
+ const appModel = new AppModel();
+ appModel.isTestMode = true;
+ const orbModel = new OrbModel();
+ const orbController = new OrbController({ appModel, orbModel }, {}, {}, "white", null);
+ const testName = "name-test";
+ beforeEach(done => {
+ orbModel.orbs = {};
+ orbController.addOrbToModel(testName, {
+ port: "test"
+ });
+ done();
+ });
+ describe("#addOrbToModel()", () => {
+ it("should add onto orbModel", () => {
+ assert(orbModel.has(testName));
+ const orb = orbModel.get(testName);
+ assert.equal(orb.orbName, testName);
+ assert.equal(orb.port, "test");
+ assert.equal(orb.battery, null);
+ assert.equal(orb.link, "unlinked");
+ assert.equal(orb.pingState, "unchecked");
+ });
+ });
+ describe("#removeOrbFromModel()", () => {
+ it("should remove from orbModel", () => {
+ assert(orbModel.has(testName));
+ orbController.removeOrbFromModel(testName);
+ assert(!orbModel.has(testName));
+ });
+ });
+ describe("#setPingStateAll()", () => {
+ it("should update pingState", () => {
+ assert(orbModel.has(testName));
+ orbController.setPingStateAll();
+ assert.equal(orbModel.get(testName).pingState, "no reply");
+ });
+ });
+ describe("#updateBattery()", () => {
+ it("should update batteryState", () => {
+ assert(orbModel.has(testName));
+ orbController.updateBattery(testName, "batteryState-test");
+ assert.equal(orbModel.get(testName).battery, "batteryState-test");
+ });
+ });
+ describe("#updatePingState()", () => {
+ it("should update pingState", () => {
+ assert(orbModel.has(testName));
+ orbController.updatePingState(testName);
+ assert.equal(orbModel.get(testName).pingState, "reply");
+ });
+ });
+});
diff --git a/test/publisher.test.js b/test/publisher.test.js
new file mode 100644
index 0000000..c98002e
--- /dev/null
+++ b/test/publisher.test.js
@@ -0,0 +1,74 @@
+import assert from "assert";
+import { EventPublisher } from "../publisher";
+
+describe("Publisher", function() {
+ describe("#constructor()", function() {
+ const publisher = new EventPublisher();
+ it("should initialize observeFunctions to {}", function() {
+ assert.deepEqual({}, publisher.observeFunctions);
+ });
+ });
+ describe("#subscribe()", function() {
+ const publisher = new EventPublisher();
+ const observeFunction = function() {};
+ publisher.subscribe("a", observeFunction);
+ it("should make subjectName into observeFunctions", function() {
+ assert.deepEqual(["a"], Object.keys(publisher.observeFunctions));
+ });
+ it("should add observeFunction into observeFunctions", function() {
+ assert.deepEqual([observeFunction], publisher.observeFunctions["a"]);
+ });
+ });
+ describe("#subscribeModel()", function() {
+ const publisher = new EventPublisher();
+ const observeFunction = function() {};
+ publisher.subscribeModel("a", observeFunction);
+ it("should make subjectName into observeFunctionsInModel", function() {
+ assert.deepEqual(["a"], Object.keys(publisher.observeFunctionsInModel));
+ });
+ it("should add observeFunction into observeFunctionsInModel", function() {
+ assert.deepEqual([observeFunction], publisher.observeFunctionsInModel["a"]);
+ });
+ });
+ describe("#publish", function() {
+ const publisher = new EventPublisher();
+ let isCalledModel = false;
+ const callbackInModel = function() {
+ isCalledModel = true;
+ };
+ const callback = function(author, data, data2, data3) {
+ it("This callback function should be called", () => {
+ assert(true);
+ });
+ it("This callback function should be called after callback function in model", () => {
+ assert(isCalledModel);
+ });
+ it("should equal author", () => {
+ assert.equal(author, "hello-author");
+ });
+ it("should equal data", () => {
+ assert.equal(data, "data-test");
+ assert.equal(data2, 100);
+ assert.equal(data3, true);
+ });
+ };
+ publisher.subscribe("a", callback);
+ publisher.subscribeModel("a", callbackInModel);
+ publisher.publish("hello-author", "a", "data-test", 100, true);
+ });
+ describe("#clearObserveFunctions", () => {
+ const publisher = new EventPublisher();
+ publisher.subscribe("hoge", () => {});
+ publisher.subscribeModel("hoge", () => {});
+
+ it("should clear observeFunctions and observeFunctionsInModel", () => {
+ assert.notDeepEqual(publisher.observeFunctions, {});
+ assert.notDeepEqual(publisher.observeFunctionsInModel, {});
+
+ publisher.clearObserveFunctions();
+
+ assert.deepEqual(publisher.observeFunctions, {});
+ assert.deepEqual(publisher.observeFunctionsInModel, {});
+ });
+ });
+});
diff --git a/test/rankingMaker.test.js b/test/rankingMaker.test.js
new file mode 100644
index 0000000..80678ce
--- /dev/null
+++ b/test/rankingMaker.test.js
@@ -0,0 +1,70 @@
+import assert from "assert";
+import RankingMaker from "../rankingMaker";
+import ControllerModel from "../model/controllerModel";
+import sinon from "sinon";
+import publisher from "../publisher";
+
+describe("RankingMaker", () => {
+ publisher.clearObserveFunctions();
+ describe("#make", () => {
+ const controllerModel = new ControllerModel();
+ controllerModel.controllers = {
+ controller1: {
+ linkedOrb: "orb1",
+ isOni: false,
+ hp: 80,
+ getStates() { return "states"; }
+ },
+ controller2: {
+ linkedOrb: "orb2",
+ isOni: false,
+ hp: 100,
+ getStates() { return "states"; }
+ },
+ controller3: {
+ linkedOrb: "orb3",
+ isOni: false,
+ hp: 80,
+ getStates() { return "states"; }
+ },
+ oni1: {
+ linkedOrb: "orb4",
+ isOni: true,
+ hp: 100,
+ getStates() { return "oni-states"; }
+ }
+ };
+
+ const rankingMaker = new RankingMaker({ controllerModel });
+ const makeSpy = sinon.spy(rankingMaker, "make");
+ let rankingDetails;
+ publisher.subscribe("ranking", (author, ranking) => {
+ it("should publish ranking", () => {
+ assert(author === rankingMaker);
+ rankingDetails = ranking;
+ });
+ });
+
+ rankingMaker.make("show");
+
+ it("should be called", () => {
+ assert(makeSpy.withArgs("show").called);
+ });
+
+ it("should return correct ranking", () => {
+ assert(typeof rankingDetails.ranking !== "undefined");
+ assert.equal(rankingDetails.ranking.length, 3);
+ assert.deepEqual(rankingDetails.ranking[0], { name: "controller2", states: "states", isTie: false });
+ assert.deepEqual(rankingDetails.ranking[1], { name: "controller1", states: "states", isTie: false });
+ assert.deepEqual(rankingDetails.ranking[2], { name: "controller3", states: "states", isTie: true });
+ });
+
+ it("should return correct onis", () => {
+ assert(typeof rankingDetails.onis !== "undefined");
+ assert(typeof rankingDetails.onis["oni1"] !== "undefined");
+ assert.equal(rankingDetails.onis["oni1"], "oni-states");
+ });
+
+ makeSpy.restore();
+ });
+});
diff --git a/test/spheroServerManager.test.js b/test/spheroServerManager.test.js
new file mode 100644
index 0000000..2b81da0
--- /dev/null
+++ b/test/spheroServerManager.test.js
@@ -0,0 +1,32 @@
+import assert from "assert";
+import SpheroServerManager from "../spheroServerManager";
+import publisher from "../publisher";
+import sinon from "sinon";
+import { EventEmitter } from "events";
+
+// test には sphero-websocket が必要で、その際ネイティブ依存のモジュールも必要になるので、
+// テストできない。テストするには、sphero-websocket の testMode 時にネイティブ依存モジュールを使わないよう
+// 改善しなければならない
+
+
+describe("SpheroServerManager", () => {
+ let spheroServerManager;
+ beforeEach(done => {
+ publisher.clearObserveFunctions();
+ spheroServerManager = new SpheroServerManager({}, {
+ spheroServer: { events: new EventEmitter() }
+ });
+ done();
+ });
+ describe("#publishAddedOrb", () => {
+ it("should publish addedOrb", () => {
+ const spy = sinon.spy();
+ const orbName = "orb1";
+ const orb = "orb";
+ publisher.subscribe("addedOrb", spy);
+ spheroServerManager.publishAddedOrb(orbName, orb);
+ assert(spy.withArgs(spheroServerManager, orbName, orb));
+ });
+ });
+});
+
diff --git a/test/uuidManager.test.js b/test/uuidManager.test.js
new file mode 100644
index 0000000..d2ffced
--- /dev/null
+++ b/test/uuidManager.test.js
@@ -0,0 +1,29 @@
+import assert from "assert";
+import UUIDManager from "../uuidManager";
+import publisher from "../publisher";
+import sinon from "sinon";
+import { EventEmitter } from "events";
+
+describe("UUIDManager", () => {
+ let uuidManager;
+ const noble = new EventEmitter();
+ beforeEach(done => {
+ noble.removeAllListeners();
+ publisher.clearObserveFunctions();
+ uuidManager = new UUIDManager({
+ appModel: { isTestMode: false }
+ }, noble);
+ done();
+ });
+
+ it("on#discover", () => {
+ const testName = "test-name";
+ const testUUID = "test-uuid";
+ const spy = sinon.spy();
+ publisher.subscribe("setNameOfUUID", spy);
+ noble.emit("discover", {
+ advertisement: { localName: testName },
+ uuid: testUUID
+ });
+ });
+});
diff --git a/test/virtualSpheroManager.test.js b/test/virtualSpheroManager.test.js
new file mode 100644
index 0000000..24be915
--- /dev/null
+++ b/test/virtualSpheroManager.test.js
@@ -0,0 +1,13 @@
+import assert from "assert";
+import VirtualSpheroManager from "../virtualSpheroManager";
+import VirtualSphero from "sphero-ws-virtual-plugin";
+
+describe("VirtualSpheroManager", () => {
+ describe("#constructor()", () => {
+ const virtualSpheroManager = new VirtualSpheroManager({}, 8081);
+ it("should initialize virtualSphero", () => {
+ assert(typeof virtualSpheroManager === "object");
+ assert(virtualSpheroManager.virtualSphero instanceof VirtualSphero);
+ });
+ });
+});
diff --git a/uuidManager.js b/uuidManager.js
new file mode 100644
index 0000000..2ca0dca
--- /dev/null
+++ b/uuidManager.js
@@ -0,0 +1,20 @@
+import ComponentBase from "./componentBase";
+
+export default class UUIDManager extends ComponentBase {
+ constructor(models, noble) {
+ super(models);
+ this.noble = noble;
+
+ if (!this.appModel.isTestMode && this.noble) {
+ this.noble.on("stateChange", state => {
+ if (state === "poweredOn") {
+ this.noble.startScanning();
+ }
+ });
+ this.noble.on("discover", peripheral => {
+ this.publish("setNameOfUUID", peripheral.uuid, peripheral.advertisement.localName);
+ });
+ }
+ }
+}
+
diff --git a/virtualSpheroManager.js b/virtualSpheroManager.js
new file mode 100644
index 0000000..bfd729d
--- /dev/null
+++ b/virtualSpheroManager.js
@@ -0,0 +1,25 @@
+import ComponentBase from "./componentBase";
+import VirtualSphero from "sphero-ws-virtual-plugin";
+
+export default class VirtualSpheroManager extends ComponentBase {
+ constructor(models, port) {
+ super(models);
+ this.virtualSphero = new VirtualSphero(port);
+
+ this.subscribe("addedClient", this.addSphero);
+ this.subscribe("removedClient", this.removeSphero);
+ this.subscribe("command", this.command);
+ }
+
+ addSphero(name) {
+ this.virtualSphero.addSphero(name);
+ }
+
+ removeSphero(name) {
+ this.virtualSphero.removeSphero(name);
+ }
+
+ command(controllerName, commandName, args) {
+ this.virtualSphero.command(controllerName, commandName, args);
+ }
+}