(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.TripleGame = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i * */ /** * Stores X,Y coordinates * @constructor */ class Pos { /** * * @param {...args} args see $assign for optional parameter * @returns {Pos} */ constructor (...args) { this.assign.apply(this, args); } /** * assign(1,2) or assign('1-2') or assign(new Pos(1,2)) * @param {type} args * */ assign (...args) { if (args.length >= 2) { this.x = args[0]; this.y = args[1]; return; } if (args.length === 1) { if (args[0] instanceof Pos) { this.x = args[0].x; this.y = args[0].y; return; } if (typeof args[0] === 'string') { this.key = args[0]; return; } } this.x = 0; this.y = 0; } /** * @returns {String} a key like '5-6' */ get key() { return this.x + '-' + this.y; } /** set key for position will change x and y * @param {string} s */ set key(s) { var a = s.split('-'); this.x = parseInt(a[0]); this.y = parseInt(a[1]); } get left() { return new Pos(this.x-1, this.y); } get right() { return new Pos(this.x+1, this.y); } get top() { return new Pos(this.x, this.y-1); } get bottom() { return new Pos(this.x, this.y+1); } /** * compare with a Pos * @param {Pos} pos * @returns {Boolean} true if pos is equal to this */ eq (pos) { return (this.x === pos.x) && (this.y === pos.y); } } /** * Game Board a 2 dimensional array to hold integers in each cell * @constructor */ class Board { /** * @param {integer} cols * @param {integer} rows * @param {type} initial a interger or function to fill the board * @returns {undefined} */ constructor (cols, rows, initial = 0) { this.setSize(cols, rows, initial); } setSize (cols, rows, initial = 0) { this._a = new Array(rows); this.cols = cols; this.rows = rows; for(let i=0; i < this.rows; ++i) this._a[i] = new Array(this.cols); this.fill(initial); } /** * @param {integer} col * @param {integer} row * @returns {integer} content of board cell */ get (col, row) { return this._a[row][col]; }; /** * @param {integer} col * @param {integer} row * @param {integer} val */ set (col, row, val) { this._a[row][col] = val; }; /** * @param {Pos} pos * @returns {integer} */ getPos (pos) { return this._a[pos.y][pos.x]; } /** * @param {Pos} pos * @param {integer} val */ setPos (pos, val) { this._a[pos.y][pos.x] = val; } /** * iterate through all cells * @param {function} callback( item, x, y) */ forEach (callback) { for(let r=0; r < this.rows; ++r) for(let c=0; c < this.cols; ++c) callback(this._a[r][c], c, r); } /** * @param {function} filter = function( {pos,value} ) { return boolean } * @returns {Object} board as object {{pos: {x:0,y:0}, val: 1},..} */ getHash (filter) { var result = {}; for(let r=0; r < this.rows; ++r) for(let c=0; c < this.cols; ++c) { var pos = new Pos(c,r); var obj = { "pos": pos, "val": this.getPos(pos) }; if ((filter === undefined) || filter(obj)) result[obj.pos.key] = obj; } return result; } /** * @param {type} value integer or function(x,y) * @returns {Array} array of Pos of cells contains value */ getPosList (value) { var result = []; for(let r=0; r < this.rows; ++r) for(let c=0; c < this.cols; ++c) { if ((value === undefined) || (value === this.get(c,r))) result.push(new Pos(c,r)); } return result; } /** * @param {type} value integer or function(x,y) */ fill (value) { for(let r=0; r < this.rows; ++r) for(let c=0; c < this.cols; ++c) { if(typeof value === 'function') { this._a[r][c] = value(c,r); } else { this._a[r][c] = value; } } } clone () { // return new Board(this.cols, this.rows, this.get.bind(this)); var result = new Board(this.cols, this.rows); for(let r=0; r < this.rows; ++r) for(let c=0; c < this.cols; ++c) result._a[r][c] = this._a[r][c]; return result; } /** * @param {Pos} pos * @returns {Boolean} true if pos is inside board dimensions */ validPos (pos) { return (pos.x >= 0) && (pos.x < this.cols) && (pos.y >= 0) && (pos.y < this.rows); } /** * @param {Pos} fromPos * @param {Pos} toPos */ move (fromPos, toPos) { this.setPos(toPos, this.getPos(fromPos)); this.setPos(fromPos, 0); } /** * @param {Pos} pos * @returns {Array} array of Pos with neigbour cells */ neighbours (pos) { var a = []; var p = pos.left; if (this.validPos(p)) a.push(p); p = pos.top; if (this.validPos(p)) a.push(p); p = pos.right; if (this.validPos(p)) a.push(p); p = pos.bottom; if (this.validPos(p)) a.push(p); return a; } floodFill (pos, matchfunc, matches, checked ) { if (!this.validPos(pos)) return; if (checked === undefined) checked = []; if (checked.indexOf(pos.key) >= 0) return; checked.push(pos.key); if (!matchfunc(pos)) return; matches.push(pos); this.floodFill( pos.left, matchfunc, matches, checked); this.floodFill( pos.right, matchfunc, matches, checked); this.floodFill( pos.top, matchfunc, matches, checked); this.floodFill( pos.bottom, matchfunc, matches, checked); } /** * Collect positions with neighbours of the same value * @param {Pos} startPos * @param {integer} members alternative array of integer * @param {Boolean} exclusive eclude Pos if true * @returns {Array} array of Pos */ getCluster( startPos, members, exclusive ) { var board = this; if (members === undefined) members = [board.getPos(startPos)]; if (typeof members === 'number') members = [members]; var result = []; this.floodFill( startPos, filter, result ); return result; function filter(pos) { if (!exclusive && pos.eq(startPos)) return true; return members.indexOf(board.getPos(pos)) >= 0; } } /** * Return board as string * @returns {string} */ toString () { var result = ''; // rows for(let r = 0; r < this.rows; ++r) { for(let c = 0; c < this.cols; ++c) { var f = '' + this._a[r][c]; if (f.length === 1) f += ' '; result += f ; } result += '\n'; } return result; }; /** * Return board as string with frame * @returns {string} */ toStringFramed () { var result = ''; result += ' '; // header for(let c = 0; c < this.cols; ++c) result += c + ' '; result += '\n'; result += '---'; for(let c = 0; c < this.cols; ++c) result += '--'; result += '\n'; // rows for(let r = 0; r < this.rows; ++r) { result += r + '| '; for(let c = 0; c < this.cols; ++c) { var f = '' + this._a[r][c]; if (f === '0') f = ' '; result += f + ' '; } result += '\n'; } return result; }; /** * Set board as string. String must have the same format as toString(). Board size will not change. * @param {string} s */ setString (s) { if (!s) return; var sr = s.split('\n'); var sc; // rows var maxR = sr.length > this.rows ? this.rows : sr.length; for(let r = 0; r < maxR; ++r) { sc = sr[r]; var maxC = sc.length > this.cols ? this.cols : sc.length; for(let c = 0; c < maxC; ++c) { var f = sc[c]; f = sc.substr(c*2,2).trim(); if (f === '') f = '0'; this._a[r][c] = parseInt(f); } } }; } module.exports = { Pos: Pos, Board: Board }; },{}],2:[function(require,module,exports){ /* * Triple Game configuration */ /** a list of game configurations * */ var configList = [ require('./config/default.js'), require('./config/test.js') ]; module.exports = { configList: configList, defaultConfig: configList[0] }; },{"./config/default.js":3,"./config/test.js":4}],3:[function(require,module,exports){ /* * */ const {TRIPLE_FIG} = require('../figures.js'); module.exports = Object({ name: 'Standard Game', description: 'Standard Game\nboard: 6 x 6\n6 random figures at start', board_size: {x: 6, y:6}, board: null, state: null, logger: false, // number of initial random figures initial_count: 6, // new game figures initial_figures: [ //[ figure, probability % ] [ TRIPLE_FIG.GRASS, 60 ], [ TRIPLE_FIG.BUSH, 15 ], [ TRIPLE_FIG.TREE, 2 ], [ TRIPLE_FIG.HUT, 0.6 ], [ TRIPLE_FIG.BEAR, 2.5 ], [ TRIPLE_FIG.ROCK, 2.5 ] ], // next figure while playing next_figures: [ //[ figure, probability % ] [ TRIPLE_FIG.GRASS, 60 ], [ TRIPLE_FIG.BUSH, 15 ], [ TRIPLE_FIG.TREE, 2 ], [ TRIPLE_FIG.HUT, 0.6 ], [ TRIPLE_FIG.BEAR, 2.5 ], [ TRIPLE_FIG.CRYSTAL, 2.5 ], [ TRIPLE_FIG.ROBOT, 2.5 ], [ TRIPLE_FIG.NINJA, 0.5 ] ] }); },{"../figures.js":5}],4:[function(require,module,exports){ /* * */ const {TRIPLE_FIG} = require('../figures.js'); module.exports = () => new Object({ name: 'Special 1', description: 'use initial board, hut,bear,crystal,rock ', board_size: {x: 4, y:6}, board: '200 0 7 \n' + '0 200 0 \n' + '0 0 200 \n' + '0 0 0 20\n' + '0 0 0 0 \n' + '300 0 0 \n', next_figure: TRIPLE_FIG.ROBOT, reserve: 0, total_score: 0, total_gold: 0, ondo: null, logger: false, // number of initial random figures initial_count: 0, // new game figures initial_figures: [ //[ figure, probability % ] ], // next figure while playing next_figures: [ //[ figure, probability % ] [ TRIPLE_FIG.HUT, 20 ], [ TRIPLE_FIG.BEAR, 5 ], [ TRIPLE_FIG.CRYSTAL, 10 ], [ TRIPLE_FIG.ROCK, 5 ], [ TRIPLE_FIG.ROBOT, 10 ] ] }); },{"../figures.js":5}],5:[function(require,module,exports){ /* * Copyright (C) 2018 wcs * */ /** * triple figure definitions * * * each figure (object or item) has an integer id. Use the symbols in TRIPLE_FIG. * * Figure properties are in 'var figures'. * use function figureProp() to retrieve figure property. Example: * score = figureProp(TRIPLE_FIG.GRASS).score; * */ /** figure id definition */ const TRIPLE_FIG = { EMPTY: 0, GRASS: 1, BUSH: 2, TREE: 3, HUT: 4, HOUSE: 5, MANSION: 6, CASTLE: 7, FLOATINGCASTLE: 8, TRIPLECASTLE: 9, ROCK: 20, MOUNTAIN: 21, ROBOT: 25, CRYSTAL: 26, BEAR: 30, TOMB: 31, NINJA: 32, CHURCH: 40, CATHEDRAL: 41, TREASURE: 50, BIGTREASURE: 51, SUPER: 100, SUPERBUSH: 102, // SUPER + BUSH SUPERTREE: 103, // SUPER + TREE SUPERHUT: 104, SUPERHOUSE: 105, SUPERMENSION: 106, SUPERCASTLE: 107, NONE: 0 }; /** * list of objects with figure properties * @namespace * @property {object} fig - figure properties * @property {number} fig.id - TRIPLE_FIG value * @property {string} fig.key - id as string * @property {string} fig.name - name * @property {number} fig.score - score for placing * @property {number} fig.gold - used for treasure (optional) * @property {number} fig.growto - id of figure to grow to (optional) * @property {number} fig.destroyto - id of figure when it is killed (optional) * @property {string} fig.img - name of image file * @property {number} fig.probability - probability to appear in percent * @property {number} fig.cost - price in shop * */ var figureList = [ { name: 'default', score: 0, growto: 0, destroyto: 0, gold: 0, key: '', id: -1, img: 'none' }, { name: 'Empty', score: 0, growto: 0, id: 0, key: '0', img: 'empty' }, { name: 'Grass', score: 5, growto: 2, probability: 60, cost: 100, id: 1, key: '1', img: 'grass' }, { name: 'Bush', score: 20, growto: 3, probability: 15, cost: 150, id: 2, key: '2', img: 'bush' }, { name: 'Tree', score: 100, growto: 4, probability: 2, cost: 400, id: 3, key: '3', img: 'tree' }, { name: 'Hut', score: 500, growto: 5, probability: 0.6, cost: 400, id: 4, key: '4', img: 'hut' }, { name: 'House', score: 1500, growto: 6, probability: 0.3, id: 5, key: '5', img: 'house' }, { name: 'Menison', score: 5000, growto: 7, id: 6, key: '6', img: 'menison' }, { name: 'Castle', score: 20000, growto: 8, id: 7, key: '7', img: 'castle' }, { name: 'Floating Castle', score: 100000, growto: 9, id: 8, key: '8', img: 'floatingcastle' }, { name: 'Triple Castle', score: 200000, id: 9, key: '9', img: 'triplecastle' }, { name: 'Rock', score: 500, growto: 21, probability: 2.5, id: 20, key: '20', img: 'rock' }, { name: 'Mountain', score: 1000, growto: 51, destroyto: 50, id: 21, key: '21', img: 'mountain' }, { name: 'Robot', score: 10, probability: 2.5, cost: 1000, id: 25, key: '25', img: 'robot' }, { name: 'Crystal', score: 10, probability: 2.5, cost: 1500, id: 26, key: '26', img: 'crystal' }, { name: 'Bear', score: 50, destroyto: 31, probability: 2.5, id: 30, key: '30', img: 'bear' }, { name: 'Tomb', score: 50, growto: 40, id: 31, key: '31', img: 'tomb' }, { name: 'Ninja', score: 50, destroyto: 31, probability: 1.5, id: 32, key: '32', img: 'ninja' }, { name: 'Church', score: 1000, growto: 41, id: 40, key: '40', img: 'church' }, { name: 'Cathedral', score: 5000, growto: 50, id: 41, key: '41', img: 'cathedral' }, { name: 'Treasure', score: 5, growto: 51, gold: 500, id: 50, key: '50', img: 'treasure' }, { name: 'Treasure', score: 10, gold: 1500, id: 51, key: '51', img: 'treasure' } ]; function generateFigureHash (list){ let result = {}; for (let i=0; i < list.length; i++){ result[list[i].key] = list[i]; } return result; } /** figures is a hash of figureList */ var figures = generateFigureHash(figureList); /** * Get figure by id * @param {string} f figure id (key) * @returns {Object} figure properties for f */ function figureProp (f) { if (figures[f] === undefined) console.warn('figureProp('+f+') is undefined'); // throw 'figureProp('+f+') is undefined'; return figures[f]; } /** * Get figure by id * @param {integer} figId * @returns {Object} figure properties for figId */ function figureById( figId ) { return figureProp(''+figId); } /** * Get image of a figure id * @param {integer} figId * @returns {string} image name */ function figureImgUrl( figId ) { let fo = figureById( figId ); if (!fo) fo = figureList[0]; return fo.img; } module.exports = { TRIPLE_FIG: TRIPLE_FIG, figures: figures, figureList: figureList, figureProp: figureProp, figureById: figureById, figureImgUrl: figureImgUrl }; },{}],6:[function(require,module,exports){ /* * module triple-game * */ const triple_game = require('./triple_game.js'); const board = require('./board.js'); const figures = require('./figures.js'); const config = require('./config.js'); module.exports = { TripleGame: triple_game.TripleGame, Game: triple_game.TripleGame, Pos: board.Pos, Board: board.Board, TRIPLE_FIG: figures.TRIPLE_FIG, figures: figures.figures, figureProp: figures.figureProp, figureList: figures.figureList, figureById: figures.figureById, figureImgUrl: figures.figureImgUrl, configList: config.configList, defaultConfig: config.defaultConfig }; },{"./board.js":1,"./config.js":2,"./figures.js":5,"./triple_game.js":7}],7:[function(require,module,exports){ /* * triple_game.js * * Triple Game Logic * * (c) 2018 by Willi Commer (wcs) * * version 2.1.3 * * * history * --------------------------------------------------- * 2.1.3 * - webpack import * - setFigure(fig) * 2.1.2 * 2018-10-xx * add nina and bear * add event handler (see ./doc/game-events.md) * * 2017-08-21 * copy from https://github.com/WilliCommer/TripleThree/blob/master/js/triple_game.js * * 2016 * * Vorbereitung der Anpassungen von der Java Version in * C:\Users\wcs\Documents\Java\JavaWeb\TiplePrime\src\main\java\com\wcs\tipleprime\TripleGame.java * * neu: function assign(src) * --------------------------------------------------- * */ // modue import const {TRIPLE_FIG, figureProp} = require('./figures.js'); const {Pos, Board} = require('./board.js'); const {percentageRandom} = require('./utils'); const {defaultConfig} = require('./config.js'); /** * Triple Game Engine * * @param {type} aConfig * @returns this */ function TripleGame(aConfig = defaultConfig) { // private variables var board = null; var empty_state = function() { return { next: 0, reserve: 0, score: 0, gold: 0, undo_enable: 0, game_over: false, move_count: 0 };}; var state = empty_state(); var bak = null; var events = {}; var config = null; var undo_fig = -1; _newGame( aConfig ); // return public interface return { /**@lends TripleGame * @returns {Board} the game board */ get board() { return board; }, /** @lends TripleGame * @returns {integer} the next figure to play */ nextFigure: function () { return state.next; }, /** @lends TripleGame * @returns {integer} the total score */ score: function () { return state.score; }, /** @lends TripleGame * @returns {integer} the reserve figure */ reserve: function () { return state.reserve; }, /** newGame() start new game */ newGame: _newGame, /** * Let the player (user) place next figure to field (x,y) * * @param {integer} x * @param {integer} y * @param {integer} fig a optional figure to play */ userMove: _userMove, /** * Let the player (user) place next figure to field (pos) * * @param {Pos} pos */ turn: function (pos) { return _userMove( pos.x,pos.y ); }, /** undoMove() undo last user move */ undoMove: restoreGame, /** swapReserve() swap reserve and next figure */ swapReserve: _swapReserve, /** * Check if figure can be set on pos * @param {Pos} pos board coordinates * @param {integer} fig figure to set on this field * @returns boolean */ isLegalMove: isLegalMove, // (pos, figure) /** * @returns boolean true if no more moves possible */ gameOver: function () { return board.getPosList(0).length === 0; }, /** * add nextFigure from shop * @param {integer} fig */ addFigure: addFigure, setConfig: setConfig, getConfig: getConfig, getState: getState, setState: setState, // DEBUG // set event callback // on('gameover', callback) || on('all',collback) on: _on, // toString() return game as string toString: _toString, // show gameevents on console enableLogger: _enableLogger }; // private functions // userMove(x,y) set next figure to x,y function _userMove(x,y, f) { let fig = state.next; let score = 0; let pos = new Pos(x,y); if (f !== undefined) fig = f; let figB = board.get(x,y); emit('move_begin', {fig: fig, pos: pos}); // move to a treasure field will get the gold only if ((figB === TRIPLE_FIG.TREASURE) || (figB === TRIPLE_FIG.BIGTREASURE)) { setBoardPos(pos, TRIPLE_FIG.EMPTY, figureProp(figB).score); state.gold += figureProp(figB).gold; emit('move_end', {gold: figureProp(figB).gold}); return figureProp(figB).score; } if(isLegalMove(pos,fig)) { saveGame(); switch (fig) { case TRIPLE_FIG.CRYSTAL: fig = getCrystalFigure(pos); score = setFigure(x, y, fig); break; case TRIPLE_FIG.ROBOT: let newfig = figureProp(figB).destroyto; if (newfig === undefined) { score = 0 - figureProp(figB).score; newfig = TRIPLE_FIG.EMPTY; } else { score = figureProp(newfig).score; } setBoardPos(pos, newfig, score); break; case TRIPLE_FIG.BEAR: score = figureProp(fig).score; setBoardPos(pos, TRIPLE_FIG.BEAR, score); break; default: score = setFigure(x, y, fig); } // switch (fig) score += animateFigures(pos); state.score += score; state.next = newNextFigure(); emit('nextfigure', {fig: state.next}); } else { // isLegalMove emit('move_fail',{fig: fig, pos:pos}); } emit('move_end', {fig: fig, pos:pos, score: score}); ++state.move_count; return score; } function animateFigures(insertPos) { var bears = []; var score = 0; emit('animate_begin'); // move bears bears = board.getPosList(TRIPLE_FIG.BEAR); for (var i=0; i < bears.length; i++) { var figPos = bears[i]; var newPos = getNewBearPos(figPos); if (newPos) { board.move(figPos, newPos); emit_movefigure(TRIPLE_FIG.BEAR, figPos, newPos); } }; // create hash table with bears bears = board.getHash( (obj) => { var ok = (obj.val === TRIPLE_FIG.BEAR); if (ok) { obj.fixed = getNewBearPos(obj.pos) === null; obj.checked = false; }; return ok; }); // convert fixed bear clusters to tomb for (var key in bears) { var bear = bears[key]; if (bear.checked) continue; var group = board.getCluster( bear.pos, [TRIPLE_FIG.BEAR, TRIPLE_FIG.NINJA]); var n = 0; for (let i=0; i < group.length; i++) { var b = bears[group[i].key]; if (b) { // else it's a ninja b.checked = true; if (b.fixed) n++; } } if (n !== group.length) continue; // continue if at least one bear is moveable // kill fixed group for (let i=0; i < group.length; i++) { setBoardPos(group[i], TRIPLE_FIG.TOMB); } score += figureProp(TRIPLE_FIG.TOMB).score * group.length; } // find tomb cluster and grow them to church var hash = board.getHash( (obj) => { return (obj.val === TRIPLE_FIG.TOMB); } ); for (let key in hash) { let obj = hash[key]; if (obj.checked) continue; let group = getPartners( obj.pos.x, obj.pos.y, TRIPLE_FIG.TOMB); if (group.length > 0) { for (let i=0; i < group.length; i++) hash[group[i].key].checked = true; let i = indexOfPos(group, insertPos); if (i < 0) i = 0; score += setFigure(group[i].x, group[i].y, TRIPLE_FIG.TOMB); } } // move ninjas bears = board.getPosList(TRIPLE_FIG.NINJA); for (let i=0; i < bears.length; i++) { let spare = board.getPosList(TRIPLE_FIG.EMPTY); let n = Math.floor(Math.random() * spare.length); board.move(bears[i], spare[n]); emit_movefigure(TRIPLE_FIG.NINJA,bears[i], spare[n]); }; emit('animate_end'); // check 'game over' state.game_over = board.getPosList(TRIPLE_FIG.EMPTY).length === 0; if (state.game_over) emit('gameover'); return score; } function getNewBearPos (actualPos) { var n = board.neighbours(actualPos); if (n.length === 0) return null; var i = Math.floor(Math.random() * n.length); if (isLegalMove(n[i],TRIPLE_FIG.BEAR)) return n[i]; for (i=0; i < n.length; i++) { if (isLegalMove(n[i],TRIPLE_FIG.BEAR)) return n[i]; } return null; } function setConfig (newConfig) { if (!config) config = defaultConfig; if (newConfig) config = Object.assign(config, newConfig); _enableLogger(config.logger); board = new Board( config.board_size.x, config.board_size.y ); if (newConfig.board) board.setString(newConfig.board); if (newConfig.state) setState(newConfig.state); emit('setconfig', {config: config.name}); } function getConfig () { emit('getconfig'); // config.board = board.toString(); return config; } function getState () { emit('getstate'); var s = Object.assign({},state); s.board = board.toString(); return s; } function setState (newState) { emit('setstate'); for (let key in state) if (newState[key] !== undefined) state[key] = newState[key]; if (newState.board) board.setString(newState.board); } // save game to bak function saveGame() { emit('saveundo'); bak = getState(); state.undo_enable = true; } // restore game from bak function restoreGame() { if(!bak) return; emit('undo'); undo_fig = state.next; setState(bak); bak = null; state.undo_enable = false; } // swapReserve() swap reserve and next figure function _swapReserve() { var f = state.reserve; state.reserve = state.next; state.next = f; if(state.next === TRIPLE_FIG.EMPTY) state.next = newNextFigure(); emit('swapreserve'); } // newGame() start new game function _newGame( aConfig = config ) { emit('newgame'); setConfig( aConfig ); emit('nextfigure', {fig: state.next}); // set initial random figures var count = config.initial_count; for(let i=0; i < count; ++i) { let x = Math.floor(Math.random() * board.cols); let y = Math.floor(Math.random() * board.rows); let f = newFigure(config.initial_figures); setFigure(x,y,f); }; if (!state.next) state.next = newNextFigure(); } // return true if fig can set at x,y function isLegalMove(pos, fig) { if(!fig) fig = state.next; if(!board.validPos(pos)) return false; var boardFig = board.getPos(pos); if ((boardFig === TRIPLE_FIG.TREASURE) || (boardFig === TRIPLE_FIG.BIGTREASURE)) return true; if(boardFig === TRIPLE_FIG.EMPTY) return fig !== TRIPLE_FIG.ROBOT; return fig === TRIPLE_FIG.ROBOT; } function addFigure ( fig ) { undo_fig = state.next; state.next = fig; } // set figure at x,y and return score function setFigure(x,y,f) { var score = figureProp(f).score; var pos = new Pos(x,y); setBoardPos(pos, f, score); var growto = figureProp(f).growto; while (growto) { var l = getPartners(x, y, f); if (l.length >= 3) { l.forEach(function(v) { setBoardPos(v, TRIPLE_FIG.EMPTY); }); var growscore = figureProp(growto).score; if (l.length > 3) growscore += growscore; score += growscore; setBoardPos(pos, growto, score); f = growto; growto = figureProp(f).growto; } else growto = null; } return score; } function getPartners( x, y, f ) { return board.getCluster( new Pos(x,y), f); } // return figure as result of a crystal function getCrystalFigure(pos) { var result = TRIPLE_FIG.ROCK; var max = 1; var neighbours = board.neighbours(pos); neighbours.forEach(function(p) { var fig = board.getPos(p); if((fig !== TRIPLE_FIG.EMPTY) && (fig !== TRIPLE_FIG.BEAR)) { var cnt = board.getCluster( pos, fig ).length; if (cnt >= 3) { cnt = cnt * figureProp(fig).score; if(cnt > max) { max = cnt; result = fig; } } } }); return result; } function setBoardPos(pos, fig, score) { board.setPos(pos, fig); emit_setfigure(fig, pos, (score || 0)); } // return index of {x,y} object pos in array a function indexOfPos( a, pos ) { if((!a) || (a.length === 0)) return -1; for(var i = 0; i < a.length; ++i) { if( (a[i].x === pos.x) && (a[i].y === pos.y) ) return i; } return -1; } // generate next figure function newFigure (list) { if (!list || list.length === 0) return 0; var prob = list.map( x => x[1] ); var i = percentageRandom(prob,true); return list[i][0]; } function newNextFigure () { var result; if (undo_fig >= 0) { result = undo_fig; undo_fig = -1; } else result = newFigure(config.next_figures); return result; } // return game as string function _toString() { var result = ''; result += board.toStringFramed(); result += 'next: ' + state.next + ' reserve: ' + state.reserve + ' score: ' + state.score + ' gold: ' + state.gold + '\n'; return result; } // ------------------------ // event interface function _on( event, callback) { events[event] = callback; } function emit( event, data ) { if (!events[event] && !events.all) return; if (!data) data = {}; data.event = event; if (events[event]) events[event]( data ); if (events.all) events.all( data ); } function emit_setfigure (fig, pos, score ) { emit('setfigure', {fig: fig, pos: pos, score: (score || 0)}); } function emit_movefigure (fig, fromPos, toPos) { emit('movefigure', {fig: fig, pos: fromPos, newpos: toPos}); } function _enableLogger (val=true) { if (!val) { events = {}; return; } if (Array.isArray(val)) { val.forEach( e => _on(e, game_logger)); return; } _on('all', game_logger); } }; // ------------------------------------------------ function gameEventToString (data) { function figStr (fig) { var p = figureProp(fig); if (p) return p.name + ' (' + fig + ')'; return fig; } function posStr (pos) { return '['+pos.x+','+pos.y+']'; } function kvStr (k,v) { return '' + k + ': ' + v; } var a = []; for (var key in data) { switch (key) { case 'event': break; case 'fig': a.push(kvStr(key, figStr(data.fig))); break; case 'pos': case 'newpos': a.push(kvStr(key, posStr(data[key]))); break; default: a.push(kvStr(key, data[key])); } } return data.event + ' ' + a.join(', '); } function game_logger (data, callback) { console.log('game-logger: ', gameEventToString(data)); // console.log('game-logger: ', data.message); }; // node.js export if(module) { module.exports = { TripleGame: TripleGame }; }; /* if(module) { var exports = module.exports = {}; exports.game = function(size) { return new TripleGame(size); }; exports.board = function(size) { return new Board(size, size); }; exports.TRIPLE_FIG = TRIPLE_FIG; exports.Pos = Pos; exports.Board = Board; }; */ },{"./board.js":1,"./config.js":2,"./figures.js":5,"./utils":9}],8:[function(require,module,exports){ /* */ module.exports = { getProp: getProp, checkProp: checkProp }; function getProp (dst, src, name, deflt) { if (!src || !dst || !name) return false; if (src[name] === undefined) { if (deflt !== undefined) dst[name] = deflt; return false; } dst[name] = src[name]; return true; } function checkProp (dst,name,deflt) { if (!dst || !name || !deflt || (dst[name] !== undefined)) return false; dst[name] = deflt; return true; } },{}],9:[function(require,module,exports){ module.exports.percentageRandom = require('./percentage-random.js'); module.exports.getProp = require('./get-prop.js').getProp; module.exports.checkProp = require('./get-prop.js').checkProp; },{"./get-prop.js":8,"./percentage-random.js":10}],10:[function(require,module,exports){ module.exports = percentageRandom; // percentageRandom (list [, roundup=false]) // list is a array of percent values like [10, 2.5, 2.5, 15, 40, 30] // is the sum of all array items is not 100%, call it with roundup=true // return value is a random array index, weight by percentage items function percentageRandom (list, roundup=false) { var r = Math.random() * 100; // random between 0 and 100 var factor = 1; // calculate factor to 100% if (roundup) { factor = 0; for (let i=0; i < list.length; i++) factor += list[i]; factor = 100 / factor; } // find random index for (let i=0; i < list.length; i++) if ((r -= (list[i] * factor)) < 0) return i; // rounding diff will result first item return 0; } function test_percentageRandom () { function run_test(list, roundup) { var runs = 1e6; var stat = new Array(list.length).fill(0); for (let i = 0; i < runs; i++) { ++stat[percentageRandom(list,roundup)]; } stat = stat.map(x => Math.floor(x / runs * 100)); console.log(list, ' > ', stat); } run_test([15, 15, 20, 50]); run_test([10, 10, 20, 50]); run_test([10, 10, 20, 50], true); run_test([10, 10, 20, 20], true); } },{}]},{},[6])(6) });