Poker Tournament manager script to be used with Google Spreadsheet
// Poker Tournament script | |
// by Steren Giannini http://www.steren.fr | |
// # How to use | |
// Use "=computePlayerScoreAndRank()" to generate a tournament ladder, as first argument select a column of player name, as second argument, select the cells of game | |
///////////// | |
// Variables | |
///////////// | |
// When participating, how much goes for this party? | |
var contribPrice = 7; | |
// When participating, how much goes for the pot? | |
var potPrice = 1; | |
// How much does a bounty represent as a gain | |
var bountyGain = 2; | |
// How much total money should you put to go into a party | |
var buyInPrice = contribPrice + potPrice + bountyGain; | |
// Coefficients used in the point computation, for 3rd place, use pointCoefs[3] | |
var pointCoefs = [ | |
0, | |
5, | |
4, | |
3, | |
2.5, | |
2.25, | |
2, | |
1.75, | |
1.5, | |
1.25 | |
]; | |
// coefficients used in the gain coefficient, for 3rd place, use gainCoefs[3] | |
var gainCoefs = [ | |
0, | |
contribPrice / 2, | |
2 * contribPrice / 7, | |
2 * contribPrice / 13 | |
]; | |
// Number under which, every game will count in the final score, above this number of playes games, score is ajusted. | |
var playedGamesNonAjustedLimit = 8; | |
//////////// | |
// Functions | |
//////////// | |
/** | |
* From a player position, return this player's score. | |
* @param position: number of the position in the game | |
* @param totalPlayers: the total player number in the game | |
*/ | |
function computePointsFromPosition(position, totalPlayers) { | |
var points; | |
if(position > pointCoefs.length - 1) { | |
points = Math.round(totalPlayers + 1 - position); | |
} else { | |
points = Math.round(pointCoefs[position] * (totalPlayers - position + 1)); | |
} | |
return points; | |
} | |
/** | |
* Compute real gain for a player, considering money won (Ladder + bounties) and money spent | |
*/ | |
function computeGainsFromPosition(position, totalPlayers) { | |
var gain = 0; | |
if (position < gainCoefs.length) { | |
gain = Math.round( totalPlayers * gainCoefs[position] ); | |
} else if (position == gainCoefs.length) { // the last paid person wins the rest. | |
gain = totalPlayers * contribPrice - Math.round(totalPlayers * gainCoefs[1]) - Math.round(totalPlayers * gainCoefs[2]) - Math.round(totalPlayers * gainCoefs[3]); | |
} | |
return gain; | |
} | |
/** | |
* for every game, count the number of players | |
*/ | |
function computePlayerPerGames(positions) { | |
// store the number of player for one game | |
var playerPerGames = []; | |
// for every game | |
for(var game = 0; game < positions[0].length; game++) { | |
playerPerGames[game] = 0; | |
// for every player | |
for(var player = 0; player < positions.length; player++) { | |
if( positions[player][game] !== "") { | |
playerPerGames[game]++; | |
} | |
} | |
} | |
return playerPerGames; | |
} | |
/** | |
* Based on number of player per games, compute the real number of games | |
*/ | |
function computeGameNumber(playerPerGames) { | |
var gameNumber = 0; | |
for(var i = 0; i < playerPerGames.length; i++) { | |
if(playerPerGames[i]) { | |
gameNumber++; | |
} | |
} | |
return gameNumber; | |
} | |
/** | |
* Compute all results for the given player | |
* @param playerPositions: an array containing the positions of this player for each game | |
* @param playerPerGames: an array containing the total number of player per games (result of computePlayerPerGames() ) | |
* @param playerBounties: bounties for this player. | |
* @param gameNumber: Total of games with more than 0 player (in order to remove empty game columns) | |
* @return object containing all results for the given player. | |
*/ | |
function computeForPlayer(playerPositions, playerPerGames, playerBounties, gameNumber) { | |
var result = {} | |
var totalScore = 0; | |
var totalGain = 0; | |
// Number of time a player has win the game | |
var totalWin = 0; | |
// Number of time a player has had a gain > 0 | |
var totalITM = 0; | |
// Number of played games for the given player | |
var totalPlayedGames = 0; | |
// Number of time a player has received a bounty from another player | |
var bountyNumberForPlayer = 0; | |
// Average position of this player | |
var averagePosition = 0; | |
// Number of time a player has finished last in the game | |
var lostNumberForPlayer = 0; | |
for(var i = 0; i < gameNumber; i++) { | |
if( playerPositions[i] !== "" ) { | |
totalScore += computePointsFromPosition(playerPositions[i], playerPerGames[i]); | |
var playerGameGain = computeGainsFromPosition(playerPositions[i], playerPerGames[i]); | |
totalGain += playerGameGain; | |
if (playerGameGain > 0) { | |
totalITM++; | |
} | |
totalPlayedGames++; | |
averagePosition += playerPositions[i]; | |
if( playerPositions[i] === playerPerGames[i]) { | |
lostNumberForPlayer++; | |
} | |
} | |
if( playerPositions[i] === 1 ) { | |
totalWin++; | |
} | |
if( playerBounties[i] !== "" ) { | |
bountyNumberForPlayer = bountyNumberForPlayer + playerBounties[i]; | |
} | |
} | |
// Now we potentially adjust the totalScore for players who played too many games. | |
// (Previously used formula) | |
//var maxCountedGames = Math.floor(gameNumber * 3 / 4); | |
//var averageScore = totalScore / totalPlayedGames; | |
//if(totalPlayedGames > playedGamesNonAjustedLimit && totalPlayedGames > maxCountedGames){ | |
// totalScore = totalScore - (totalPlayedGames - maxCountedGames) * averageScore; | |
//} | |
var averageScore = totalScore / totalPlayedGames; | |
if(totalPlayedGames > playedGamesNonAjustedLimit){ | |
totalScore = totalScore - (totalPlayedGames - playedGamesNonAjustedLimit) * averageScore / 3; | |
} | |
averagePosition = totalPlayedGames === 0 ? 0 : averagePosition / totalPlayedGames; | |
result.totalScore = totalScore; | |
result.totalGain = totalGain; | |
result.totalITM = totalITM; | |
result.totalWin = totalWin; | |
result.totalPlayedGames = totalPlayedGames; | |
result.bountyNumberForPlayer = bountyNumberForPlayer; | |
result.averagePosition = averagePosition; | |
result.lostNumberForPlayer = lostNumberForPlayer; | |
return result; | |
} | |
/** | |
* Main entry point | |
* Compute for the scores of every given players, based on all their positions | |
* @param playerNames: range of cell containing the player names. | |
* @param positions: range double array, one row per player, in columns: the positions and bounties for one game (one game = 2 consecutive columns). | |
* @return: a sorted multi columns array with: the player name as first column, other data as next columns | |
*/ | |
function computePlayerScoreAndRank(playerNames, positionsAndBounties) { | |
// playerNames is a [][], we need to transform it to an array. | |
var playerNamesArray = []; | |
for(var p = 0; p < playerNames.length; p++) { | |
playerNamesArray.push(playerNames[p][0]); | |
} | |
// split the big game array in two smaller arrays, one containing only the player's positions and the other containing the player's bounties | |
var positions = []; | |
var bounties = []; | |
for(var player = 0; player < positionsAndBounties.length; player++) { | |
positions[player] = []; | |
bounties[player] = []; | |
for(var game = 0; game < positionsAndBounties[player].length / 2; game++) { | |
positions[player][game] = positionsAndBounties[player][2 * game]; | |
bounties[player][game] = positionsAndBounties[player][2 * game + 1]; | |
} | |
} | |
var playerPerGames = computePlayerPerGames(positions); | |
var gameNumber = computeGameNumber(playerPerGames); | |
var playerScores = []; | |
var playerAverage = []; | |
var playerITMs = []; | |
var playerWins = []; | |
var playerLasts = []; | |
var playerGains = []; | |
var playerBounties = []; | |
var playerPlayedGames = []; | |
var playerRes; | |
// for every player, compute his score and other data | |
for(var player = 0; player < positions.length; player++) { | |
playerRes = computeForPlayer(positions[player], playerPerGames, bounties[player], gameNumber); | |
playerScores[player] = playerRes.totalScore; | |
playerWins[player] = playerRes.totalWin; | |
playerITMs[player] = playerRes.totalITM; | |
playerLasts[player] = playerRes.lostNumberForPlayer; | |
playerBounties[player] = playerRes.bountyNumberForPlayer; | |
playerPlayedGames[player] = playerRes.totalPlayedGames; | |
//Total gain is gain + bounties - buy ins | |
playerGains[player] = playerRes.totalGain + playerBounties[player] * bountyGain - playerPlayedGames[player] * buyInPrice | |
} | |
var result = arrayTranspose([playerNamesArray, playerPlayedGames, playerScores, playerWins, playerITMs, playerLasts, playerGains, playerBounties]); | |
var sortFunction = function (a, b) { | |
if (a[2] > b[2]) | |
return 1; | |
if (a[2] < b[2]) | |
return -1; | |
// a must be equal to b | |
return 0; | |
}; | |
// now time to sort everything, we write a custom sorting function that taks into account the score | |
return result.sort(sortFunction).reverse(); | |
} | |
/** | |
* Given a JavaScript 2d Array, this function returns the transposed table. | |
* Example: arrayTranspose([[1,2,3],[4,5,6]]) returns [[1,4],[2,5],[3,6]]. | |
* @param data: JavaScript 2d Array | |
* @return a JavaScript 2d Array | |
*/ | |
function arrayTranspose(data) { | |
if (data.length == 0 || data[0].length == 0) { | |
return null; | |
} | |
var ret = []; | |
for (var i = 0; i < data[0].length; ++i) { | |
ret.push([]); | |
} | |
for (var i = 0; i < data.length; ++i) { | |
for (var j = 0; j < data[i].length; ++j) { | |
ret[j][i] = data[i][j]; | |
} | |
} | |
return ret; | |
} | |
/////////// | |
// TESTS // | |
/////////// | |
/** Check tests results by displaying the Log view (View > Logs) */ | |
function test() { | |
Logger.log("Running tests"); | |
Logger.log("Test compute player per games:"); | |
var scores = computePlayerPerGames([[1,3,2], [2,2,""], ["",1,1]]); | |
if( scores[0] !== 2 | |
|| scores[1] !== 3 | |
|| scores[2] !== 2 ) { | |
Logger.log("FAIL"); | |
} else { | |
Logger.log("OK"); | |
} | |
Logger.log("Test point per position"); | |
if(computePointsFromPosition(1, 19) == 95) { Logger.log("OK"); } else { Logger.log("FAIL"); } | |
if(computePointsFromPosition(2, 14) == 52) { Logger.log("OK"); } else { Logger.log("FAIL"); } | |
if(computePointsFromPosition(6, 12) == 14) { Logger.log("OK"); } else { Logger.log("FAIL"); } | |
if(computePointsFromPosition(11, 15) == 5) { Logger.log("OK"); } else { Logger.log("FAIL"); } | |
if(computePointsFromPosition(4, 20) == 43) { Logger.log("OK"); } else { Logger.log("FAIL"); } | |
Logger.log("Test gain per position"); | |
if(computeGainsFromPosition(1, 19) == 67) { Logger.log("OK"); } else { Logger.log("FAIL"); } | |
if(computeGainsFromPosition(2, 14) == 28) { Logger.log("OK"); } else { Logger.log("FAIL"); } | |
if(computeGainsFromPosition(6, 12) == 0) { Logger.log("OK"); } else { Logger.log("FAIL"); } | |
if(computeGainsFromPosition(11, 15) == 0) { Logger.log("OK"); } else { Logger.log("FAIL"); } | |
if(computeGainsFromPosition(4, 20) == 8) { Logger.log("OK"); } else { Logger.log("FAIL"); } | |
} | |
/** calls computePlayerScoreAndRank() on current active sheet */ | |
function triggerMainFunctionOnRealSheet() { | |
var playerNamesRange = "A6:A40"; | |
var positionsAndBountiesRange = "B6:AM40"; | |
var testSheet = SpreadsheetApp.getActive(); | |
Logger.log("testing on current sheet: " + testSheet.getSheetName()); | |
var result = computePlayerScoreAndRank(testSheet.getRange(playerNamesRange).getValues(), testSheet.getRange(positionsAndBountiesRange).getValues()); | |
Logger.log(result); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment