UNPKG

@ayshrj/ludo.js

Version:

A TypeScript-based headless Ludo game engine for simulating game logic, AI moves, and game state management.

2 lines (1 loc) 7.99 kB
"use strict";var b=Object.defineProperty;var y=Object.getOwnPropertyDescriptor;var N=Object.getOwnPropertyNames;var E=Object.prototype.hasOwnProperty;var _=(h,c)=>{for(var i in c)b(h,i,{get:c[i],enumerable:!0})},D=(h,c,i,o)=>{if(c&&typeof c=="object"||typeof c=="function")for(let e of N(c))!E.call(h,e)&&e!==i&&b(h,e,{get:()=>c[e],enumerable:!(o=y(c,e))||o.enumerable});return h};var B=h=>D(b({},"__esModule",{value:!0}),h);var O={};_(O,{Ludo:()=>g,initializeTokenPosition:()=>T});module.exports=B(O);var R=require("events");function k(h,c){return Math.floor(Math.random()*(c-h+1))+h}function T(){return{red:[-1,-1,-1,-1],green:[-1,-1,-1,-1],yellow:[-1,-1,-1,-1],blue:[-1,-1,-1,-1]}}var g=class extends R.EventEmitter{constructor(i=4){super();this.numberOfPlayers=i;this.ranking=[];this.currentDiceRoll=null;this.lastDiceRoll=null;this.validTokenIndices=[];this.currentConsecutiveSixes=0;this.TRACK_LENGTH=57;this.safeZones=[0,8,13,21,26,34,39,47];this.currentBoardStatus="";this.gameState="playerHasToRollADice";this.players=[];i===2?this.players=["blue","green"]:i===3?this.players=["blue","red","green"]:this.players=["blue","red","green","yellow"],this.reset()}emitStateChange(){this.emit("stateChange",this.getCurrentState())}reset(){this.board=Array.from({length:15},()=>Array.from({length:15},()=>null)),this.tokenPositions=T(),this.ranking=[],this.currentDiceRoll=null,this.lastDiceRoll=null,this.validTokenIndices=[],this.currentConsecutiveSixes=0,this.currentBoardStatus="",this.gameState="playerHasToRollADice";let i=k(0,this.players.length-1);this.currentPiece=this.players[i];let o={red:[[1,1],[1,4],[4,1],[4,4]],green:[[1,10],[1,13],[4,10],[4,13]],yellow:[[10,10],[10,13],[13,10],[13,13]],blue:[[10,1],[10,4],[13,1],[13,4]]};for(let t of this.players)o[t].forEach(([l,s])=>{this.board[l][s]={isHome:t}});let e=[];for(let t=1;t<=5;t++)e.push([6,t]);for(let t=5;t>=0;t--)e.push([t,6]);for(let t=7;t<=8;t++)e.push([0,t]);for(let t=1;t<=5;t++)e.push([t,8]);for(let t=9;t<=14;t++)e.push([6,t]);for(let t=7;t<=8;t++)e.push([t,14]);for(let t=13;t>=9;t--)e.push([8,t]);for(let t=9;t<=14;t++)e.push([t,8]);for(let t=7;t>=6;t--)e.push([14,t]);for(let t=13;t>=9;t--)e.push([t,6]);for(let t=5;t>=0;t--)e.push([8,t]);for(let t=0;t<=6;t++)e.push([7,t]);let a={red:e,green:e.map(t=>this.rotateCoord(t,90)),yellow:e.map(t=>this.rotateCoord(t,180)),blue:e.map(t=>this.rotateCoord(t,270))};this.colorPaths=a;for(let t of this.players)a[t].forEach(([s,r],n)=>{this.board[s][r]||(this.board[s][r]={}),t==="red"&&(this.board[s][r].redTrack=n),t==="green"&&(this.board[s][r].greenTrack=n),t==="blue"&&(this.board[s][r].blueTrack=n),t==="yellow"&&(this.board[s][r].yellowTrack=n),this.safeZones.includes(n)&&(this.board[s][r].isSafeZone=!0),n===0&&(this.board[s][r].isStartingPosition=t,this.board[s][r].isSafeZone=!0),n>=51&&n<=55&&(this.board[s][r].isOnPathToFinalPosition=t),n===56&&(this.board[s][r].isFinalPosition=t)});this.emitStateChange()}rotateCoord([i,o],e){return e===90?[7+(o-7),7-(i-7)]:e===180?[14-i,14-o]:[7-(o-7),7+(i-7)]}rollDiceForCurrentPiece(){if(this.gameState!=="playerHasToRollADice")return this.currentBoardStatus=`Invalid action. Current state: ${this.gameState}.`,this.emitStateChange(),-1;if(this.currentDiceRoll!==null)return this.currentBoardStatus="Already rolled. You must move a token or wait/pass.",this.emitStateChange(),this.currentDiceRoll;let i=k(1,6);if(this.currentDiceRoll=i,this.lastDiceRoll=i,i===6){if(this.currentConsecutiveSixes++,this.currentConsecutiveSixes===3)return this.currentBoardStatus=`Three consecutive sixes. Turn skipped for ${this.currentPiece}.`,this.resetTurnState(!1),this.nextTurn(),this.emitStateChange(),i}else this.currentConsecutiveSixes=0;return this.validTokenIndices=this.getValidMoves(this.currentPiece,i),this.validTokenIndices.length===0?(this.currentBoardStatus=`No valid moves for ${this.currentPiece} (rolled ${i}). Passing turn.`,this.resetTurnState(!1),this.nextTurn(),this.emitStateChange()):(this.gameState="playerHasToSelectAPosition",this.emitStateChange()),i}selectToken(i){if(this.gameState!=="playerHasToSelectAPosition"){this.currentBoardStatus=`Invalid action. State: ${this.gameState}`,this.emitStateChange();return}if(this.currentDiceRoll===null){this.currentBoardStatus="You must roll before selecting a token.",this.emitStateChange();return}if(!this.validTokenIndices.includes(i)){this.currentBoardStatus="That token is not a valid choice.",this.emitStateChange();return}let o=this.currentDiceRoll,e=this.tokenPositions[this.currentPiece][i],a;if(e===-1){if(o!==6){this.currentBoardStatus="Cannot leave home without rolling a 6.",this.emitStateChange();return}a=0}else{if(e+o>this.TRACK_LENGTH-1){this.currentBoardStatus="Move would go beyond final square. Invalid.",this.emitStateChange();return}a=e+o}this.tokenPositions[this.currentPiece][i]=a;let t=0;if(a!==56&&!this.safeZones.includes(a)&&(t=this.handleCollisions(a,this.currentPiece)),a===56&&this.tokenPositions[this.currentPiece].every(s=>s===56)&&!this.ranking.includes(this.currentPiece)&&this.ranking.push(this.currentPiece),this.resetTurnState(!1),t>0){this.currentBoardStatus=`${this.currentPiece} captured ${t} token(s). Roll again!`,this.gameState="playerHasToRollADice",this.emitStateChange();return}if(o===6){this.currentBoardStatus=`${this.currentPiece} rolled a 6. Roll again!`,this.gameState="playerHasToRollADice",this.emitStateChange();return}this.nextTurn(),this.emitStateChange()}getValidMoves(i,o){let e=this.tokenPositions[i],a=[];for(let t=0;t<4;t++){let l=e[t];l!==56&&(l===-1?o===6&&a.push(t):l+o<=56&&a.push(t))}return a}handleCollisions(i,o){let e=0,[a,t]=this.colorPaths[o][i];for(let l of this.players)if(l!==o)for(let s=0;s<4;s++){let r=this.tokenPositions[l][s];if(r<0||r===56)continue;let[n,S]=this.colorPaths[l][r];n===a&&S===t&&(this.tokenPositions[l][s]=-1,e++)}return e}nextTurn(){if(this.ranking.length>=this.players.length){this.gameState="gameFinished",this.currentBoardStatus="Game Over! All players finished.";return}let i=this.players.indexOf(this.currentPiece);this.currentPiece=this.players[(i+1)%this.players.length],this.currentConsecutiveSixes=0,this.gameState="playerHasToRollADice",this.currentBoardStatus=`Now it's ${this.currentPiece}'s turn to roll.`}resetTurnState(i=!0){this.currentDiceRoll=null,this.validTokenIndices=[],i&&(this.lastDiceRoll=null)}getCurrentState(){return{turn:this.currentPiece,tokenPositions:this.tokenPositions,ranking:this.ranking,boardStatus:this.currentBoardStatus,diceRoll:this.currentDiceRoll,lastDiceRoll:this.lastDiceRoll,gameState:this.gameState,players:this.players}}bestMove(){if(this.currentDiceRoll===null)return console.warn("bestMove called but no dice roll available"),-1;let i=this.currentDiceRoll,o=this.getValidMoves(this.currentPiece,i);if(o.length===0)return-1;let e={CAPTURE_BONUS:50,LEAVE_HOME_BONUS:35,LAND_SAFE_ZONE_BONUS:25,APPROACH_FINAL_BONUS:15,REACH_FINAL_BONUS:100,DISTANCE_ADVANCE_FACTOR:.5,RISK_PENALTY_NEAR_OPPONENT:40},a=Number.NEGATIVE_INFINITY,t=o[0];for(let l of o){let s=this.tokenPositions[this.currentPiece][l],r=s===-1?0:s+i,n=0;if(s===-1&&i===6&&(n+=e.LEAVE_HOME_BONUS),this.safeZones.includes(r)&&(n+=e.LAND_SAFE_ZONE_BONUS),r!==56&&!this.safeZones.includes(r)){let[S,C]=this.colorPaths[this.currentPiece][r],u=0;for(let f of this.players)if(f!==this.currentPiece)for(let d=0;d<4;d++){let m=this.tokenPositions[f][d];if(m<0||m===56)continue;let[p,P]=this.colorPaths[f][m];p===S&&P===C&&u++}u>0&&(n+=u*e.CAPTURE_BONUS)}if(r>=51&&r<56&&(n+=e.APPROACH_FINAL_BONUS),r===56&&(n+=e.REACH_FINAL_BONUS),n+=r*e.DISTANCE_ADVANCE_FACTOR,r<56){let[S,C]=this.colorPaths[this.currentPiece][r],u=0;for(let f of this.players)if(f!==this.currentPiece)for(let d=0;d<4;d++){let m=this.tokenPositions[f][d];if(!(m<0||m===56))for(let p=1;p<=6;p++){let P=m+p;if(P<=56){let[A,v]=this.colorPaths[f][P];if(A===S&&v===C){u++;break}}}}u>0&&(n-=u*e.RISK_PENALTY_NEAR_OPPONENT)}n>a&&(a=n,t=l)}return t}};0&&(module.exports={Ludo,initializeTokenPosition});