UNPKG

playship_ludo_pseudo_quick

Version:

Server side neutrino plugin code for ludo game

853 lines (805 loc) 32.8 kB
const enums = require('../utils/enums'); const logger = require('../utils/playshipUtils').getLogger(); const board2Player = require('./board2Player'); const board3Player = require('./board3Player'); const board4Player = require('./board4Player'); const stringify = require('json-stringify'); const eventObjs = require('../utils/newGameEvent') const externalEventObjs = require('../utils/externalGameEvent') const ShuffleRoller = require('../utils/shuffleRoller'); const {_getGameProp, _setGameProp, _setPlayerProp, _getPlayerProp, _gameObjById, _getMeta,_getAllPlayerOwnedObjectIds ,_getNextTurnPlayerId,_handleRoll, _aggregatePlayerTurnTime , _findAbsoluteWinner , _offExtraTimeIfEnabled, _isPauseEnabled } = require('../utils/utils'); const GAME_PROPS = enums.GAME_PROPS; const PLAYER_PROPS = enums.PLAYER_PROPS; const OBJECT_PROPS = enums.OBJECT_PROPS; const gameAction = function(game, playerId, actionMessage) { const gk = {key: game.getId()}; const turnEvent = new eventObjs.turnEvent() if (game.getState() >= 30) return logger.warn(gk, 'Game has ended, nothing to do'); if (game.getCurrentTurnPlayerId() !== playerId) return logger.warn(gk, 'Received action message out of turn by', playerId); if (_getGameProp(game, GAME_PROPS.GAME_OVER_TS) > 0) return logger.warn(gk, 'Game is marked to end, no input allowed'); if (_getGameProp(game, GAME_PROPS.IS_TURN_PROCESSED_SERVER)) return logger.debug(gk, 'Already processed for this turn, waiting for turn over'); let tes = testableInput(game, playerId, actionMessage); if (tes === -1) return logger.warn(gk, 'Testable input not allowed in this environment'); if (tes === 1) return; // already processed return game.incrPluginStats('ludo.gameaction.'+actionMessage.actionName); if (actionMessage.actionName === 'rolledDice') { const diceRollEvent = new eventObjs.diceRoll() _setGameProp(game, GAME_PROPS.IS_DICE_REVEALED, true); _handleRoll(game,playerId,actionMessage.actionSpecificData,diceRollEvent) game.syncGameStateServVars(); return; } if(_getPlayerProp(game,playerId,PLAYER_PROPS.TURN_SKIP_DIE_ROLL_SIX)) return logger.info( gk,"player %s got three consecutive 6's hence breaking",playerId ) if (actionMessage.uniqueActionId !== 1 || actionMessage.actionName !== 'selectedPawn') return logger.warn(gk, 'Unknown invoke action received'); let pieceId = actionMessage.actionSpecificData; let pieceOwnerId = game.getOwner(pieceId); if (pieceOwnerId !== playerId) { game.incrPluginStats('ludo.foulmove.count'); return logger.warn(gk, 'Piece is not owned by player, cant move'); } logger.debug(gk, 'Received user action piece', pieceId); _setGameProp(game, GAME_PROPS.IS_TURN_PROCESSED, true); _setGameProp(game, GAME_PROPS.IS_TURN_PROCESSED_SERVER, true); game.syncGameStateServVars(); if(_isPauseEnabled(game)) _offExtraTimeIfEnabled(game,playerId,turnEvent); turnEndHandler(game, playerId, pieceId,null ,null ,turnEvent); }; function isAtleastOnePawnSelectable(game,playerId){ const gk = {key: game.getId()}; let pieceType = _getPlayerProp(game,playerId,PLAYER_PROPS.COLOR) let objIds = game.getAllObjIds(); for (let objId of objIds) { let obj = _gameObjById(game, objId) if(pieceType === obj.type) if (obj._get(OBJECT_PROPS.CAN_SELECT)) return true } logger.debug(gk, 'no moves possible'); game.sendTip(playerId, 18); return false } function testableInput(game, playerId, actionMessage) { if (actionMessage.uniqueActionId !== 100) return 0; const gk = {key: game.getId()}; if (actionMessage.uniqueActionId === 100 && parseInt(process.env._env) === 0) return -1; _setGameProp(game, GAME_PROPS.IS_TURN_PROCESSED, true); _setGameProp(game, GAME_PROPS.IS_TURN_PROCESSED_SERVER, true); game.syncGameStateServVars(); let data = JSON.parse(actionMessage.actionSpecificData); let pieceId = data.networkId; let roll = data.roll; logger.debug(gk, 'Received user test action piece', pieceId, 'roll', roll); turnEndHandler(game, playerId, pieceId, roll); return 1; } const turnEndHandler = function(game, playerId, pieceId, roll,isImmediate, turnEventObj, addOnTime) { const gk = {key: game.getId()}; let exec = (isTurnContinue) => { const meta = _getMeta(game); if (isTurnContinue === -1) { _setGameProp(game, GAME_PROPS.IS_TURN_PROCESSED, false); _setGameProp(game,GAME_PROPS.IS_TURN_PROCESSED_SERVER,false) game.addTransient(GAME_PROPS.IS_SKIP_CHECKED, false); if(turnEventObj) { turnEventObj.kingdom = _getPlayerProp(game, playerId, PLAYER_PROPS.TURN_COUNT) game.publishNewGameEvent(playerId, turnEventObj) } game.addTransient(); game.syncPlayerServVars(playerId); return game.syncGameStateServVars(); } let curTurnCount = _getPlayerProp(game, playerId, PLAYER_PROPS.TURN_COUNT); if(isTurnContinue !== 1) { _setPlayerProp(game, playerId, PLAYER_PROPS.TURN_COUNT, curTurnCount + 1); game.publishGameEvent(1612,[playerId],curTurnCount + 1) } if(turnEventObj) { turnEventObj.score = game.getPlayerScore(playerId) turnEventObj.kingdom = curTurnCount + 1 game.publishNewGameEvent(playerId, turnEventObj) } _aggregatePlayerTurnTime(game,playerId) _setGameProp(game, GAME_PROPS.IS_CONTINUE, isTurnContinue); let nextTurnTs = Date.now() let extraTime = 0 if(!isImmediate) extraTime= (meta.animTime || 1500) + (game.getTransient(GAME_PROPS.IS_EXTRA_ANIM_TIME_REQ) ? meta.extraAnimTime : 0) else extraTime += addOnTime ? addOnTime :0; logger.debug({key: game.getId()}, 'extra time ', extraTime); nextTurnTs += extraTime _setGameProp(game, GAME_PROPS.NEXT_TURN_TS, nextTurnTs); _setGameProp(game, GAME_PROPS.IS_TURN_PROCESSED, false); turnInit(game, playerId,isTurnContinue); game.syncGameStateServVars(); game.syncPlayerServVars(playerId); logger.debug({key: game.getId()}, 'next turn ts is ', nextTurnTs); game.incrPluginStats('ludo.turnend.count'); }; if (pieceId) processPiece(game, playerId, pieceId, roll, turnEventObj). then(exec). catch(err => { logger.error({key: game.getId()}, 'error processing turn', err); return game.abandonGame(enums.SERVER_ERROR_REASON); }); else exec(0); }; const turnInit = function(game, playerId,isTurnContinue) { const gk = {key: game.getId()}; const meta = _getMeta(game); if (!playerId) return; // if no playerId supplied, (game start scenario) // add new roll of die to current rolls _setGameProp(game, GAME_PROPS.IS_DICE_REVEALED, false); _setGameProp(game, GAME_PROPS.NO_POSSIBLE_MOVE, true); game.addTransient(GAME_PROPS.IS_EXTRA_ANIM_TIME_REQ, false); if(isTurnContinue) { let newRoll = rollADie(game, playerId); let isRolledSix = newRoll === 6 ? 1 : 0; if(isRolledSix){ if(_getPlayerProp(game,playerId, PLAYER_PROPS.CONSECUTIVE_SIXES) === 2){ game.incrPluginStats('ludo.consecutive.six.count'); _setPlayerProp(game,playerId,PLAYER_PROPS.TURN_SKIP_DIE_ROLL_SIX, true) } } _setPlayerProp(game, playerId, PLAYER_PROPS.DIE_ROLL, newRoll); game.syncPlayerServVars(playerId); enablePawns(game,playerId).catch(e =>{ logger.error(gk,"Enabling Pawns failed ,", e) }); sendTurnInitHHEvent(game,playerId,newRoll) } else { _setPlayerProp(game,playerId,PLAYER_PROPS.TURN_SKIP_DIE_ROLL_SIX, false) _setPlayerProp(game, playerId, PLAYER_PROPS.DIE_ROLL, null); let nextTurnPlayerId = game.fetchNextTurnPlayerId() let newRoll = rollADie(game, nextTurnPlayerId); _setPlayerProp(game,nextTurnPlayerId,PLAYER_PROPS.DIE_ROLL, newRoll) game.syncPlayerServVars(nextTurnPlayerId); enablePawns(game,nextTurnPlayerId).catch(e =>{ logger.error(gk,"Enabling Pawns failed ,", e) }); disablePawns(game,playerId); sendTurnInitHHEvent(game,nextTurnPlayerId,newRoll) _setGameProp(game, GAME_PROPS.IS_TURN_COMPLETED, true) } }; const issueTurnStartSavePoint = function(game) { const gameKey = game.getId(); game.issueSavepoint('__turn_start', err => { if (err) return logger.error({err: err, key: gameKey}, 'Error in issuing savepoint'); logger.debug({key: gameKey}, 'pre-turn save point issued'); }); }; function getBord(game){ let playerCount = parseInt(game.getNumberOfPlayers()); switch (playerCount){ case 2 : return board2Player; case 3 : return board3Player; case 4 : return board4Player; } } async function processPiece(game, playerId, pieceId, roll, turnEventObj) { const gk = {key: game.getId()}; let currentBoard = getBord(game) let scoreObj = { score : 0, message : { dieRoll: 0, extra: 0 } } let turnExpiry = game.getCurrentTurnExpiry(); let now = Date.now() const meta = _getMeta(game); let duration = meta.turnDuration; let timeTakenToPlay = duration - turnExpiry + now; turnEventObj.family = pieceId turnEventObj.time_stats = timeTakenToPlay // fetch die roll info let dieRoll = roll || parseInt( _getPlayerProp(game, playerId, PLAYER_PROPS.DIE_ROLL)); turnEventObj.class = dieRoll scoreObj.score += dieRoll scoreObj.message.dieRoll = dieRoll let isRolledSix = dieRoll === 6 ? 1 : 0; if(isRolledSix) { game.addTransient(GAME_PROPS.IS_EXTRA_ANIM_TIME_REQ, true); let consecutiveSixesCount = _getPlayerProp(game,playerId, PLAYER_PROPS.CONSECUTIVE_SIXES) +1 _setPlayerProp(game,playerId,PLAYER_PROPS.CONSECUTIVE_SIXES, consecutiveSixesCount) } logger.info(gk,"Dice roll is % , hence turn continue is ,",dieRoll, isRolledSix) game.publishGameEvent(1608,[playerId],dieRoll) let piece = _gameObjById(game, pieceId); // fetch destination for current roll and piece piece._set(OBJECT_PROPS.SCORE,piece._get(OBJECT_PROPS.SCORE) + dieRoll) game.syncGameObjServVars(...[pieceId]); promisifyFetchBoundaryForObjId(game); promisifyCollidedObjsWithBoundary(game); let curPosKey = await game.fetchBoundaryForObjIdProm(pieceId); turnEventObj.start_value = parseInt(curPosKey) //let curPosInfo = board.posInfoForPosKey(curPosKey); //if (curPosInfo.isHeaven) return null; let dest = currentBoard.nextDestination(piece.type, curPosKey, dieRoll,game,playerId); logger.info(gk, 'Moving piece', pieceId, 'distance', dieRoll, 'current posKey', curPosKey, 'to destination', stringify(dest)); // if invalid move, dont process if (dest == null) { logger.warn(gk, 'Invalid piece selected to move by', playerId, 'pieceId', pieceId, 'dieRoll', dieRoll); return -1; } if(!dest.isSafePosition) await isSafeZoneAvoided(game,playerId,pieceId,dest) game.publishHHGameEvent(playerId,"pawn_start",{ turn_id : game.getCurrentTurnId(), update_time : Date.now(), pawn_id : pieceId, current_index : parseInt(curPosKey), final_index : dest.posKey }) game.incrPluginStats('ludo.processPiece.count'); game.timePluginStats('ludo.processPiece.timeTakenToPlay', timeTakenToPlay); turnEventObj.end_value = dest.posKey //move the piece to destination movePiece(game, pieceId, dest); //await handleSizes(game, curPosKey, dest.posKey); // if destinations is heaven if (dest.isHeaven) { game.addTransient(GAME_PROPS.IS_EXTRA_ANIM_TIME_REQ, true); piece._set(OBJECT_PROPS.SCORE,piece._get(OBJECT_PROPS.SCORE) + 56) game.syncGameObjServVars(...[pieceId]); scoreObj.message.extra = 56 scoreObj.score += 56 scoreObj.message = JSON.stringify(scoreObj.message) game.awardScore(playerId,scoreObj) logger.info(gk,"player %s scored %s",playerId,scoreObj) game.sendTip(playerId, 14); let curAscendCount = _getPlayerProp(game, playerId, PLAYER_PROPS.ASCEND_COUNT) || 0; _setPlayerProp(game, playerId, PLAYER_PROPS.ASCEND_COUNT, curAscendCount + 1); let homeEvent = new externalEventObjs.homeEvent(); homeEvent.current_home_count = curAscendCount + 1; game.publishExternalGameEvent('BE_GAME_HOME_EVENT',playerId, homeEvent); game.publishGameEvent(1603, [playerId]); let piecesToAscend = meta.piecesToAscend || 1; await handleSizes(game, curPosKey, dest.posKey); if (curAscendCount + 1 >= piecesToAscend) { _setPlayerProp(game, playerId, PLAYER_PROPS.IS_ASCENDED, true); let gameOverTs = Date.now() + (meta.animTime || 1000); _setGameProp(game, GAME_PROPS.GAME_OVER_TS, gameOverTs); _setGameProp(game, GAME_PROPS.GAME_OVER_REASON, enums.PIECE_HEAVEN_END_GAME_REASON); logger.info(gk, 'Game marked to end at', gameOverTs, 'for reason', enums.PIECE_HEAVEN_END_GAME_REASON); return -1; } return 1; } if (dest.isSafePosition) { scoreObj.message = JSON.stringify(scoreObj.message) turnEventObj.order = true logger.info(gk,"player %s scored %s",playerId,scoreObj) game.awardScore(playerId,scoreObj) logger.debug(gk, 'reached safe position by', playerId, 'position', dest); game.publishGameEvent(1610,[playerId]) await handleSizes(game, curPosKey, dest.posKey); return isRolledSix; } // fetch all pieces in that block let allPieceIdsInBlock = await game.collidedObjsWithBoundaryProm(dest); if (allPieceIdsInBlock.length > 2) { //> 2 safe point scoreObj.message = JSON.stringify(scoreObj.message) logger.info(gk,"player %s scored %s",playerId,scoreObj) game.awardScore(playerId,scoreObj) logger.info(gk, 'Multiple pieces on same spot, it is a safe spot', pieceId, allPieceIdsInBlock); await handleSizes(game, curPosKey, dest.posKey); return isRolledSix; } else if (allPieceIdsInBlock.length === 2) {// 2 pieces then either kill or no kill let isKilledAndPosInfo = await handleTwoPieces(game, playerId, allPieceIdsInBlock, pieceId,scoreObj,turnEventObj); let posKey = dest.posKey; if(isKilledAndPosInfo.dest) { posKey = isKilledAndPosInfo.dest.posKey; await handleSizes(game, dest.posKey,posKey); } await handleSizes(game, curPosKey,dest.posKey); return isKilledAndPosInfo.isContinue ? isKilledAndPosInfo.isContinue : isRolledSix; } scoreObj.message = JSON.stringify(scoreObj.message) logger.info(gk,"player %s scored %s",playerId,scoreObj) game.awardScore(playerId,scoreObj) await handleSizes(game, curPosKey, dest.posKey); return isRolledSix // otherwise no turn continue } function movePiece(game, objId, dest,message,noUpdate) { let objTrans = { id: objId, posKey: dest.posKey, posX: dest.x, posY: dest.y, posZ: dest.z, message :message }; if(noUpdate) return; game.forceUpdateGameObjects(objTrans); } async function handleSizes(game, prevPosKey, destPosKey) { let meta = _getMeta(game); const gk = {key: game.getId()}; let prevList = await game.collidedObjsWithBoundaryProm({posKey: prevPosKey}); let destList = await game.collidedObjsWithBoundaryProm({posKey: destPosKey}); if (logger.debug()) logger.debug(gk, 'Ids for list sizes are', stringify({prevList, destList})); let prev = {r: 0, b: 0, y : 0, g :0}; let des = {r: 0, b: 0, y : 0, g :0}; for (let a of prevList) { let ob = _gameObjById(game, a); if (ob.type === "greenPiece") prev.g++; else if (ob.type === "redPiece") prev.r++; else if (ob.type === "yellowPiece") prev.y++; else if (ob.type === "bluePiece") prev.b++; } for (let a of destList) { let ob = _gameObjById(game, a); if (ob.type === "greenPiece") des.g++; else if (ob.type === "redPiece") des.r++; else if (ob.type === "yellowPiece") des.y++; else if (ob.type === "bluePiece") des.b++; } for (let a of prevList) { let ob = _gameObjById(game, a); if (ob.type === "greenPiece") { ob._set(OBJECT_PROPS.SELF_SHARE, prev.g); ob._set(OBJECT_PROPS.OPP_SHARE, prev.b + prev.r + prev.y); }else if (ob.type === "redPiece") { ob._set(OBJECT_PROPS.SELF_SHARE, prev.r); ob._set(OBJECT_PROPS.OPP_SHARE, prev.b + prev.g + prev.y); }else if (ob.type === "yellowPiece") { ob._set(OBJECT_PROPS.SELF_SHARE, prev.y); ob._set(OBJECT_PROPS.OPP_SHARE, prev.b + prev.g + prev.r); }else if (ob.type === "bluePiece") { ob._set(OBJECT_PROPS.SELF_SHARE, prev.b); ob._set(OBJECT_PROPS.OPP_SHARE, prev.r + prev.g + prev.y); } } for (let a of destList) { let ob = _gameObjById(game, a); if (ob.type === "greenPiece") { ob._set(OBJECT_PROPS.SELF_SHARE, des.g); ob._set(OBJECT_PROPS.OPP_SHARE, des.b + des.r + des.y); }else if (ob.type === "redPiece") { ob._set(OBJECT_PROPS.SELF_SHARE, des.r); ob._set(OBJECT_PROPS.OPP_SHARE, des.b + des.g + des.y); }else if (ob.type === "yellowPiece") { ob._set(OBJECT_PROPS.SELF_SHARE, des.y); ob._set(OBJECT_PROPS.OPP_SHARE, des.b + des.g + des.r); }else if (ob.type === "bluePiece") { ob._set(OBJECT_PROPS.SELF_SHARE, des.b); ob._set(OBJECT_PROPS.OPP_SHARE, des.r + des.g + des.y); } } game.syncGameObjServVars(...prevList, ...destList); } async function handleTwoPieces(game, playerId, allPieceIdsInBlock, pieceId,scoreObj, turnEventObj) { const gk = {key: game.getId()}; const meta = _getMeta(game); let currentBoard = getBord(game); let prevPieceId = allPieceIdsInBlock[0] === pieceId ? allPieceIdsInBlock[1] : allPieceIdsInBlock[0]; turnEventObj.genus = prevPieceId; let prevPiece = _gameObjById(game, prevPieceId); let curPiece = _gameObjById(game, pieceId); let isAKill = prevPiece.type !== curPiece.type; if (isAKill) { game.addTransient(GAME_PROPS.IS_EXTRA_ANIM_TIME_REQ, true); logger.info(gk, 'A kill by player', playerId, 'pieceId', pieceId, 'killed', prevPieceId); if(meta.noScoreOnKill) { scoreObj.message = JSON.stringify(scoreObj.message) game.awardScore(playerId, scoreObj) }else { scoreObj.message.extra = 7; scoreObj.score += 7 scoreObj.message = JSON.stringify(scoreObj.message) logger.info(gk, "player %s scored %s", playerId, scoreObj) game.awardScore(playerId, scoreObj) } // let dest = await fetchEmptyHomeDest(game, prevPiece); let dest = currentBoard.posInfoPieceInit(prevPiece.type) let opponentId = game.getOwner(prevPieceId); logger.info(gk,"score deducted from user %s with value &s",opponentId, -prevPiece._get(OBJECT_PROPS.SCORE)) game.awardScore(opponentId,{ score : -prevPiece._get(OBJECT_PROPS.SCORE), message : JSON.stringify({ extra : -prevPiece._get(OBJECT_PROPS.SCORE) })} ) let killedPawnPoints = prevPiece._get(OBJECT_PROPS.SCORE); prevPiece._set(OBJECT_PROPS.SCORE,0) game.syncGameObjServVars(...[prevPieceId]); movePiece(game, prevPieceId, dest,'isKilled'); let curKillCount = _getPlayerProp(game, playerId, PLAYER_PROPS.KILLS); let currOppKilledCount = _getPlayerProp(game , opponentId,PLAYER_PROPS.GOT_KILLED) isPlayerFraudAndPublishEvent(game, playerId, opponentId); _setPlayerProp(game, playerId, PLAYER_PROPS.KILLS, curKillCount + 1); _setPlayerProp(game, opponentId,PLAYER_PROPS.GOT_KILLED,currOppKilledCount + 1) let killTypes = _getPlayerProp(game, playerId, PLAYER_PROPS.KILL_TYPES); killTypes.types.push(prevPiece.type); _setPlayerProp(game, playerId, PLAYER_PROPS.KILL_TYPES, killTypes); let killEvent = new externalEventObjs.killEvent(); killEvent.killer_score = game.getPlayerScore(playerId); killEvent.victim_score = game.getPlayerScore(opponentId); killEvent.killed_pawn_points = killedPawnPoints; killEvent.killer_pawn_points = curPiece._get(OBJECT_PROPS.SCORE); game.publishExternalGameEvent('BE_GAME_KILL',playerId, killEvent); game.publishGameEvent(1601, [opponentId]); game.publishGameEvent(1602, [playerId]); game.sendTip(playerId, 13); if (curKillCount === 0) game.publishGameEvent(1611, [playerId], _getPlayerProp(game, playerId, PLAYER_PROPS.TURN_COUNT) + 1) // send tips /* // holding tips for sometime if (curKillCount === 0) game.sendTip(playerId, 3); else if (curKillCount === 1) game.sendTip(playerId, 4); */ /* // removed logic for max kills if (curKillCount + 1 >= meta.maxPieceKills) { let gameOverTs = Date.now() + (meta.animTime || 1000); _setGameProp(game, GAME_PROPS.GAME_OVER_TS, gameOverTs); logger.info(gk, 'Game marked to end at', gameOverTs); _setGameProp(game, GAME_PROPS.GAME_OVER_REASON, enums.PIECE_HEAVEN_END_GAME_REASON); logger.info(gk, 'Game marked to end at', gameOverTs, 'for reason', enums.MAX_KILLS_REACHED_REASON); return -1; } */ return {dest : dest, isContinue : 1}; } else { scoreObj.message = JSON.stringify(scoreObj.message) logger.info(gk,"player %s scored %s",playerId,scoreObj) turnEventObj.order = true game.awardScore(playerId,scoreObj) game.sendTip(playerId, 10); game.publishGameEvent(1610,[playerId]) return {isContinue : 0}; } } const isTurnSkip = function(game, playerId) { if(_getGameProp(game,GAME_PROPS.IS_DICE_REVEALED)){ if(_getPlayerProp(game,playerId,PLAYER_PROPS.TURN_SKIP_DIE_ROLL_SIX)){ game.sendTip(playerId, 17); return true; } return !isAtleastOnePawnSelectable(game,playerId); } return false; }; async function fetchEmptyHomeDest(game, piece) { let currentBoard = getBord(game) let allHomeDest = currentBoard.pieceAllHomePos(piece.type); for (let a of allHomeDest) { let allPieceIdsInBlock = await game.collidedObjsWithBoundaryProm(a); if (allPieceIdsInBlock.length === 0) return a; } } function fetchInitSpawnPosition(game,piece){ } function promisifyCollidedObjsWithBoundary(game) { if (typeof game.collidedObjsWithBoundaryProm !== 'function') { game.collidedObjsWithBoundaryProm = (boundary) => new Promise( (resolve, reject) => { game.collidedObjsWithBoundary(boundary, (err, res) => { if (err) return reject(err); return resolve(res); }); }); } } function promisifyFetchBoundaryForObjId(game) { if (typeof game.fetchBoundaryForObjIdProm !== 'function') { game.fetchBoundaryForObjIdProm = (pieceId) => new Promise( (resolve, reject) => { game.fetchBoundaryForObjId(pieceId, (err, res) => { if (err) return reject(err); return resolve(res); }); }); } } const rollADie = function(game, playerId) { let meta = _getMeta(game); let transKey = '_shuffler_' + playerId; let roller = game.getTransient(transKey); if (!roller) { roller = new ShuffleRoller(meta, game.dieRollShuffledBag); game.addTransient(transKey, roller); } let nextRoll = roller.nextRoll(); if(nextRoll != 6) _setPlayerProp(game, playerId, PLAYER_PROPS.CONSECUTIVE_SIXES,0); logger.debug({key: game.getId()}, 'Next roll for', playerId, 'is', nextRoll); return nextRoll; }; async function destroyForfeitPlayerPawns(game){ const gk = {key: game.getId()}; let objListToDel = [] let currentBoard = getBord(game) promisifyFetchBoundaryForObjId(game); promisifyCollidedObjsWithBoundary(game); for (let p of game.getAllPlayerIds()) { logger.debug(gk,"forfeit for player ",p,game.getPlayerState(p), _getPlayerProp(game,p,enums.PLAYER_PROPS.ARE_PAWNS_DESTROYED)) if(parseInt(game.getPlayerState(p)) === 30 && !_getPlayerProp(game,p,enums.PLAYER_PROPS.ARE_PAWNS_DESTROYED) && game.getNumberOfPlayers() > 2){ logger.debug(gk,"forfeit for player ",p) let objIds = game.getAllObjIds(); let pieceType = _getPlayerProp(game, p, enums.PLAYER_PROPS.COLOR); for (let objId of objIds) { let obj = _gameObjById(game, objId); if (pieceType === obj.type) { let curPosKey = await game.fetchBoundaryForObjIdProm(objId); let curPosInfo = currentBoard.posInfoForPosKey(curPosKey); logger.debug(gk,"moving ",obj ," with index ",curPosKey, " curPosInfo ",curPosInfo ," to home") obj._set(OBJECT_PROPS.SCORE,0) if(!curPosInfo.isHome){ let dest = await fetchEmptyHomeDest(game, obj); logger.debug(gk,"empty home pos",dest) movePiece(game,objId,dest) await handleSizes(game,curPosKey,dest.posKey) } objListToDel.push(objId) } } logger.debug(gk,"forfeit game objects ",p,objListToDel) game.destroyGameObjects(true,...objListToDel) _setPlayerProp(game,p,enums.PLAYER_PROPS.ARE_PAWNS_DESTROYED,true) } } } function sendTurnInitHHEvent(game,playerId,diceRoll){ game.publishHHGameEvent(playerId,"turn_init",{ turn_id: game.getCurrentTurnId()+1, roll: diceRoll, update_time: Date.now(), scores: getSeatingPlayerScore(game) } ) } function getSeatingPlayerScore (game){ let scores = [] for (let p of game.getPlayerSeating()) { scores.push(game.getPlayerScore(p)) } return scores } function disablePawns(game,playerId ){ let pieceType = _getPlayerProp(game,playerId,PLAYER_PROPS.COLOR) let objIds = game.getAllObjIds(); let objIdList = []; for (let objId of objIds) { let obj = _gameObjById(game, objId) if(pieceType === obj.type) { objIdList.push(objId) obj._set(OBJECT_PROPS.CAN_SELECT, false) } } if(objIdList.length > 0) game.syncGameObjServVars(...objIdList); } async function enablePawns(game,playerId){ let currentBoard = getBord(game) const gk = {key: game.getId()}; promisifyFetchBoundaryForObjId(game); promisifyCollidedObjsWithBoundary(game); let dieRoll = parseInt( _getPlayerProp(game, playerId, PLAYER_PROPS.DIE_ROLL)); let pieceType = _getPlayerProp(game,playerId,PLAYER_PROPS.COLOR) let objIds = game.getAllObjIds(); let objIdList = []; for (let objId of objIds) { let obj = _gameObjById(game, objId) let curPosKey = await game.fetchBoundaryForObjIdProm(objId); if(pieceType === obj.type) { let dist = currentBoard.calculateDist(obj.type, curPosKey, dieRoll,logger); if(isNaN(dist)) { if(!obj._get(OBJECT_PROPS.CAN_SELECT)) { objIdList.push(objId) obj._set(OBJECT_PROPS.CAN_SELECT, true) } } if (dist > 0) { objIdList.push(objId) obj._set(OBJECT_PROPS.CAN_SELECT, false) } else { if(!obj._get(OBJECT_PROPS.CAN_SELECT)) { objIdList.push(objId) obj._set(OBJECT_PROPS.CAN_SELECT, true) } } } } if(objIdList.length > 0) game.syncGameObjServVars(...objIdList); if(!isAtleastOnePawnSelectable(game,playerId)) { _setGameProp(game, GAME_PROPS.NO_POSSIBLE_MOVE, false); } } async function isSafeZoneAvoided(game,playerId,pawnId,dest){ const meta = _getMeta(game); const maxTurnCountRuleTwo = parseInt(meta.maxTurnCountRuleTwo); const curTurnCount = _getPlayerProp(game, playerId, PLAYER_PROPS.TURN_COUNT); if(curTurnCount > maxTurnCountRuleTwo) return; let currentBoard = getBord(game) const gk = {key: game.getId()}; logger.debug(gk,"turn count %s and threashold %s " , curTurnCount,maxTurnCountRuleTwo) logger.debug(gk,"validating safe zone is pawn",playerId) promisifyFetchBoundaryForObjId(game); promisifyCollidedObjsWithBoundary(game); let missedSafeZones = 0 let dieRoll = _getPlayerProp(game, playerId, PLAYER_PROPS.DIE_ROLL) let pieceType = _getPlayerProp(game,playerId,PLAYER_PROPS.COLOR) let objIds = game.getAllObjIds(); for (let objId of objIds) { let obj = _gameObjById(game, objId) if(pieceType === obj.type) { logger.debug(gk," safe zone check for",playerId,dest,objId) if(objId !== pawnId){ logger.debug(gk," safe zone validation for missed",playerId,dest) let curPosKey = await game.fetchBoundaryForObjIdProm(objId); let pDest = currentBoard.nextDestination(obj.type, curPosKey, dieRoll, game, playerId); if (pDest && pDest.isSafePosition) { logger.debug(gk, "safe zone reach missed", playerId, pDest) missedSafeZones++ } } } } if(missedSafeZones > 0 ){ let pMissedSafeZonesCount = _getPlayerProp(game,playerId,PLAYER_PROPS.MISSED_SAFE_ZONES) _setPlayerProp(game,playerId,PLAYER_PROPS.MISSED_SAFE_ZONES,pMissedSafeZonesCount + missedSafeZones) } } const getAbsoluteGameResult = function(game, scoreList) { const gk = {key: game.getId()}; scoreList.sort((a, b) => b.score - a.score); let res = [] let startPos = 0; for(let i = 0 ; i < scoreList.length ; i++){ let s = scoreList[i] let totalMatches = 0; for(let j = i + 1 ; j < scoreList.length ; j++){ if(s.score !== scoreList[j].score){ _setGameProp(game,GAME_PROPS.ABSOLUTE_WIN_TYPE,2) break; }else { logger.info(gk,"score list tie found",scoreList[i],"and",scoreList[j]) totalMatches++ } } logger.info(gk,"total matches found",totalMatches) // todo add proper wintypes if(totalMatches === 0 ) { let winType = 1 if(startPos !== 0) winType = 2 startPos = startPos + 1; res.push({userId: s.userId, winType: winType, position: startPos}) }else { let tiePlayers = [] let playerScoreMap = new Map(); for(let z = 0 ; z <= totalMatches ; z++){ playerScoreMap.set(scoreList[i+z].playerId,scoreList[i+z]) tiePlayers.push(scoreList[i+z].playerId) } logger.info(gk,"total tie players found",tiePlayers) let winnersList = _findAbsoluteWinner(game,tiePlayers); logger.info(gk," tie winners ",winnersList) for(let winnerPlayer of winnersList){ startPos = startPos + 1; let playerWinInfo = playerScoreMap.get(winnerPlayer) let winType = 1 if(startPos !== 0) winType = 2 res.push({userId: playerWinInfo.userId, winType: winType, position: startPos}) } i += totalMatches; } } game.syncGameStateServVars(); return res; } function isPlayerFraudAndPublishEvent(game, playerId, opponentPlayerId) { // Player is killing Opponent const curTurnCount = _getPlayerProp(game, playerId, PLAYER_PROPS.TURN_COUNT); const gk = {key: game.getId()};// player current Turn Count const opponentUserId = game.getUserIdByPlayerId(opponentPlayerId); const key = `${opponentPlayerId.toString()}_killCount`; let value = parseInt(game.getPlayerProp(playerId, key)); game.setPlayerProp(playerId, key, value + 1); value += 1; const meta = _getMeta(game); const minKillCountRuleOne = parseInt(meta.minKillCountRuleOne); const minKillCountRuleTwo = parseInt(meta.minKillCountRuleTwo); const maxTurnCountRuleOne = parseInt(meta.maxTurnCountRuleOne); const maxTurnCountRuleTwo = parseInt(meta.maxTurnCountRuleTwo); let fraudUserEvent; if(value === minKillCountRuleOne && curTurnCount <= maxTurnCountRuleOne) { fraudUserEvent = new eventObjs.fraudFirstKill(); fraudUserEvent.userId2 = opponentUserId; game.publishGameFraudEvent(playerId,fraudUserEvent); } else if(value === minKillCountRuleTwo && curTurnCount <= maxTurnCountRuleTwo) { fraudUserEvent = new eventObjs.fraudQuadrantKill(); fraudUserEvent.userId2 = opponentUserId; let pMissedSafeZonesCount = _getPlayerProp(game,opponentPlayerId,PLAYER_PROPS.MISSED_SAFE_ZONES) let qtkSafeZoneMissedThreashold = parseInt(meta.qtkSafeZoneMissedThreashold) || 2 if(pMissedSafeZonesCount > qtkSafeZoneMissedThreashold){ let fraudUserSafeZoneEvent = new eventObjs.fraudQuadrantKillSafeZone(); fraudUserSafeZoneEvent.userId2 = opponentUserId; game.publishGameFraudEvent(playerId,fraudUserSafeZoneEvent); } logger.debug(gk,"missed safe zone count %s " , pMissedSafeZonesCount) game.publishGameFraudEvent(playerId,fraudUserEvent); } } const initPlayerShuffleRoller = function(game , playerId){ let meta = _getMeta(game); let transKey = '_shuffler_' + playerId; let roller = game.getTransient(transKey); if (!roller) { roller = new ShuffleRoller(meta, game.dieRollShuffledBag); game.addTransient(transKey, roller); } } module.exports = { gameAction, turnInit, turnEndHandler, rollADie, issueTurnStartSavePoint, isTurnSkip, destroyForfeitPlayerPawns, enablePawns, getAbsoluteGameResult, sendTurnInitHHEvent, initPlayerShuffleRoller };