Last active
January 9, 2016 17:06
-
-
Save steren/6636508 to your computer and use it in GitHub Desktop.
Poker Tournament manager script to be used with Google Spreadsheet
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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