implemented new app API
This commit is contained in:
parent
8e811c8f0b
commit
7a3510251f
|
@ -26,50 +26,63 @@
|
|||
|
||||
function StartGame(gameName) {
|
||||
var area = $("<div>").addClass("game-area-mini");
|
||||
function NotifyWinner(winner) {
|
||||
var txt;
|
||||
switch(winner) {
|
||||
case 1:
|
||||
txt = "Player A wins";
|
||||
break;
|
||||
case -1:
|
||||
txt = "Player B wins";
|
||||
break;
|
||||
case 2:
|
||||
txt = "Draw";
|
||||
break;
|
||||
}
|
||||
area.parent().append($("<div>").addClass("game-winner-mini").text(txt));
|
||||
}
|
||||
|
||||
function Listen(message) {
|
||||
var game = this;
|
||||
switch(message.type) {
|
||||
case "human-move":
|
||||
if(message.finished)
|
||||
NotifyWinner(message.winner);
|
||||
else {
|
||||
game.machineTurn({
|
||||
level: game.config.model.levels[2],
|
||||
threaded: true
|
||||
});
|
||||
}
|
||||
break;
|
||||
case "machine-move":
|
||||
if(message.finished)
|
||||
NotifyWinner(message.winner);
|
||||
else
|
||||
game.humanTurn();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$("<div>").addClass("game-area-mini-cont").append(area).appendTo($("body"));
|
||||
|
||||
Jocly.createGame(gameName,area[0]).then((game) => {
|
||||
game.listen(Listen)
|
||||
.then(()=>{
|
||||
game.humanTurn();
|
||||
function NotifyWinner(winner) {
|
||||
var txt = "?";
|
||||
if(winner==Jocly.PLAYER_A)
|
||||
txt = "Player A wins";
|
||||
else if(winner==Jocly.PLAYER_B)
|
||||
txt = "Player B wins";
|
||||
else if(winner==Jocly.DRAW)
|
||||
txt = "Draw";
|
||||
alert(txt);
|
||||
}
|
||||
|
||||
function RunMatch(match, progressBar) {
|
||||
function NextMove() {
|
||||
match.getTurn()
|
||||
.then( (player) => {
|
||||
var promise;
|
||||
if(player==Jocly.PLAYER_A)
|
||||
promise = match.userTurn();
|
||||
else {
|
||||
if(progressBar) {
|
||||
progressBar.style.display = "block";
|
||||
progressBar.style.width = 0;
|
||||
}
|
||||
promise = match.machineSearch({
|
||||
progress: (progress) => {
|
||||
if(progressBar)
|
||||
progressBar.style.width = progress +"%";
|
||||
}
|
||||
})
|
||||
.then( (result) => {
|
||||
return match.playMove(result.move);
|
||||
})
|
||||
.then( () => {
|
||||
if(progressBar)
|
||||
progressBar.style.display = "none";
|
||||
});
|
||||
}
|
||||
promise.then( () => {
|
||||
return match.getFinished()
|
||||
})
|
||||
.then( (result) => {
|
||||
if(result.finished)
|
||||
NotifyWinner(result.winner);
|
||||
else
|
||||
NextMove();
|
||||
});
|
||||
})
|
||||
}
|
||||
NextMove();
|
||||
}
|
||||
|
||||
Jocly.createMatch(gameName).then((match) => {
|
||||
match.attachElement(area[0])
|
||||
.then( () => {
|
||||
RunMatch(match);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -1,68 +1,65 @@
|
|||
|
||||
var progressBar;
|
||||
|
||||
function NotifyWinner(winner) {
|
||||
var txt;
|
||||
switch(winner) {
|
||||
case 1:
|
||||
txt = "Player A wins";
|
||||
break;
|
||||
case -1:
|
||||
txt = "Player B wins";
|
||||
break;
|
||||
case 2:
|
||||
txt = "Draw";
|
||||
break;
|
||||
}
|
||||
var txt = "?";
|
||||
if(winner==Jocly.PLAYER_A)
|
||||
txt = "Player A wins";
|
||||
else if(winner==Jocly.PLAYER_B)
|
||||
txt = "Player B wins";
|
||||
else if(winner==Jocly.DRAW)
|
||||
txt = "Draw";
|
||||
alert(txt);
|
||||
}
|
||||
|
||||
function PrintMove(game,move) {
|
||||
game.getMoveString(move)
|
||||
.then((move)=>{
|
||||
console.info("=>",move);
|
||||
});
|
||||
}
|
||||
|
||||
function Listen(message) {
|
||||
var game = this;
|
||||
switch(message.type) {
|
||||
case "human-move":
|
||||
PrintMove(game,message.move);
|
||||
if(message.finished)
|
||||
NotifyWinner(message.winner);
|
||||
else {
|
||||
game.machineTurn({
|
||||
level: game.config.model.levels[2],
|
||||
threaded: true
|
||||
});
|
||||
progressBar.style.display = "block";
|
||||
}
|
||||
break;
|
||||
case "machine-move":
|
||||
PrintMove(game,message.move);
|
||||
progressBar.style.display = "none";
|
||||
if(message.finished)
|
||||
NotifyWinner(message.winner);
|
||||
else
|
||||
game.humanTurn();
|
||||
break;
|
||||
case "machine-progress":
|
||||
progressBar.style.width = message.progress +"%";
|
||||
break;
|
||||
function RunMatch(match, progressBar) {
|
||||
function NextMove() {
|
||||
match.getTurn()
|
||||
.then( (player) => {
|
||||
var promise;
|
||||
if(player==Jocly.PLAYER_A)
|
||||
promise = match.userTurn();
|
||||
else {
|
||||
if(progressBar) {
|
||||
progressBar.style.display = "block";
|
||||
progressBar.style.width = 0;
|
||||
}
|
||||
promise = match.machineSearch({
|
||||
progress: (progress) => {
|
||||
if(progressBar)
|
||||
progressBar.style.width = progress +"%";
|
||||
}
|
||||
})
|
||||
.then( (result) => {
|
||||
return match.playMove(result.move);
|
||||
})
|
||||
.then( () => {
|
||||
if(progressBar)
|
||||
progressBar.style.display = "none";
|
||||
});
|
||||
}
|
||||
promise.then( () => {
|
||||
return match.getFinished()
|
||||
})
|
||||
.then( (result) => {
|
||||
if(result.finished)
|
||||
NotifyWinner(result.winner);
|
||||
else
|
||||
NextMove();
|
||||
});
|
||||
})
|
||||
}
|
||||
NextMove();
|
||||
}
|
||||
|
||||
window.addEventListener("DOMContentLoaded", function () {
|
||||
progressBar = document.getElementById("progress-bar");
|
||||
var match = /\?game=([^&]+)/.exec(window.location.href);
|
||||
var gameName = match && match[1] || "classic-chess";
|
||||
var m = /\?game=([^&]+)/.exec(window.location.href);
|
||||
var gameName = m && m[1] || "classic-chess";
|
||||
var elementId = "game-area1";
|
||||
var area = document.getElementById(elementId);
|
||||
Jocly.createGame(gameName,area).then((game) => {
|
||||
game.listen(Listen)
|
||||
.then(()=>{
|
||||
game.humanTurn();
|
||||
Jocly.createMatch(gameName).then((match) => {
|
||||
match.attachElement(area)
|
||||
.then( () => {
|
||||
RunMatch(match,progressBar);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
<head>
|
||||
<meta charset="utf-8">
|
||||
|
||||
<title>Jocly Test - Simple</title>
|
||||
<meta name="description" content="Jocly simple test">
|
||||
<title>Jocly Test Multiple</title>
|
||||
<meta name="description" content="Jocly example multiple boards">
|
||||
<meta name="author" content="Jocly">
|
||||
|
||||
<link rel="stylesheet" href="css/styles.css">
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
<head>
|
||||
<meta charset="utf-8">
|
||||
|
||||
<title>Jocly Test - Simple</title>
|
||||
<meta name="description" content="Jocly simple test">
|
||||
<title>Jocly Example Simple</title>
|
||||
<meta name="description" content="Jocly simple example">
|
||||
<meta name="author" content="Jocly">
|
||||
|
||||
<link rel="stylesheet" href="css/styles.css">
|
||||
|
|
|
@ -1,58 +1,50 @@
|
|||
var jocly = require("../../dist/node");
|
||||
var Jocly = require("../../dist/node");
|
||||
|
||||
var moveCount = 0;
|
||||
|
||||
process.on('uncaughtException', function(err) {
|
||||
console.error(err,err.stack)
|
||||
});
|
||||
function RunMatch(match) {
|
||||
function NextMove() {
|
||||
var move;
|
||||
match.machineSearch()
|
||||
.then( (result) => {
|
||||
move = result.move;
|
||||
return match.getMoveString(move);
|
||||
})
|
||||
.then( (moveStr) => {
|
||||
console.info(
|
||||
((!(moveCount%2) && ((moveCount>>1)+1)+". ") || "")
|
||||
+(moveCount%2 && " " || "")+" "+moveStr
|
||||
);
|
||||
moveCount++;
|
||||
})
|
||||
.then( () => {
|
||||
return match.applyMove(move);
|
||||
})
|
||||
.then( (result) => {
|
||||
if(result.finished) {
|
||||
if(result.winner==Jocly.PLAYER_A)
|
||||
console.info("Player A wins");
|
||||
else if(result.winner==Jocly.PLAYER_B)
|
||||
console.info("Player B wins");
|
||||
else if(result.winner==Jocly.DRAW)
|
||||
console.info("Draw");
|
||||
} else
|
||||
NextMove();
|
||||
})
|
||||
.catch( (e) => {
|
||||
console.info("Error",e);
|
||||
})
|
||||
|
||||
function Turn(game) {
|
||||
game.machineTurn({
|
||||
level: game.config.model.levels[2],
|
||||
})
|
||||
.then(()=>{
|
||||
},(e)=>{
|
||||
console.error("cannot machine turn",e);
|
||||
})
|
||||
.catch((e)=>{
|
||||
console.error("error in machine turn",e);
|
||||
})
|
||||
;
|
||||
}
|
||||
|
||||
function Listen(message) {
|
||||
//console.info("Top receives",message);
|
||||
switch(message.type) {
|
||||
case "machine-move":
|
||||
var move = this.getMoveString(message.move)
|
||||
.then((move)=>{
|
||||
console.info(
|
||||
((!(moveCount%2) && ((moveCount>>1)+1)+". ") || "")
|
||||
+(moveCount%2 && " " || "")+" "+move
|
||||
);
|
||||
moveCount++;
|
||||
if(message.finished)
|
||||
switch(message.winner) {
|
||||
case 1: console.info("Player A wins"); break;
|
||||
case -1: console.info("Player B wins"); break;
|
||||
case 2: console.info("Draw"); break;
|
||||
}
|
||||
else
|
||||
Turn(this);
|
||||
});
|
||||
break;
|
||||
}
|
||||
NextMove();
|
||||
}
|
||||
|
||||
jocly.createGame("classic-chess")
|
||||
.then((game)=>{
|
||||
game.listen(Listen);
|
||||
Turn(game);
|
||||
},(e)=>{
|
||||
console.info("error",e);
|
||||
|
||||
Jocly.createMatch("classic-chess")
|
||||
.then((match)=>{
|
||||
RunMatch(match);
|
||||
})
|
||||
.catch((e)=>{
|
||||
console.info("error",e);
|
||||
})
|
||||
;
|
||||
console.info("Error creating match",e);
|
||||
});
|
||||
|
||||
|
|
16
gulpfile.js
16
gulpfile.js
|
@ -151,7 +151,8 @@ function ProcessJS(stream,concatName,skipBabel) {
|
|||
stream = stream.pipe(sourcemaps.init());
|
||||
if(!skipBabel)
|
||||
stream = stream.pipe(babel({
|
||||
presets: ['es2015']
|
||||
presets: ['es2015'],
|
||||
compact: !!argv.prod
|
||||
}));
|
||||
if(argv.prod)
|
||||
stream = stream.pipe(uglify())
|
||||
|
@ -184,17 +185,11 @@ gulp.task("build-node-core",function() {
|
|||
"src/node/package.json",
|
||||
]);
|
||||
|
||||
var joclyExtraScriptStream =
|
||||
ProcessJS(gulp.src([
|
||||
"src/node/jocly.proxy.js"
|
||||
]));
|
||||
|
||||
var allGamesStream = source('jocly-allgames.js');
|
||||
allGamesStream.end('exports.games = '+JSON.stringify(allGames));
|
||||
allGamesStream = ProcessJS(allGamesStream.pipe(buffer()));
|
||||
|
||||
return merge(joclyCoreStream,allGamesStream,joclyBaseStream,
|
||||
joclyExtraStream,joclyExtraScriptStream)
|
||||
return merge(joclyCoreStream,allGamesStream,joclyBaseStream,joclyExtraStream)
|
||||
.pipe(gulp.dest("dist/node"));
|
||||
|
||||
})
|
||||
|
@ -248,7 +243,6 @@ gulp.task("build-browser-core",function() {
|
|||
|
||||
var joclyExtraScriptsStream = ProcessJS(gulp.src([
|
||||
"src/browser/jocly.aiworker.js",
|
||||
"src/browser/jocly.proxy.js",
|
||||
"src/browser/jocly.embed.js"
|
||||
]));
|
||||
|
||||
|
@ -322,8 +316,8 @@ gulp.task("clean",function() {
|
|||
|
||||
gulp.task("watch",function() {
|
||||
gulp.watch("src/games/**/*",["build-node-games","build-browser-games"]);
|
||||
gulp.watch("src/{browser,core,lib}",["build-browser-core","build-browser-xdview"]);
|
||||
gulp.watch("src/{node,core}",["build-node-core"]);
|
||||
gulp.watch("src/{browser,core,lib}/**/*",["build-browser-core","build-browser-xdview"]);
|
||||
gulp.watch("src/{node,core}/**/*",["build-node-core"]);
|
||||
});
|
||||
|
||||
gulp.task("help", function() {
|
||||
|
|
|
@ -44,7 +44,7 @@ onmessage = function(e) {
|
|||
importTime = Date.now() - t0;
|
||||
break;
|
||||
case "Play":
|
||||
Jocly.createInternalGame(message.gameName,options).then((game)=>{
|
||||
Jocly._createInternalGame(message.gameName,options).then((game)=>{
|
||||
game.mBoard.mMoves = [];
|
||||
game.Load({
|
||||
playedMoves: message.playedMoves
|
||||
|
|
|
@ -25,39 +25,57 @@
|
|||
* then also delete it in the license file.
|
||||
*/
|
||||
|
||||
var gameId;
|
||||
var gGame;
|
||||
var matchId;
|
||||
var gMatch;
|
||||
|
||||
window.addEventListener("message", receiveMessage, false);
|
||||
window.addEventListener("message", ReceiveMessage, false);
|
||||
|
||||
function receiveMessage(event)
|
||||
function ReceiveMessage(event)
|
||||
{
|
||||
console.info("embed receives message",event.data);
|
||||
//console.info("embed receives message",event.data);
|
||||
var origin = event.origin || event.originalEvent.origin;
|
||||
var url = new URL(window.location);
|
||||
if(origin!=url.origin)
|
||||
return;
|
||||
function Reply(message) {
|
||||
message = message || {};
|
||||
window.parent.postMessage({
|
||||
joclyEmbeddedGameId: gameId,
|
||||
joclyEmbeddedGameId: matchId,
|
||||
replyId: event.data.replyId,
|
||||
message: message
|
||||
},"*");
|
||||
}
|
||||
function ReplyError(error) {
|
||||
if(typeof error=="object")
|
||||
error = {
|
||||
message: error.message,
|
||||
fileName: error.fileName,
|
||||
lineNumber: error.lineNumber,
|
||||
stack: error.stack
|
||||
}
|
||||
Reply({
|
||||
type: "error",
|
||||
error: error
|
||||
});
|
||||
}
|
||||
var message = event.data.message;
|
||||
switch(message.type) {
|
||||
case "init":
|
||||
gameId = message.id;
|
||||
matchId = message.id;
|
||||
var area = document.getElementById("area");
|
||||
Jocly.createInternalGame(message.gameName).then((game) => {
|
||||
gGame = game;
|
||||
game.AddListener(Listen);
|
||||
Jocly.createMatch(message.gameName).then((match) => {
|
||||
gMatch = match;
|
||||
match.game.Load({
|
||||
playedMoves: message.playedMoves
|
||||
});
|
||||
function Start() {
|
||||
game.AttachElement(area).then(()=>{
|
||||
game.GameInitView();
|
||||
game.DisplayBoard();
|
||||
Reply();
|
||||
});
|
||||
match.game.AttachElement(area).then(()=>{
|
||||
match.area = area;
|
||||
match.game.GameInitView();
|
||||
match.game.DisplayBoard();
|
||||
Reply();
|
||||
})
|
||||
.catch(ReplyError);
|
||||
}
|
||||
if (document.readyState === "complete"
|
||||
|| document.readyState === "loaded"
|
||||
|
@ -67,65 +85,30 @@ function receiveMessage(event)
|
|||
window.addEventListener("DOMContentLoaded", () => {
|
||||
Start();
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch(ReplyError);
|
||||
break;
|
||||
case "humanTurn":
|
||||
gGame.HumanTurn();
|
||||
case "method":
|
||||
//console.info("embed execute method",message.methodName,message.args);
|
||||
try {
|
||||
gMatch[message.methodName].apply(gMatch,message.args)
|
||||
.then( function() {
|
||||
Reply({
|
||||
args: Array.from(arguments)
|
||||
})
|
||||
},ReplyError)
|
||||
} catch(e) {
|
||||
ReplyError(e);
|
||||
}
|
||||
break;
|
||||
case "machineTurn":
|
||||
gGame.StartMachine(message.options);
|
||||
break;
|
||||
case "getFinished":
|
||||
var finished = gGame.GetFinished();
|
||||
Reply({
|
||||
finished: !!finished,
|
||||
winner: finished
|
||||
});
|
||||
break;
|
||||
case "getMoveString":
|
||||
var move = new (gGame.GetMoveClass())(message.move);
|
||||
Reply(move.ToString());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function Listen(message) {
|
||||
switch(message.type) {
|
||||
case "human-move":
|
||||
gGame.ApplyMove(message.move);
|
||||
var finished = gGame.GetFinished();
|
||||
if(!finished)
|
||||
gGame.InvertWho();
|
||||
gGame.DisplayBoard();
|
||||
Object.assign(message,{
|
||||
finished: !!finished,
|
||||
winner: finished
|
||||
});
|
||||
break;
|
||||
case "machine-move":
|
||||
var move = message.result.move;
|
||||
delete message.result.move;
|
||||
gGame.PlayMove(move)
|
||||
.then(()=>{
|
||||
var finished = gGame.GetFinished();
|
||||
if(!finished)
|
||||
gGame.InvertWho();
|
||||
gGame.DisplayBoard();
|
||||
window.parent.postMessage({
|
||||
joclyEmbeddedGameId: gameId,
|
||||
message: {
|
||||
type: "machine-move",
|
||||
moveData: message.result,
|
||||
move: move,
|
||||
finished: !!finished,
|
||||
winner: finished
|
||||
}
|
||||
},"*");
|
||||
case "destroy":
|
||||
var playedMoves = gMatch.game.mPlayedMoves;
|
||||
gMatch.destroy()
|
||||
.then( ()=> {
|
||||
Reply({
|
||||
playedMoves: playedMoves
|
||||
});
|
||||
});
|
||||
return;
|
||||
break;
|
||||
}
|
||||
window.parent.postMessage({
|
||||
joclyEmbeddedGameId: gameId,
|
||||
message: message
|
||||
},"*");
|
||||
}
|
||||
|
|
|
@ -58,7 +58,10 @@ function ExportFunction(fName) {
|
|||
}
|
||||
}
|
||||
|
||||
["listGames","createGame","createInternalGame"].forEach((fName)=>{
|
||||
["listGames","createMatch"].forEach((fName)=>{
|
||||
ExportFunction(fName);
|
||||
});
|
||||
|
||||
exports.PLAYER_A = 1;
|
||||
exports.PLAYER_B = -1;
|
||||
exports.DRAW = 2;
|
||||
|
|
|
@ -1,232 +0,0 @@
|
|||
/* Copyright 2017 Jocly
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* As a special exception, the copyright holders give permission to link the
|
||||
* code of portions of this program with the OpenSSL library under certain
|
||||
* conditions as described in each individual source file and distribute
|
||||
* linked combinations including the program with the OpenSSL library. You
|
||||
* must comply with the GNU Affero General Public License in all respects
|
||||
* for all of the code used other than as permitted herein. If you modify
|
||||
* file(s) with this exception, you may extend this exception to your
|
||||
* version of the file(s), but you are not obligated to do so. If you do not
|
||||
* wish to do so, delete this exception statement from your version. If you
|
||||
* delete this exception statement from all source files in the program,
|
||||
* then also delete it in the license file.
|
||||
*/
|
||||
|
||||
var gameConfigs = {}
|
||||
var gameIdRef = 0;
|
||||
var messageListenerInstalled = false;
|
||||
var games = {};
|
||||
var msgIdRef = 0;
|
||||
var messageReplies = {};
|
||||
|
||||
function MessageListener(event) {
|
||||
var game = games[event.data.joclyEmbeddedGameId];
|
||||
console.info("proxy receives message",event.data);
|
||||
if(game) {
|
||||
var replyId = event.data.replyId;
|
||||
if(replyId) {
|
||||
var reply = messageReplies[replyId];
|
||||
if(reply) {
|
||||
delete messageReplies[replyId];
|
||||
reply(event.data.message);
|
||||
}
|
||||
} else
|
||||
game.listeners.forEach((listener)=>{
|
||||
listener.call(game,event.data.message);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function GameProxyClass(gameName) {
|
||||
this.gameName = gameName;
|
||||
this.listeners = [];
|
||||
this.id = ++gameIdRef;
|
||||
}
|
||||
|
||||
GameProxyClass.prototype.attachElement = function(element,options) {
|
||||
var self = this;
|
||||
var promise = new Promise((resolve,reject)=>{
|
||||
if(!messageListenerInstalled) {
|
||||
messageListenerInstalled = true;
|
||||
window.addEventListener("message",MessageListener);
|
||||
}
|
||||
|
||||
this.element = element;
|
||||
var iframeUrl = "jocly.embed.html";
|
||||
|
||||
var iframe = document.createElement("iframe");
|
||||
var attrs = {
|
||||
name: "jocly-embedded-"+self.id,
|
||||
frameborder: 0,
|
||||
src: SystemJS.getConfig().baseURL+iframeUrl,
|
||||
width: "100%",
|
||||
height: "100%"
|
||||
}
|
||||
Object.keys(attrs).forEach((attr)=>{
|
||||
iframe.setAttribute(attr,attrs[attr]);
|
||||
});
|
||||
Object.assign(iframe.style,{
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
whiteSpace: "normal",
|
||||
});
|
||||
|
||||
var wrapper = document.createElement("div");
|
||||
Object.assign(wrapper.style,{
|
||||
position: "relative",
|
||||
whiteSpace: "nowrap",
|
||||
width: "100%",
|
||||
height: "100%"
|
||||
});
|
||||
wrapper.appendChild(iframe);
|
||||
element.appendChild(wrapper);
|
||||
|
||||
this.iframe = iframe;
|
||||
|
||||
iframe.onload = ()=>{
|
||||
PostMessage(self,{
|
||||
type: "init",
|
||||
id: this.id,
|
||||
gameName: this.gameName
|
||||
},(reply)=>{
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
|
||||
function PostMessage(game,message,reply) {
|
||||
var wrapper = {
|
||||
message: message
|
||||
}
|
||||
if(reply) {
|
||||
var replyId = ++msgIdRef;
|
||||
wrapper.replyId = replyId;
|
||||
messageReplies[replyId] = reply;
|
||||
}
|
||||
game.iframe.contentWindow.postMessage(wrapper,"*");
|
||||
}
|
||||
|
||||
GameProxyClass.prototype.listen = function(listener) {
|
||||
var self = this;
|
||||
var promise = new Promise((resolve,reject)=>{
|
||||
self.listeners.push(listener);
|
||||
resolve();
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
|
||||
GameProxyClass.prototype.unlisten = function(listener) {
|
||||
var self = this;
|
||||
var promise = new Promise((resolve,reject)=>{
|
||||
for(var i = self.listeners.length-1;i>=0;i--)
|
||||
if(self.listeners[i]==listener)
|
||||
self.listeners.splice(i,1);
|
||||
resolve();
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
|
||||
GameProxyClass.prototype.humanTurn = function() {
|
||||
var self = this;
|
||||
var promise = new Promise((resolve,reject)=>{
|
||||
PostMessage(self,{
|
||||
type: "humanTurn"
|
||||
});
|
||||
resolve();
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
|
||||
GameProxyClass.prototype.machineTurn = function(options) {
|
||||
var self = this;
|
||||
var promise = new Promise((resolve,reject)=>{
|
||||
PostMessage(self,{
|
||||
type: "machineTurn",
|
||||
options: options
|
||||
});
|
||||
resolve();
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
|
||||
GameProxyClass.prototype.getFinished = function() {
|
||||
var self = this;
|
||||
var promise = new Promise((resolve,reject)=>{
|
||||
PostMessage(self,{
|
||||
type: "getFinished"
|
||||
},(result) => {
|
||||
resolve(result);
|
||||
});
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
|
||||
GameProxyClass.prototype.getMoveString = function(move) {
|
||||
var self = this;
|
||||
var promise = new Promise((resolve,reject)=>{
|
||||
PostMessage(self,{
|
||||
type: "getMoveString",
|
||||
move: move
|
||||
},(move) => {
|
||||
resolve(move);
|
||||
});
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
|
||||
function CreateGame(gameName) {
|
||||
var promise = new Promise((resolve,reject)=>{
|
||||
|
||||
function CreateGame(config) {
|
||||
var game = new GameProxyClass(gameName);
|
||||
games[game.id] = game;
|
||||
Object.assign(game,config);
|
||||
resolve(game);
|
||||
}
|
||||
|
||||
var config = gameConfigs[gameName];
|
||||
if(config)
|
||||
CreateGame(config);
|
||||
else {
|
||||
Jocly.listGames()
|
||||
.then((games)=>{
|
||||
var gameDescr = games[gameName];
|
||||
if (!gameDescr)
|
||||
return reject(new Error("Game " + gameName + " not found"));
|
||||
SystemJS.import("games/" + gameDescr.module + "/" + gameName + "-config.js")
|
||||
.then((config)=>{
|
||||
gameConfigs[gameName] = config;
|
||||
CreateGame(config);
|
||||
});
|
||||
|
||||
},reject);
|
||||
}
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
|
||||
if (typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope)
|
||||
this.GameProxy = {
|
||||
createGame: CreateGame
|
||||
}
|
||||
else
|
||||
exports.createGame = CreateGame;
|
|
@ -27,193 +27,811 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
if (typeof WorkerGlobalScope == 'undefined' || !(self instanceof WorkerGlobalScope)) {
|
||||
var GameProxy = require("./jocly.proxy.js");
|
||||
}
|
||||
/*
|
||||
* Keys to understand this code
|
||||
*
|
||||
* This script can be executed under various contexts:
|
||||
* A. in the browser within the application context
|
||||
* B. in the browser within an iframe controlled by the application
|
||||
* C. in the browser as a worker for running the AI
|
||||
* D. in node.js
|
||||
*
|
||||
* In case A, the Match object can either be:
|
||||
* - attached to an element (A1)
|
||||
* - not attached (A2)
|
||||
*
|
||||
* A Match object is only glue. The actual object doing the job is generated
|
||||
* from function CreateInternalGame(). This object is constructed by defining
|
||||
* a base (from jocly.game.js) overloaded by the model (<game-name>_model.js),
|
||||
* and possibly by the view (<game-name>_view.js) if there is a need for a UI
|
||||
* (case B only).
|
||||
*
|
||||
* In cases A2, B, C and D, the Match object contains 'game', the actual smart object
|
||||
* In case A1, it contains property 'iframe' which is an actual iframe running
|
||||
* the code as case B. Properties 'game' and 'iframe' should never be set at the
|
||||
* same time.
|
||||
*
|
||||
* Match.attachElement() moves from case A2 to A1, creating an iframe running in
|
||||
* in case B and passing the responsability of the actual game object to it.
|
||||
* Match.detachElement() (A1 to A2) gets the game back from the iframe and destroys
|
||||
* the iframe.
|
||||
*
|
||||
* In case B (and only in this case), the Match object has a property 'area'
|
||||
* representing the DOM element containing the UI.
|
||||
*
|
||||
* By convention, Match object method names starts with a lowercase character,
|
||||
* the game object with uppercase. Both APIs may have some similarities but
|
||||
* are actually different.
|
||||
*
|
||||
* Most Match API methods do the following: check whether we are in case A1,
|
||||
* if so, the command/reply is passed/answered to/from the iframe running case B.
|
||||
* In all other cases, the action is performed locally using the API of the game
|
||||
* object.
|
||||
*
|
||||
*/
|
||||
|
||||
exports.listGames = function () {
|
||||
(function() {
|
||||
|
||||
var promise = new Promise((resolve, reject) => {
|
||||
if(typeof SystemJS!="undefined")
|
||||
SystemJS.import("jocly-allgames.js").then((m) => {
|
||||
if(typeof SystemJS!="undefined") {
|
||||
var baseURL = SystemJS.getConfig().baseURL;
|
||||
for(var gameName in m.games) {
|
||||
var game = m.games[gameName];
|
||||
game.thumbnail = baseURL + "games/" + game.module + "/" + game.thumbnail;
|
||||
}
|
||||
}
|
||||
resolve(m.games);
|
||||
}, (e) => {
|
||||
console.warn("Could not load Jocly games", e);
|
||||
reject(e);
|
||||
var gameClassCache = {};
|
||||
|
||||
function CreateGame(gameName, gameDescr,base, model, config) {
|
||||
var Game = function () {
|
||||
this.g = {}; // some games assume this member exists
|
||||
};
|
||||
Object.assign(Game.prototype, base.Game.prototype, model.model.Game);
|
||||
|
||||
var Board = function () { };
|
||||
Object.assign(Board.prototype, base.Board.prototype, model.model.Board);
|
||||
Game.prototype.mBoardClass = Board;
|
||||
|
||||
var Move = function (args) { this.Init(args); };
|
||||
Object.assign(Move.prototype, base.Move.prototype, model.model.Move);
|
||||
Game.prototype.mMoveClass = Move;
|
||||
|
||||
|
||||
Game.prototype.config = config.config;
|
||||
|
||||
if(typeof SystemJS!="undefined") {
|
||||
Game.prototype.config.baseURL = SystemJS.getConfig().baseURL;
|
||||
Game.prototype.config.view.fullPath = SystemJS.getConfig().baseURL + "games/" + gameDescr.module;
|
||||
}
|
||||
Game.prototype.module = gameDescr.module;
|
||||
Game.prototype.name = gameName;
|
||||
|
||||
var initArgs = {
|
||||
game: config.config.model.gameOptions,
|
||||
view: config.config.view
|
||||
};
|
||||
|
||||
gameClassCache[gameName] = {
|
||||
gameClass: Game,
|
||||
initArgs: initArgs
|
||||
}
|
||||
|
||||
var game = new Game();
|
||||
|
||||
game.Init(initArgs);
|
||||
|
||||
return game;
|
||||
}
|
||||
|
||||
function CreateInternalGame(gameName, options) {
|
||||
var gameClassData = gameClassCache[gameName];
|
||||
if(gameClassData) {
|
||||
var promise = new Promise((resolve,reject)=>{
|
||||
var game = new gameClassData.gameClass();
|
||||
game.Init(gameClassData.initArgs);
|
||||
resolve(game);
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
if (typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope)
|
||||
return WorkerCreateGame(gameName, options);
|
||||
else if(typeof process!=="undefined" && process.title === "node")
|
||||
return NodeCreateGame(gameName,options);
|
||||
else
|
||||
resolve(require("./jocly-allgames.js").games);
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
|
||||
var gameClassCache = {};
|
||||
|
||||
function CreateGame(gameName, gameDescr,base, model, config) {
|
||||
var Game = function () {
|
||||
this.g = {}; // some games assume this member exists
|
||||
};
|
||||
Object.assign(Game.prototype, base.Game.prototype, model.model.Game);
|
||||
|
||||
var Board = function () { };
|
||||
Object.assign(Board.prototype, base.Board.prototype, model.model.Board);
|
||||
Game.prototype.mBoardClass = Board;
|
||||
|
||||
var Move = function (args) { this.Init(args); };
|
||||
Object.assign(Move.prototype, base.Move.prototype, model.model.Move);
|
||||
Game.prototype.mMoveClass = Move;
|
||||
|
||||
|
||||
Game.prototype.config = config.config;
|
||||
|
||||
if(typeof SystemJS!="undefined") {
|
||||
Game.prototype.config.baseURL = SystemJS.getConfig().baseURL;
|
||||
Game.prototype.config.view.fullPath = SystemJS.getConfig().baseURL + "games/" + gameDescr.module;
|
||||
}
|
||||
Game.prototype.module = gameDescr.module;
|
||||
Game.prototype.name = gameName;
|
||||
|
||||
var initArgs = {
|
||||
game: config.config.model.gameOptions,
|
||||
view: config.config.view
|
||||
};
|
||||
|
||||
gameClassCache[gameName] = {
|
||||
gameClass: Game,
|
||||
initArgs: initArgs
|
||||
return BrowserCreateGame(gameName, options);
|
||||
}
|
||||
|
||||
var game = new Game();
|
||||
function NodeCreateGame(gameName, options) {
|
||||
var t0 = Date.now();
|
||||
options = options || {};
|
||||
var promise = new Promise((resolve, reject) => {
|
||||
|
||||
game.Init(initArgs);
|
||||
|
||||
return game;
|
||||
}
|
||||
|
||||
exports.createInternalGame = function (gameName, options) {
|
||||
var gameClassData = gameClassCache[gameName];
|
||||
if(gameClassData) {
|
||||
var promise = new Promise((resolve,reject)=>{
|
||||
var game = new gameClassData.gameClass();
|
||||
game.Init(gameClassData.initArgs);
|
||||
resolve(game);
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
if (typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope)
|
||||
return WorkerCreateGame(gameName, options);
|
||||
else if(typeof process!=="undefined" && process.title === "node")
|
||||
return NodeCreateGame(gameName,options);
|
||||
else
|
||||
return BrowserCreateGame(gameName, options);
|
||||
}
|
||||
|
||||
function NodeCreateGame(gameName, options) {
|
||||
var t0 = Date.now();
|
||||
options = options || {};
|
||||
var promise = new Promise((resolve, reject) => {
|
||||
|
||||
var games = require("./jocly-allgames.js").games;
|
||||
|
||||
var gameDescr = games[gameName];
|
||||
if (!gameDescr)
|
||||
return reject(new Error("Game " + gameName + " not found"));
|
||||
|
||||
var base = require("./jocly.game.js");
|
||||
var model = require("./games/" + gameDescr.module + "/" + gameName + "-model.js");
|
||||
var config = require("./games/" + gameDescr.module + "/" + gameName + "-config.js");
|
||||
|
||||
var game = CreateGame(gameName, gameDescr, base, model, config);
|
||||
|
||||
resolve(game);
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
|
||||
function WorkerCreateGame(gameName, options) {
|
||||
var t0 = Date.now();
|
||||
options = options || {};
|
||||
var promise = new Promise((resolve, reject) => {
|
||||
|
||||
importScripts("jocly-allgames.js");
|
||||
var games = exports.games;
|
||||
|
||||
var gameDescr = games[gameName];
|
||||
if (!gameDescr)
|
||||
return reject(new Error("Game " + gameName + " not found"));
|
||||
|
||||
var originalExports = exports;
|
||||
var base = exports = {};
|
||||
importScripts("jocly.game.js");
|
||||
var model = exports = {};
|
||||
importScripts("games/" + gameDescr.module + "/" + gameName + "-model.js");
|
||||
var config = exports = {};
|
||||
importScripts("games/" + gameDescr.module + "/" + gameName + "-config.js");
|
||||
exports = originalExports;
|
||||
|
||||
var game = CreateGame(gameName, gameDescr, base, model, config);
|
||||
|
||||
resolve(game);
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
|
||||
function BrowserCreateGame(gameName, options) {
|
||||
options = options || {};
|
||||
var promise = new Promise((resolve, reject) => {
|
||||
|
||||
exports.listGames().then((games) => {
|
||||
var games = require("./jocly-allgames.js").games;
|
||||
|
||||
var gameDescr = games[gameName];
|
||||
if (!gameDescr)
|
||||
return reject(new Error("Game " + gameName + " not found"));
|
||||
|
||||
var gamePromises = [
|
||||
SystemJS.import("jocly.game.js"),
|
||||
SystemJS.import("games/" + gameDescr.module + "/" + gameName + "-model.js"),
|
||||
SystemJS.import("games/" + gameDescr.module + "/" + gameName + "-config.js")
|
||||
];
|
||||
var base = require("./jocly.game.js");
|
||||
var model = require("./games/" + gameDescr.module + "/" + gameName + "-model.js");
|
||||
var config = require("./games/" + gameDescr.module + "/" + gameName + "-config.js");
|
||||
|
||||
Promise.all(gamePromises).then(([base, model, config]) => {
|
||||
var game = CreateGame(gameName, gameDescr, base, model, config);
|
||||
|
||||
var game = CreateGame(gameName, gameDescr, base, model, config);
|
||||
resolve(game);
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
|
||||
resolve(game);
|
||||
}, (e) => {
|
||||
reject(e);
|
||||
});
|
||||
}, reject);
|
||||
function WorkerCreateGame(gameName, options) {
|
||||
var t0 = Date.now();
|
||||
options = options || {};
|
||||
var promise = new Promise((resolve, reject) => {
|
||||
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
importScripts("jocly-allgames.js");
|
||||
var games = exports.games;
|
||||
|
||||
exports.createGame = function (gameName) {
|
||||
var options, element;
|
||||
if(typeof Element !== "undefined" && arguments[1] instanceof Element) {
|
||||
element = arguments[1];
|
||||
options = arguments[2];
|
||||
} else
|
||||
options = arguments[1];
|
||||
var gameDescr = games[gameName];
|
||||
if (!gameDescr)
|
||||
return reject(new Error("Game " + gameName + " not found"));
|
||||
|
||||
var originalExports = exports;
|
||||
var base = exports = {};
|
||||
importScripts("jocly.game.js");
|
||||
var model = exports = {};
|
||||
importScripts("games/" + gameDescr.module + "/" + gameName + "-model.js");
|
||||
var config = exports = {};
|
||||
importScripts("games/" + gameDescr.module + "/" + gameName + "-config.js");
|
||||
exports = originalExports;
|
||||
|
||||
var game = CreateGame(gameName, gameDescr, base, model, config);
|
||||
|
||||
resolve(game);
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
|
||||
function BrowserCreateGame(gameName, options) {
|
||||
options = options || {};
|
||||
var promise = new Promise((resolve, reject) => {
|
||||
|
||||
exports.listGames().then((games) => {
|
||||
|
||||
var gameDescr = games[gameName];
|
||||
if (!gameDescr)
|
||||
return reject(new Error("Game " + gameName + " not found"));
|
||||
|
||||
var gamePromises = [
|
||||
SystemJS.import("jocly.game.js"),
|
||||
SystemJS.import("games/" + gameDescr.module + "/" + gameName + "-model.js"),
|
||||
SystemJS.import("games/" + gameDescr.module + "/" + gameName + "-config.js")
|
||||
];
|
||||
|
||||
Promise.all(gamePromises).then(([base, model, config]) => {
|
||||
|
||||
var game = CreateGame(gameName, gameDescr, base, model, config);
|
||||
|
||||
var promise = new Promise((resolve,reject)=>{
|
||||
GameProxy.createGame(gameName,options)
|
||||
.then((game)=>{
|
||||
if(element) {
|
||||
game.attachElement(element,options)
|
||||
.then(()=>{
|
||||
resolve(game);
|
||||
},reject);
|
||||
} else
|
||||
resolve(game);
|
||||
},reject);
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
}, (e) => {
|
||||
reject(e);
|
||||
});
|
||||
}, reject);
|
||||
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
|
||||
// Check in what context this code is running
|
||||
var jsContext = "browser";
|
||||
if(typeof process!=="undefined" && process.title === "node")
|
||||
jsContext = "node";
|
||||
else if(typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope)
|
||||
jsContext = "worker";
|
||||
|
||||
|
||||
exports.listGames = function () {
|
||||
|
||||
var promise = new Promise((resolve, reject) => {
|
||||
if(typeof SystemJS!="undefined")
|
||||
SystemJS.import("jocly-allgames.js").then((m) => {
|
||||
if(typeof SystemJS!="undefined") {
|
||||
var baseURL = SystemJS.getConfig().baseURL;
|
||||
for(var gameName in m.games) {
|
||||
var game = m.games[gameName];
|
||||
game.thumbnail = baseURL + "games/" + game.module + "/" + game.thumbnail;
|
||||
}
|
||||
}
|
||||
resolve(m.games);
|
||||
}, (e) => {
|
||||
console.warn("Could not load Jocly games", e);
|
||||
reject(e);
|
||||
});
|
||||
else
|
||||
resolve(require("./jocly-allgames.js").games);
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
|
||||
function CreateMatch(gameName) {
|
||||
var promise = new Promise( function(resolve, reject) {
|
||||
CreateInternalGame(gameName)
|
||||
.then( function(game) {
|
||||
var proxy = new GameProxy(gameName,game);
|
||||
resolve(proxy);
|
||||
}, function(error) {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
|
||||
|
||||
var messageListenerInstalled = false;
|
||||
var matches = {};
|
||||
var msgIdRef = 0;
|
||||
var messageReplies = {};
|
||||
var matchIdRef = 0;
|
||||
|
||||
function GameProxy(gameName,game) {
|
||||
this.gameName = gameName;
|
||||
this.game = game;
|
||||
this.iframe = null;
|
||||
this.id = ++matchIdRef;
|
||||
matches[this.id] = this;
|
||||
}
|
||||
|
||||
function MessageListener(event) {
|
||||
var message = event.data;
|
||||
var match = matches[message.joclyEmbeddedGameId];
|
||||
//console.info("proxy receives message",event.data,!!match);
|
||||
if(match) {
|
||||
var replyId = message.replyId;
|
||||
if(replyId) {
|
||||
var reply = messageReplies[replyId];
|
||||
if(reply) {
|
||||
delete messageReplies[replyId];
|
||||
if(message.message.type=="error") {
|
||||
var error = new Error(message.message.error.message,message.message.error.fileName,message.message.error.lineNumber);
|
||||
reply(error);
|
||||
} else
|
||||
reply(null,message.message);
|
||||
}
|
||||
} else if(message.message.type=="machine-progress") {
|
||||
if(match.machineProgressListener)
|
||||
match.machineProgressListener(message.message.progress);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function PostMessage(match,message,reply) {
|
||||
var wrapper = {
|
||||
message: message
|
||||
}
|
||||
if(reply) {
|
||||
var replyId = ++msgIdRef;
|
||||
wrapper.replyId = replyId;
|
||||
messageReplies[replyId] = reply;
|
||||
}
|
||||
try {
|
||||
match.iframe.contentWindow.postMessage(wrapper,"*");
|
||||
} catch(e) {
|
||||
console.error("Message",message,"could not be posted:",e);
|
||||
}
|
||||
}
|
||||
|
||||
GameProxy.prototype.attachElement = function(element, options) {
|
||||
if(jsContext=="node")
|
||||
return Promise.reject(new Error("attachElement(): not supported in node.js"));
|
||||
if(!this.game || this.iframe)
|
||||
return Promise.reject(new Error("attachElement(): match is already attached"));
|
||||
options = options || {};
|
||||
var self = this;
|
||||
var promise = new Promise( function(resolve, reject) {
|
||||
if(!messageListenerInstalled) {
|
||||
messageListenerInstalled = true;
|
||||
window.addEventListener("message",MessageListener);
|
||||
}
|
||||
|
||||
self.element = element;
|
||||
var iframeUrl = "jocly.embed.html";
|
||||
|
||||
var iframe = document.createElement("iframe");
|
||||
var attrs = {
|
||||
name: "jocly-embedded-"+self.id,
|
||||
frameborder: 0,
|
||||
src: SystemJS.getConfig().baseURL+iframeUrl,
|
||||
width: "100%",
|
||||
height: "100%"
|
||||
}
|
||||
Object.keys(attrs).forEach((attr)=>{
|
||||
iframe.setAttribute(attr,attrs[attr]);
|
||||
});
|
||||
Object.assign(iframe.style,{
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
whiteSpace: "normal",
|
||||
});
|
||||
|
||||
var wrapper = document.createElement("div");
|
||||
Object.assign(wrapper.style,{
|
||||
position: "relative",
|
||||
whiteSpace: "nowrap",
|
||||
width: "100%",
|
||||
height: "100%"
|
||||
});
|
||||
wrapper.appendChild(iframe);
|
||||
element.appendChild(wrapper);
|
||||
|
||||
self.iframe = iframe;
|
||||
|
||||
iframe.onload = ()=>{
|
||||
PostMessage(self,{
|
||||
type: "init",
|
||||
id: self.id,
|
||||
gameName: self.gameName,
|
||||
playedMoves: self.game.mPlayedMoves
|
||||
},(error,reply)=>{
|
||||
if(error)
|
||||
reject(error);
|
||||
else {
|
||||
self.game = null;
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
};
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
|
||||
GameProxy.prototype.detachElement = function() {
|
||||
if(jsContext=="node")
|
||||
return Promise.reject(new Error("detachElement(): not supported in node.js"));
|
||||
if(this.game || !this.iframe)
|
||||
return Promise.reject(new Error("detachElement(): match is not attached"));
|
||||
var self = this;
|
||||
var promise = new Promise( function(resolve, reject) {
|
||||
Promise.all([
|
||||
CreateInternalGame(self.gameName),
|
||||
ProxiedMethod(self,"getPlayedMoves")
|
||||
])
|
||||
.then( ([game,moves]) => {
|
||||
self.game = game;
|
||||
game.Load({
|
||||
playedMoves: moves
|
||||
});
|
||||
return ProxiedMethod(self,"destroy");
|
||||
})
|
||||
.then( () => {
|
||||
while(self.element.firstChild)
|
||||
self.element.removeChild(self.element.firstChild);
|
||||
delete self.element;
|
||||
delete self.iframe;
|
||||
resolve();
|
||||
})
|
||||
.catch( reject );
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
|
||||
function ProxiedMethod(match,methodName,args) {
|
||||
args = Array.from(args || []);
|
||||
var promise = new Promise( function(resolve, reject) {
|
||||
PostMessage(match,{
|
||||
type: "method",
|
||||
id: match.id,
|
||||
methodName: methodName,
|
||||
args: args,
|
||||
},(error,reply)=>{
|
||||
if(error)
|
||||
reject(error);
|
||||
else
|
||||
resolve.apply(null,reply && reply.args || []);
|
||||
});
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
|
||||
GameProxy.prototype.getTurn = function() {
|
||||
if(this.game) {
|
||||
var self = this;
|
||||
var promise = new Promise( function(resolve, reject) {
|
||||
var who = self.game.GetWho();
|
||||
resolve(who);
|
||||
});
|
||||
return promise;
|
||||
} else
|
||||
return ProxiedMethod(this,"getTurn",arguments);
|
||||
}
|
||||
|
||||
GameProxy.prototype.getMoveString = function(move) {
|
||||
if(this.game) {
|
||||
var self = this;
|
||||
var promise = new Promise( function(resolve, reject) {
|
||||
if(Array.isArray(move))
|
||||
resolve(
|
||||
move.map( (m) => {
|
||||
return self.game.CreateMove(m).ToString();
|
||||
})
|
||||
)
|
||||
else
|
||||
resolve(self.game.CreateMove(move).ToString());
|
||||
});
|
||||
return promise;
|
||||
} else
|
||||
return ProxiedMethod(this,"getMoveString",arguments);
|
||||
}
|
||||
|
||||
GameProxy.prototype.pickMove = function(moveString) {
|
||||
if(this.game) {
|
||||
var self = this;
|
||||
var promise = new Promise( function(resolve, reject) {
|
||||
if(!self.game.mBoard.mMoves || self.game.mBoard.mMoves.length==0)
|
||||
self.game.mBoard.GenerateMoves(self.game);
|
||||
var move = self.game.GetBestMatchingMove(moveString,self.game.mBoard.mMoves);
|
||||
resolve(move);
|
||||
});
|
||||
return promise;
|
||||
} else
|
||||
return ProxiedMethod(this,"pickMove",arguments);
|
||||
}
|
||||
|
||||
GameProxy.prototype.playMove = function(move) {
|
||||
if(jsContext=="node" || (!this.iframe && !this.area))
|
||||
return this.applyMove(move);
|
||||
if(this.game) {
|
||||
var self = this;
|
||||
var promise = new Promise( function(resolve, reject) {
|
||||
|
||||
self.game.PlayMove(move)
|
||||
.then(()=>{
|
||||
var finished = self.game.GetFinished();
|
||||
if(!finished)
|
||||
self.game.InvertWho();
|
||||
self.game.DisplayBoard();
|
||||
resolve({
|
||||
finished: !!finished,
|
||||
winner: finished
|
||||
});
|
||||
})
|
||||
.catch( (err) => {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
return promise;
|
||||
} else
|
||||
return ProxiedMethod(this,"playMove",arguments);
|
||||
}
|
||||
|
||||
GameProxy.prototype.applyMove = function(move) {
|
||||
if(this.game) {
|
||||
var self = this;
|
||||
var promise = new Promise( function(resolve, reject) {
|
||||
|
||||
self.game.ApplyMove(move);
|
||||
var finished = self.game.GetFinished();
|
||||
if(!finished)
|
||||
self.game.InvertWho();
|
||||
if(self.area)
|
||||
self.game.DisplayBoard();
|
||||
resolve({
|
||||
finished: !!finished,
|
||||
winner: finished
|
||||
});
|
||||
});
|
||||
return promise;
|
||||
} else
|
||||
return ProxiedMethod(this,"applyMove",arguments);
|
||||
}
|
||||
|
||||
GameProxy.prototype.destroy = function(move) {
|
||||
var self = this;
|
||||
if(this.game) {
|
||||
var promise = new Promise( function(resolve, reject) {
|
||||
if(self.area)
|
||||
self.game.GameDestroyView();
|
||||
resolve();
|
||||
});
|
||||
return promise;
|
||||
} else
|
||||
return this.detachElement()
|
||||
.then( () => {
|
||||
delete matches[self.id];
|
||||
})
|
||||
}
|
||||
|
||||
GameProxy.prototype.getPlayedMoves = function() {
|
||||
if(this.game) {
|
||||
var self = this;
|
||||
var promise = new Promise( function(resolve, reject) {
|
||||
resolve(Array.from(self.game.mPlayedMoves));
|
||||
});
|
||||
return promise;
|
||||
} else
|
||||
return ProxiedMethod(this,"getPlayedMoves");
|
||||
}
|
||||
|
||||
GameProxy.prototype.userTurn = function() {
|
||||
if(jsContext=="node")
|
||||
return Promise.reject(new Error("userTurn(): not supported in node.js"));
|
||||
if(!this.area && !this.iframe)
|
||||
return Promise.reject(new Error("userTurn(): match is not attached to DOM element"));
|
||||
|
||||
if(this.game) {
|
||||
var self = this;
|
||||
|
||||
if(this.userTurnReject)
|
||||
return Promise.reject(new Error("userTurn(): already in user input mode"));
|
||||
|
||||
var savedHumanMove;
|
||||
|
||||
function Restore() {
|
||||
self.game.HumanMove = savedHumanMove;
|
||||
delete self.userTurnReject;
|
||||
}
|
||||
|
||||
var promise = new Promise( function(resolve, reject) {
|
||||
self.userTurnReject = reject;
|
||||
|
||||
function HumanMove(move) {
|
||||
self.game.ApplyMove(move);
|
||||
var finished = self.game.GetFinished();
|
||||
if(!finished)
|
||||
self.game.InvertWho();
|
||||
self.game.DisplayBoard();
|
||||
resolve({
|
||||
move: move,
|
||||
finished: !!finished,
|
||||
winner: finished
|
||||
});
|
||||
}
|
||||
savedHumanMove = self.game.HumanMove;
|
||||
self.game.HumanMove = HumanMove;
|
||||
|
||||
self.game.HumanTurn();
|
||||
});
|
||||
return promise
|
||||
.then( (result) => {
|
||||
Restore();
|
||||
return result;
|
||||
})
|
||||
.catch( (err) => {
|
||||
Restore();
|
||||
throw err;
|
||||
})
|
||||
} else
|
||||
return ProxiedMethod(this,"userTurn");
|
||||
}
|
||||
|
||||
GameProxy.prototype.abortUserTurn = function() {
|
||||
if(jsContext=="node")
|
||||
return Promise.reject(new Error("abortUserTurn(): not supported in node.js"));
|
||||
if(!this.area && !this.iframe)
|
||||
return Promise.reject(new Error("abortUserTurn(): match is not attached to DOM element"));
|
||||
|
||||
if(this.game) {
|
||||
var self = this;
|
||||
|
||||
if(!this.userTurnReject)
|
||||
return Promise.reject(new Error("abortUserTurn(): not in user input mode"));
|
||||
|
||||
var promise = new Promise( function(resolve, reject) {
|
||||
self.game.HumanTurnEnd();
|
||||
var userTurnReject = self.userTurnReject;
|
||||
delete self.userTurnReject;
|
||||
userTurnReject(new Error("User input aborted"));
|
||||
resolve();
|
||||
});
|
||||
return promise;
|
||||
} else
|
||||
return ProxiedMethod(this,"abortUserTurn");
|
||||
}
|
||||
|
||||
GameProxy.prototype.machineSearch = function(options) {
|
||||
|
||||
var self = this;
|
||||
|
||||
if(this.game) {
|
||||
|
||||
options = Object.assign({
|
||||
level: self.game.config.model.levels[0],
|
||||
threaded: true
|
||||
},options);
|
||||
|
||||
var savedMachineMove, savedMachineProgress;
|
||||
|
||||
function Restore() {
|
||||
self.game.MachineMove = savedMachineMove;
|
||||
self.game.MachineProgress = savedMachineProgress;
|
||||
delete self.machineSearchReject;
|
||||
}
|
||||
|
||||
var promise = new Promise( function(resolve, reject) {
|
||||
self.machineSearchReject = reject;
|
||||
|
||||
function MachineMove(result) {
|
||||
var move = result.move;
|
||||
delete result.move;
|
||||
var finished = self.game.GetFinished();
|
||||
resolve({
|
||||
move: move,
|
||||
finished: !!finished,
|
||||
winner: finished
|
||||
});
|
||||
}
|
||||
savedMachineMove = self.game.MachineMove;
|
||||
self.game.MachineMove = MachineMove;
|
||||
|
||||
function MachineProgress(percent) {
|
||||
if(self.area)
|
||||
window.parent.postMessage({
|
||||
joclyEmbeddedGameId: self.id,
|
||||
message: {
|
||||
type: "machine-progress",
|
||||
progress: percent
|
||||
}
|
||||
},"*");
|
||||
}
|
||||
savedMachineProgress = self.game.MachineProgress;
|
||||
self.game.MachineProgress = MachineProgress;
|
||||
|
||||
self.game.StartMachine(options);
|
||||
});
|
||||
return promise
|
||||
.then( (result) => {
|
||||
Restore();
|
||||
return result;
|
||||
})
|
||||
.catch( (err) => {
|
||||
Restore();
|
||||
throw err;
|
||||
})
|
||||
} else {
|
||||
function Restore2() {
|
||||
delete self.machineProgressListener;
|
||||
}
|
||||
options = options || {};
|
||||
self.machineProgressListener = options.progress || function() {};
|
||||
delete options.progress;
|
||||
|
||||
return ProxiedMethod(this,"machineSearch",[options])
|
||||
.then( (result) => {
|
||||
Restore2();
|
||||
return result;
|
||||
})
|
||||
.catch( (err) => {
|
||||
Restore2();
|
||||
throw err;
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
GameProxy.prototype.abortMachineSearch = function() {
|
||||
if(this.game) {
|
||||
var self = this;
|
||||
|
||||
if(!this.machineSearchReject)
|
||||
return Promise.reject(new Error("abortMachineSearch(): machine not searching"));
|
||||
|
||||
var promise = new Promise( function(resolve, reject) {
|
||||
self.game.StopThreadedMachine();
|
||||
var machineSearchReject = self.machineSearchReject;
|
||||
delete self.machineSearchReject;
|
||||
machineSearchReject(new Error("Machine search aborted"));
|
||||
resolve();
|
||||
});
|
||||
return promise;
|
||||
} else
|
||||
return ProxiedMethod(this,"abortMachineSearch");
|
||||
}
|
||||
|
||||
GameProxy.prototype.setViewOptions = function(options) {
|
||||
if(jsContext=="node")
|
||||
return Promise.reject(new Error("setViewOptions(): not supported in node.js"));
|
||||
if(!this.area && !this.iframe)
|
||||
return Promise.reject(new Error("setViewOptions(): match is not attached to DOM element"));
|
||||
|
||||
if(this.game) {
|
||||
var self = this;
|
||||
|
||||
var promise = new Promise( function(resolve, reject) {
|
||||
self.game.GameDestroyView();
|
||||
const optDefs = {
|
||||
"mSkin": "skin",
|
||||
"mNotation": "notation",
|
||||
"mSounds": "sounds",
|
||||
"mShowMoves": "showMoves",
|
||||
"mAutoComplete": "autoComplete"
|
||||
}
|
||||
for(var o in optDefs)
|
||||
if(typeof options[optDefs[o]]!="undefined")
|
||||
self.game[o] = options[optDefs[o]];
|
||||
self.game.GameInitView();
|
||||
resolve();
|
||||
});
|
||||
return promise;
|
||||
} else
|
||||
return ProxiedMethod(this,"setViewOptions",arguments);
|
||||
}
|
||||
|
||||
GameProxy.prototype.getFinished = function() {
|
||||
if(this.game) {
|
||||
var self = this;
|
||||
|
||||
var promise = new Promise( function(resolve, reject) {
|
||||
var finished = self.game.GetFinished();
|
||||
resolve({
|
||||
finished: !!finished,
|
||||
winner: finished
|
||||
});
|
||||
});
|
||||
return promise;
|
||||
} else
|
||||
return ProxiedMethod(this,"getFinished");
|
||||
}
|
||||
|
||||
GameProxy.prototype.rollback = function(index) {
|
||||
if(this.game) {
|
||||
var self = this;
|
||||
|
||||
var promise = new Promise( function(resolve, reject) {
|
||||
if(index<0)
|
||||
index = self.game.mPlayedMoves.length + index;
|
||||
index = Math.max(0,Math.min(index,self.game.mPlayedMoves.length));
|
||||
var playedMoves = self.game.mPlayedMoves;
|
||||
self.game.BackTo(index,playedMoves);
|
||||
if(self.area)
|
||||
self.game.DisplayBoard();
|
||||
resolve();
|
||||
});
|
||||
return promise;
|
||||
} else
|
||||
return ProxiedMethod(this,"rollback",arguments);
|
||||
}
|
||||
|
||||
GameProxy.prototype.otherPlayer = function(player) {
|
||||
var promise = new Promise( function(resolve, reject) {
|
||||
if(player==1)
|
||||
resolve(-1);
|
||||
else if(player==-1)
|
||||
resolve(1);
|
||||
else
|
||||
reject(new Error("otherPlayer: invalid input"));
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
|
||||
GameProxy.prototype.save = function() {
|
||||
if(this.game) {
|
||||
var self = this;
|
||||
|
||||
var promise = new Promise( function(resolve, reject) {
|
||||
resolve({
|
||||
playedMoves: Array.from(self.game.mPlayedMoves)
|
||||
})
|
||||
});
|
||||
return promise;
|
||||
} else
|
||||
return ProxiedMethod(this,"save");
|
||||
}
|
||||
|
||||
GameProxy.prototype.load = function(data) {
|
||||
if(this.game) {
|
||||
var self = this;
|
||||
|
||||
var promise = new Promise( function(resolve, reject) {
|
||||
self.game.Load(data);
|
||||
if(self.area)
|
||||
self.game.DisplayBoard();
|
||||
resolve();
|
||||
});
|
||||
return promise;
|
||||
} else
|
||||
return ProxiedMethod(this,"load",arguments);
|
||||
}
|
||||
|
||||
exports.createMatch = CreateMatch;
|
||||
exports._createInternalGame = CreateInternalGame; // do not use this
|
||||
|
||||
exports.PLAYER_A = 1;
|
||||
exports.PLAYER_B = -1;
|
||||
exports.DRAW = 2;
|
||||
|
||||
})();
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -1,162 +0,0 @@
|
|||
/* Copyright 2017 Jocly
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* As a special exception, the copyright holders give permission to link the
|
||||
* code of portions of this program with the OpenSSL library under certain
|
||||
* conditions as described in each individual source file and distribute
|
||||
* linked combinations including the program with the OpenSSL library. You
|
||||
* must comply with the GNU Affero General Public License in all respects
|
||||
* for all of the code used other than as permitted herein. If you modify
|
||||
* file(s) with this exception, you may extend this exception to your
|
||||
* version of the file(s), but you are not obligated to do so. If you do not
|
||||
* wish to do so, delete this exception statement from your version. If you
|
||||
* delete this exception statement from all source files in the program,
|
||||
* then also delete it in the license file.
|
||||
*/
|
||||
|
||||
var joclyGame = require("./jocly.game");
|
||||
var joclyCore = require("./jocly.core");
|
||||
|
||||
var gameConfigs = {}
|
||||
|
||||
function GameProxyClass(gameName) {
|
||||
this.gameName = gameName;
|
||||
this.listeners = [];
|
||||
}
|
||||
|
||||
GameProxyClass.prototype.listen = function(listener) {
|
||||
var self = this;
|
||||
var promise = new Promise((resolve,reject)=>{
|
||||
self.listeners.push(listener);
|
||||
resolve();
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
|
||||
GameProxyClass.prototype.unlisten = function(listener) {
|
||||
var self = this;
|
||||
var promise = new Promise((resolve,reject)=>{
|
||||
for(var i = self.listeners.length-1;i>=0;i--)
|
||||
if(self.listeners[i]==listener)
|
||||
self.listeners.splice(i,1);
|
||||
resolve();
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
|
||||
GameProxyClass.prototype.humanTurn = function() {
|
||||
return this.game.humanTurn();
|
||||
}
|
||||
|
||||
GameProxyClass.prototype.machineTurn = function(options) {
|
||||
var self = this;
|
||||
var promise = new Promise((resolve,reject)=>{
|
||||
self.game.StartMachine(options);
|
||||
resolve();
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
|
||||
GameProxyClass.prototype.getFinished = function() {
|
||||
var self = this;
|
||||
var promise = new Promise((resolve,reject)=>{
|
||||
var finished = self.gameName.GetFinished();
|
||||
resolve(finished);
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
|
||||
GameProxyClass.prototype.getMoveString = function(move) {
|
||||
var self = this;
|
||||
var promise = new Promise((resolve,reject)=>{
|
||||
var m = new (self.game.GetMoveClass())(move);
|
||||
resolve(m.ToString());
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
|
||||
function Post(game,message) {
|
||||
game.listeners.forEach((listener)=>{
|
||||
listener.call(game,message);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function GetListener(game) {
|
||||
return function(message) {
|
||||
var _game = this;
|
||||
switch(message.type) {
|
||||
case "human-move":
|
||||
_game.ApplyMove(message.move);
|
||||
var finished = _game.GetFinished();
|
||||
if(!finished)
|
||||
_game.InvertWho();
|
||||
Object.assign(message,{
|
||||
finished: !!finished,
|
||||
winner: finished
|
||||
});
|
||||
break;
|
||||
case "machine-move":
|
||||
var move = message.result.move;
|
||||
delete message.result.move;
|
||||
_game.ApplyMove(move);
|
||||
var finished = _game.GetFinished();
|
||||
if(!finished)
|
||||
_game.InvertWho();
|
||||
Post(game,{
|
||||
type: "machine-move",
|
||||
moveData: message.result,
|
||||
move: move,
|
||||
finished: !!finished,
|
||||
winner: finished
|
||||
});
|
||||
return;
|
||||
}
|
||||
Post(game,message);
|
||||
}
|
||||
}
|
||||
|
||||
function CreateGame(gameName) {
|
||||
var promise = new Promise((resolve,reject)=>{
|
||||
|
||||
function CreateGame(config) {
|
||||
var game = new GameProxyClass(gameName);
|
||||
Object.assign(game,config);
|
||||
game.game = new joclyCore.createInternalGame(gameName)
|
||||
.then((_game)=>{
|
||||
_game.AddListener(GetListener(game));
|
||||
game.game = _game;
|
||||
resolve(game);
|
||||
},reject);
|
||||
}
|
||||
|
||||
var config = gameConfigs[gameName];
|
||||
if(config)
|
||||
CreateGame(config);
|
||||
else {
|
||||
joclyCore.listGames()
|
||||
.then((games)=>{
|
||||
var gameDescr = games[gameName];
|
||||
if (!gameDescr)
|
||||
return reject(new Error("Game " + gameName + " not found"));
|
||||
var config = require("./games/" + gameDescr.module + "/" + gameName + "-config.js")
|
||||
gameConfigs[gameName] = config;
|
||||
CreateGame(config);
|
||||
},reject);
|
||||
}
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
|
||||
exports.createGame = CreateGame;
|
Loading…
Reference in a new issue