diff --git a/app.js b/app.js
index b56d131..da04a2e 100644
--- a/app.js
+++ b/app.js
@@ -1,23 +1,24 @@
-var createError = require('http-errors');
-var express = require('express');
-var path = require('path');
-var cookieParser = require('cookie-parser');
-var logger = require('morgan');
-var indexRouter = require('./routes/index');
-var usersRouter = require('./routes/users');
+var createError = require('http-errors')
+var express = require('express')
+var path = require('path')
+var cookieParser = require('cookie-parser')
+var logger = require('morgan')
+var indexRouter = require('./routes/index')
+var usersRouter = require('./routes/users')
const config = require('./config')
const gameEngine = require('./game/gameEngine')
+const socketManager = require('./game/socketManager')
var app = express();
// view engine setup
-app.set('views', path.join(__dirname, 'views'));
+app.set('views', path.join(__dirname, 'views'))
app.set('view engine', 'jade');
app.use(logger('dev'));
app.use(express.json());
-app.use(express.urlencoded({ extended: false }));
+app.use(express.urlencoded({ extended: false }))
app.use(cookieParser());
-app.use(express.static(path.join(__dirname, 'public')));
+app.use(express.static(path.join(__dirname, 'public')))
app.use('/', indexRouter);
app.use('/users', usersRouter);
diff --git a/bin/www b/bin/www
index d814c1d..f13d8a1 100755
--- a/bin/www
+++ b/bin/www
@@ -101,7 +101,10 @@ let result = figlet("Burger Kin",(error,result) => {
console.log("written by Oren Zakay and Alon Genosar, Kin.org\n\nconfig:")
jclrz(config)
console.log('\n')
-
+ start()
})
-require('../game/socketManager')(server)
\ No newline at end of file
+ const start = async () => {
+ await require('../core/blockchain').init()
+ require('../game/socketManager')(server)
+}
diff --git a/config.js b/config.js
index 37a426a..ef6fd78 100644
--- a/config.js
+++ b/config.js
@@ -14,8 +14,14 @@ module.exports = { master_public_address: process.env.hasOwnProperty('master_pub
board_height: process.env.hasOwnProperty('board_height') ? parseInt(process.env.board_height) : 5,
monitor_tables: process.env.hasOwnProperty('monitor_tables') ? parseInt(process.env.monitor_tables) : false,
monitor_tables_interval: process.env.hasOwnProperty('monitor_tables_interval') ? parseInt(process.env.monitor_tables_interval) : 2000,
- game_fee: process.env.hasOwnProperty('game_fee') ? parseFloat(process.env.game_fee) : 10,
+ game_fee: process.env.hasOwnProperty('game_fee') ? parseFloat(process.env.game_fee) : 5,
bad_card_symbol_index: 1,
total_bad_card_pairs: 1,
- flipped_card_symbol_index: 0
- };
+ flipped_card_symbol_index: 0,
+ transaction_experation_in_sec: 10,
+ pre_result_timeout:200,
+ result_timout:2000,
+ server_version:0.1,
+ turn_timeout: process.env.hasOwnProperty('turn_timeout') ? parseInt(process.env.turn_timeout) : 10 * 1000,
+ totalChannels: process.env.hasOwnProperty('totalChannels') ? parseInt(process.env.totalChannels) : 10
+ }
diff --git a/core/blockchain.js b/core/blockchain.js
index c58e05f..02346ff 100644
--- a/core/blockchain.js
+++ b/core/blockchain.js
@@ -4,27 +4,38 @@
*
* Desc
*
- * @author Oren Zakay.
+ * @author Oren Zakay, Alon Genosar
*/
-const KinClient = require('@kinecosystem/kin-sdk-node').KinClient;
-const Environment = require('@kinecosystem/kin-sdk-node').Environment;
+const { KinClient, Transaction, Environment, Channels } = require('@kinecosystem/kin-sdk-node')
const config = require('../config')
-
const client = new KinClient(Environment.Testnet);
-
let masterAccount
-async function getMasterAccount() {
- if (!masterAccount) {
- masterAccount = await client.createKinAccount({
- seed: config.master_seed,
- appId: config.appId
- });
- }
- return masterAccount
+async function init() {
+ console.log("Creating channels")
+ let keepers = await Channels.createChannels({
+ environment: Environment.Testnet,
+ baseSeed: config.master_seed,
+ salt: "Dubon Haya Po",
+ channelsCount: config.totalChannels,
+ startingBalance: 0
+ })
+
+ let keys = keepers.map( item => {
+ return item.seed
+ })
+ console.log("Creating master account")
+ masterAccount = await client.createKinAccount({
+ seed: config.master_seed,
+ appId: config.appId,
+ channelSecretKeys:keys
+ });
+ console.log("Channels created succsesfully")
}
+
+
async function isAccountExisting(wallet_address) {
try {
const result = await client.isAccountExisting(wallet_address)
@@ -34,26 +45,30 @@ async function isAccountExisting(wallet_address) {
return false
}
}
+//console.log(Environment.Testnet.passphrase)
async function validateTransaction(transactionId) {
- const data = await client.getTransactionData(transactionId)
-
- return data
- //check for correct amount
- && data.hasOwnProperty('amount')
- && data.amount === config.game_fee
- //check for transaction date
- && data.hasOwnProperty('timeStamp')
- && new Date() - Date(data.timestamp) < 10
+ try {
+ const data = await client.getTransactionData(transactionId)
+ return data
+ //check for correct amount
+ && data.hasOwnProperty('amount')
+ && data.amount === config.game_fee
+ //check for transaction date
+ && data.hasOwnProperty('timeStamp')
+ && new Date() - Date(data.timestamp) < config.transaction_experation_in_sec // 10 sec
+ }
+ catch {
+ return false
+ }
}
+
async function createAccount(wallet_address) {
console.log("buildCreateAccount -> " + wallet_address)
- // Sign the account creation transaction
- const masterAccount = await getMasterAccount()
let createAccountBuilder = await masterAccount.buildCreateAccount({
address: wallet_address,
startingBalance: 100,
- fee: 100,
+ fee: 0,
memoText: "C" + createID(9)
})
@@ -63,18 +78,25 @@ async function createAccount(wallet_address) {
console.log("createAccount transaction id -> ", id)
}
+async function whitelistTransaction(walletPayload) {
+ try {
+ const whitelistTx = await masterAccount.whitelistTransaction(walletPayload)
+ return whitelistTx
+ } catch(error) {
+ throw error
+ }
+}
async function payToUser(wallet_address, amount) {
- //console.log("payToUser -> " + wallet_address + " with amount = " + amount)
- const masterAccount = await getMasterAccount()
- const transactionBuilder = await masterAccount.buildSendKin({
- address: wallet_address,
- amount: amount,
- fee: 100,
- memoText: createID(10)
+ masterAccount.channelsPool.acquireChannel( async channel => {
+ const transactionBuilder = await masterAccount.buildSendKin({
+ address: wallet_address,
+ amount: amount,
+ fee: 0,
+ memoText: createID(10),
+ channel: channel
+ })
+ return await masterAccount.submitTransaction(transactionBuilder)
})
-
- await masterAccount.submitTransaction(transactionBuilder)
- console.log("payToUser submitTransaction -> ", transactionBuilder)
}
function createID(length) {
@@ -91,5 +113,7 @@ module.exports = {
validateTransaction,
isAccountExisting,
createAccount,
- payToUser
+ payToUser,
+ whitelistTransaction,
+ init
}
\ No newline at end of file
diff --git a/game/Game.js b/game/Game.js
index cd5182a..8aa7c8a 100644
--- a/game/Game.js
+++ b/game/Game.js
@@ -15,17 +15,18 @@ for(var i = 0; i < config.board_width * config.board_height / 2.0; i++) {
symbols.push(i + 1)
symbols.push(i + 1)
}
-const states = Object.freeze({ PENDING: 'pending', PLAYING: 'playing', COMPLETED: 'completed' })
+const states = Object.freeze({ PENDING: 'pending', STARTING:'starting', TURN: 'turn', RESULT: 'result', COMPLETED: 'completed' })
class Game {
static get states() { return states }
constructor() {
this.id = newId(5)
- this.state = 'pending'
+ this.state = states.PENDING
this.players = {}
this.flipped = []
this.turn = null
+ this.stateValue = null
this.boardSize = [config.board_width,config.board_height]
this.board = this.shuffle(JSON.parse(JSON.stringify(symbols)))
}
@@ -46,12 +47,11 @@ class Game {
let cpy = JSON.parse(JSON.stringify(this))
cpy.board = cpy.board.map( item => { return item == null ? null : Math.min(item,0) } )
this.flipped.forEach( index => { cpy.board[index] = this.board[index] });
- delete cpy.flipped
return cpy
}
cardsLeft() {
- return this.board.filter( item => { return item != null }).length
+ return this.board.filter( item => { return item != null })
}
}
diff --git a/game/Player.js b/game/Player.js
index 8b73605..1d84ef1 100644
--- a/game/Player.js
+++ b/game/Player.js
@@ -9,10 +9,12 @@
class Player {
- constructor({id,name}) {
+ constructor({id,name,facebookId,avatar}) {
this.id = id
this.name = name
this.score = 0
+ this.facebookId = facebookId
+ this.avatar = avatar
}
}
module.exports = Player
\ No newline at end of file
diff --git a/game/gameEngine.js b/game/gameEngine.js
index e71d657..51e4ff5 100644
--- a/game/gameEngine.js
+++ b/game/gameEngine.js
@@ -13,9 +13,9 @@ const Game = require('./Game')
const Player = require('./Player')
const blockchain = require('../core/blockchain')
const events = require('events')
-
const Spinner = require('cli-spinner').Spinner;
const spinner = new Spinner("Monitoring Games")
+const timerByGameId = {}
spinner.setSpinnerString(2)
//ENUMS
@@ -26,18 +26,22 @@ var games = []
var gamesByUserId = {}
//Utils
-function gameEmit( {gameId, action, sender = "server", callerId, value } ) {
- module.exports.eventEmitter.emit('action', { action:action, gameId:gameId, callerId:callerId, value:value })
+function gameEmit( {gameId, action, sender = "server", callerId, value, result } ) {
+ module.exports.eventEmitter.emit('action', { action:action, gameId:gameId, callerId:callerId, value:value, result:result })
}
//API
module.exports = {
eventEmitter: new events.EventEmitter()
,actions: actions
- ,isInGame(callerId) {
- return (gamesByUserId[callerId])
+ ,isPlayerInGame(callerId) {
+ return gamesByUserId[callerId] !== undefined
}
,reset: () => {
+ timerByGameId.forEach( timer => {
+ clearTimeout( timer )
+ })
+ timerByGameId = {}
games = []
gamesByUserId = {}
}
@@ -52,14 +56,13 @@ module.exports = {
}, interval);
}
,doAction: async ({action,callerId,value}) => {
- if(!config.monitor_tables)
- console.log("[gameEngine] doAction",{action:action,callerId:callerId,value:value})
+ // if(!config.monitor_tables)
+ // console.log("[gameEngine] doAction",{action:action,callerId:callerId,value:value})
if( !callerId ) throw new Error("Missing callerId")
var game = gamesByUserId[callerId]
switch (action) {
-
//
// Recover
//
@@ -71,45 +74,71 @@ module.exports = {
//
// Join
//
- case actions.JOIN:
- const result = await blockchain.isAccountExisting(callerId)
- if(!result) throw new Error("Invalid public id")
-
- game = game || games.filter( game => game.state == Game.states.PENDING )[0] || new Game()
+ case actions.JOIN:
+ if(game)
+ return game.userFriendly()
+
+ //Validate
+ if( !value ) throw new Error("Missing transaction id" )
+ if( !value.name ) throw new Error("Missing name" )
+ //if( !value.transactionId ) throw new Error("Missing transaction id" )
+ if(!await blockchain.isAccountExisting(callerId) ) throw new Error("Invalid public id")
+
+ //Validate transaction
+ await !blockchain.validateTransaction(value.transactionId)
+
+ //Check for pending games
+ game = games.filter( game => game.state == Game.states.PENDING && Object.keys(game.players).length < 2 )[0] || new Game()
+
+ //Push game to games list if not already there
if(games.indexOf(game) < 0 )
games.push(game)
- // If new game created , check player's transaction
- if( game.players.length == 0 ) {
- if( !value ) throw new Error("Missing transaction id")
- if( await !blockchain.validateTransaction(value))
- throw new Error("Invalid transaction Id")
- }
- game.players[callerId] = new Player( { id:callerId, name:value } )
+ //Add player to game object
+ game.players[callerId] = new Player( { id:callerId, name:value.name,facebookId: value.facebookId,avatar: value.avatar } )
+
+ //Index games by player
gamesByUserId[callerId] = game
+
+ //Change state to starting if two player's has joind
+ if( Object.keys(game.players).length == 2 )
+ game.state = Game.states.STARTING
- game = game.userFriendly()
- if( game.state == Game.states.PENDING && Object.keys(game.players).length == 2 ) {
+ //Start game
+ if( game.state == Game.states.STARTING ) {
setTimeout( async function() {
- let result = await module.exports.doAction({ action:actions.TURN, callerId:callerId } )
- gameEmit( { gameId:game.id, action:actions.TURN, value:result, callerId:callerId } )
+ module.exports.doAction({ action:actions.TURN, callerId:callerId } )
}, 1000);
}
- return game
+ return game.userFriendly()
//
// Turn
//
case actions.TURN:
if(!game) throw new Error("User not in game")
- if(game.state != Game.states.PENDING && game.state != Game.states.PLAYING ) throw new Error("Turn not allowed")
-
+ if(game.state != Game.states.STARTING && game.state != Game.states.TURN && game.state != Game.states.RESULT ) throw new Error("Turn not allowed")
+
+ //Clear turn timeout
+ clearTimeout( timerByGameId[game.id])
+ delete game.stateTimeout
+
const playersId = Object.keys(game.players)
const i = playersId.indexOf(game.turn)
- game.turn = playersId[ (i + 1) % playersId.length]
- game.state = Game.states.PLAYING
+ game.turn = value || playersId[ (i + 1) % playersId.length]
+ game.state = Game.states.TURN
game.flipped = []
- return game.turn
+
+ //Set turn timoutÂ
+ game.stateTimeout = new Date().getTime() + config.turn_timeout
+ game.stateTotalTimeout = config.turn_timeout
+
+ timerByGameId[game.id] = setTimeout( async () => {
+ delete timerByGameId[game.id]
+ module.exports.doAction({ action:actions.RESULT, callerId:callerId } )
+ },config.turn_timeout )
+ gameEmit({ gameId:game.id,action:actions.TURN, value:value,result: game.userFriendly() } )
+ return game.userFriendly()
//
// Flip
@@ -117,75 +146,89 @@ module.exports = {
case actions.FLIP:
value = parseInt(value)
if( !game ) throw new Error("User not in game")
- if( game.state != Game.states.PLAYING ) throw new Error("Turn not allowed in that state")
+ if( game.state != Game.states.TURN ) throw new Error("Turn not allowed in that state")
if( game.turn != callerId) throw new Error("Not your turn")
- if( value === undefined || value !== parseInt(value)) throw new Error("Invalid value")
+ if( value === undefined) throw new Error("Invalid value")
if( game.flipped && game.flipped.indexOf(value) > -1 ) throw new Error("Card already flipeed")
if( game.flipped && game.flipped.length == 2 ) throw new Error("Cards already flipped")
if( game.board[value] === null ) throw new Error("Card alread removed")
game.flipped = game.flipped || []
game.flipped.push(value)
-
+
+
if( game.flipped.length == 2 ) {
+ clearTimeout( timerByGameId[game.id] )
setTimeout( async function() {
- let result = await module.exports.doAction({action:actions.RESULT,callerId:callerId})
- gameEmit( { gameId:game.id,action:actions.RESULT, value:result,callerId:callerId } )
- }, 1000);
- }
- return { position:value, symbol:game.board[value]}
+ module.exports.doAction({ action: actions.RESULT, callerId })
+ }, 1500 );
+ }
+ return game.userFriendly()
//
// Result
//
case actions.RESULT:
- const match = game.flipped.length == 2 && game.board[game.flipped[0]] === game.board[game.flipped[1] ]
- var p = null
- if(match) {
- const cardValue = game.board[game.flipped[0]]
- game.flipped.forEach( i => { game.board[i] = null })
- p = game.players[callerId]
- p.score = cardValue != config.bad_card_symbol_index ? p.score + 1 : -1
+ let match = false
+ if(game.flipped.length < 2) {
+ game.players[callerId].turnMissed = game.players[callerId].turnMissed || 0 + 1
+ } else {
+ match = game.flipped.length == 2 && game.board[game.flipped[0]] === game.board[game.flipped[1] ]
+ var p = null
+ if(match) {
+ const cardValue = game.board[game.flipped[0]]
+ game.flipped.forEach( i => { game.board[i] = null })
+ p = game.players[callerId]
+ p.score += cardValue != config.bad_card_symbol_index ? 1 : -1
+ }
}
-
- setTimeout( async function() {
- if( game.cardsLeft() > 1 && game.players[callerId].score > -1 ) {
- let result = await module.exports.doAction({action:actions.TURN,callerId:callerId})
- gameEmit( { gameId:game.id,action:actions.TURN, value:result } )
+ game.state = Game.states.RESULT
+ gameEmit( { gameId:game.id,action:actions.RESULT, value: match, callerId: "server",result: game.userFriendly()} )
+
+ var cardsLeft = game.cardsLeft()
+
+
+
+ if( cardsLeft.length > 2 || ( cardsLeft[0] && cardsLeft[0] != config.bad_card_symbol_index && cardsLeft[0] )) {
+ module.exports.doAction({ action: actions.TURN, callerId: callerId, value: match ? callerId : undefined })
+ }
+ else {
+ var winnerId = "tie"
+ let players = Object.values(game.players)
+ if( players[0].score !== players[1].score )
+ winnerId = players[0].score > players[1].score ? players[0].id : players[1].id
+ players.forEach( player => { delete gamesByUserId[player.id] })
+ games.splice(games.indexOf(game),1)
+ if(winnerId == 'tie') {
+ blockchain.payToUser( players[0].id, config.game_fee)
+ blockchain.payToUser( players[1].id, config.game_fee)
}
else {
- let result = await module.exports.doAction({action:actions.WIN,callerId:callerId})
- gameEmit( { gameId:game.id,action:actions.WIN, value:result } )
+ blockchain.payToUser(winnerId, config.game_fee * 2)
}
- }, 3000);
- return { match:match, callerId:callerId, positions:game.flipped,player:p}
-
- //
- // Win
- //
- case actions.WIN:
- let players = Object.values(game.players)
- var winnerId = players[0].score > players[1].score ? players[0].id : players[1].id
- players.forEach( player => { delete gamesByUserId[player.id] })
- games.splice(games.indexOf(game),1)
- blockchain.payToUser(winnerId,config.game_fee)
- return winnerId
+ game.state = Game.states.COMPLETED
+ gameEmit( { gameId:game.id,action:actions.WIN, value:winnerId, result: game } )
+ }
+ // }, 100);
break
-
+
//
// Leave
//
case actions.LEAVE:
if( game && game.state == Game.states.PENDING ) {
+
delete game.players[callerId]
delete gamesByUserId[callerId]
if(!Object.keys(game.players).length)
games.splice(games.indexOf(game),1)
+
+ blockchain.payToUser(callerId, config.game_fee )
}
break
default:
- throw new Error("Action",action," not supported")
+ throw new Error("Action",action,"not supported")
}
}
}
\ No newline at end of file
diff --git a/game/socketManager.js b/game/socketManager.js
index cf8aa8a..36dade1 100644
--- a/game/socketManager.js
+++ b/game/socketManager.js
@@ -7,6 +7,7 @@
* @author Alon Genosar.
*/
+
let config = require('../config')
const { doAction, actions, eventEmitter,test } = require('./gameEngine')
const gameEngine = require('./gameEngine')
@@ -16,45 +17,60 @@ const socketByUserId = []
const allowedUserActions = [ actions.FLIP, actions.JOIN, actions.RECOVER ]
// Game engine event listener
-eventEmitter.on("action",( {gameId,action,callerId,value} ) => {
- io.to(gameId).emit("action", { action:action, callerId:callerId, value:value })
+eventEmitter.on("action",( {gameId,action,callerId,value, result} ) => {
+ io.to(gameId).emit("action", { action:action, callerId:callerId, value:value,result:result })
})
-
// API
module.exports = function (server,options,cb) {
+ console.log("Starting socket manager")
io = require('socket.io')(server)
- io.on('connection', async function (socket,next) {
- //console.log("Connecting",socket.handshake.query.token, socket.handshake.query.name)
+ io.on('connection', async function (socket,next,a) {
if (socket.handshake.query &&
socket.handshake.query.token &&
socket.handshake.query.token != 'undefined' &&
socket.handshake.query.name &&
socket.handshake.query.name != 'undefined') {
try {
- //console.log(socket.handshake.query.token ,socket.handshake.query.name)
- let game = await doAction({ action:actions.JOIN, callerId: socket.handshake.query.token ,value:socket.handshake.query.name,socket:socket })
+ let value = {
+ name: socket.handshake.query.name,
+ transactionId: socket.handshake.query.transactionId,
+ facebookId: socket.handshake.query.facebookId,
+ avatar: socket.handshake.query.avatar
+ }
+
+ let game = await doAction({ action:actions.JOIN, callerId:socket.handshake.query.token, value, socket })
socket.gameId = game.id
socket.join(game.id)
socket.token = socket.handshake.query.token
- io.to(game.id).emit("action",{action:actions.JOIN, callerId:socket.token,value:game})
+ io.to(game.id).emit("action",{action:actions.JOIN, callerId:socket.token,value,result:game})
}
catch(error) {
- console.log("error",error)
socket.disconnect()
}
} else {
socket.disconnect()
}
socket.on('action',async function (action,value,cb) {
- // console.log("socket recieved aciton",action,value, socket.isAuthorized)
if( socket.token && allowedUserActions.indexOf(action) > -1 ) {
try {
- let result = await doAction({action:action,callerId:socket['token'], value:value,socket:socket})
+ let result = await doAction({action, callerId:socket['token'], value, socket})
+
if(cb)
cb(result)
- if(socket.gameId)
- io.to(socket.gameId).emit("action",{action:action,callerId:socket.token ,value:{result}})
+
+ if( result && result.hasOwnProperty('id') && !socket.gameId ) {
+ socket.gameId = result.id
+ socket.join(result.id)
+ socket.token = socket.handshake.query.token
+ }
+
+ if(socket.gameId)
+ io.to(socket.gameId).emit("action", { action: action,
+ callerId:socket.token,
+ value: value,
+ result: result
+ })
}
catch(error) {
if(cb)
diff --git a/public/socketio.html b/public/socketio.html
index 9829897..cc841be 100644
--- a/public/socketio.html
+++ b/public/socketio.html
@@ -1,7 +1,9 @@