From affc103e88eb9e098f250b266c1fd3c1e613600c Mon Sep 17 00:00:00 2001 From: Queercat Date: Thu, 9 Apr 2020 13:49:06 -0700 Subject: [PATCH 01/12] Added pack statistics generation to bot.js. --- backend/bot.js | 111 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) diff --git a/backend/bot.js b/backend/bot.js index b3b89c23..533abe0c 100644 --- a/backend/bot.js +++ b/backend/bot.js @@ -1,6 +1,115 @@ const {sample, pull} = require("lodash"); const Player = require("./player"); +const CardColors = { + "W": 0, + "U": 1, + "B": 2, + "R": 3, + "G": 4, + "C": 5 +}; + +const CardColorNames = { + "White": 0, + "Blue": 1, + "Black": 2, + "Red": 3, + "Green": 4, + "Colorless": 5, +} + +/** + * @desc eatColorPips ... Separates out the colored pips {4}{W} --> W for bias analysis. + * @param {String} manaCost ... String of the manacost with generic and colored costs. + * @returns {Array} ... Returns an array of the color pip bias. + */ +function eatColorPips(manaCost) { + colorBias = [0, 0, 0, 0, 0]; + + /** + * Eventually will replace this with a proper RegEX. + */ + + manaCost = manaCost.split("{").join(""); + manaCost = manaCost.split("}").join("") + manaCost = manaCost.split("X").join("") + manaCost = manaCost.split("/").join("") + + for (var number = 0; number <= 9; number++) { + manaCost = manaCost.split(number).join(""); + } + + for (var char = 0; char < manaCost.length; char++) { + colorBias[CardColors[manaCost.charAt(char)]]++; + } + + return colorBias; +} + +/** + * @desc generatePackStats(pack) ... Examines and create an object for the statistics of a pack. + * @param {object} pack ... An object containing the name, UUID, CMC, and other relevant information about the pack. + * @returns {object} ... Returns an object containing information about the pack. + */ +function generatePackStats(packs) { + // colorBias, an array from [0, 1] reference enum CardColors, each pack is weighted by color. + var colorBias = [0, 0, 0, 0, 0, 0]; + var colorPipBias = [0, 0, 0, 0, 0]; + var typeBias = {}; + + var cmcBias = 0; + var totalCount = packs.length; + var nonLandCount = 0; + + for (var pack in packs) { + // packObj used to make my life easier regarding referencing. + var packObj = packs[pack]; + + var manaCost = packObj.manaCost; + var type = packObj.type; + var color = packObj.color; + var CMC = packObj.cmc; + + if (type != "Land") { + nonLandCount++; + colorBias[CardColorNames[color]]++; + + if (CMC > 0) { + cmcBias += CMC; + } + + if (color != "Colorless") { + newColorBias = eatColorPips(manaCost); + + for (var val = 0; val < colorPipBias.length; val++) { + colorPipBias[val] += newColorBias[val]; + } + } + } + + typeBias[type] = (typeBias[type] || 0) + 1; // Increase the number for whatever type it is or initialize the value. + } + + for (var val = 0; val < colorPipBias.length; val++) { + colorPipBias[val] /= nonLandCount; + } + + for (var val = 0; val < colorBias.length; val++) { + colorBias[val] /= nonLandCount; + } + + cmcBias /= nonLandCount; + + for (var type in typeBias) { + typeBias[type] /= totalCount; + } + + var packStats = {"colorBias": colorBias, "colorPipBias": colorPipBias, "typeBias": typeBias, "cmcBias": cmcBias} + + return packStats; +} + module.exports = class extends Player { constructor() { super({ @@ -12,6 +121,8 @@ module.exports = class extends Player { } getPack(pack) { + //var stats = generatePackStats(pack); + const randomPick = sample(pack); this.picks.push(randomPick.name); pull(pack, randomPick); From 4590b0299e0421b7f50a906d47eb52792676e094 Mon Sep 17 00:00:00 2001 From: May Date: Sat, 11 Apr 2020 14:34:19 -0700 Subject: [PATCH 02/12] Initial commit, created analytics.js. --- backend/analytics.js | 108 ++++++++++++++++++++++++++++++++++++++++++ backend/bot.js | 109 ------------------------------------------- 2 files changed, 108 insertions(+), 109 deletions(-) create mode 100644 backend/analytics.js diff --git a/backend/analytics.js b/backend/analytics.js new file mode 100644 index 00000000..4c9e1e0e --- /dev/null +++ b/backend/analytics.js @@ -0,0 +1,108 @@ +const CardColors = { + "W": 0, + "U": 1, + "B": 2, + "R": 3, + "G": 4, + "C": 5 + }; + + const CardColorNames = { + "White": 0, + "Blue": 1, + "Black": 2, + "Red": 3, + "Green": 4, + "Colorless": 5, + } + + /** + * @desc eatColorPips ... Separates out the colored pips {4}{W} --> W for bias analysis. + * @param {String} manaCost ... String of the manacost with generic and colored costs. + * @returns {Array} ... Returns an array of the color pip bias. + */ + function eatColorPips(manaCost) { + colorBias = [0, 0, 0, 0, 0]; + + /** + * Eventually will replace this with a proper RegEX. + */ + + manaCost = manaCost.split("{").join(""); + manaCost = manaCost.split("}").join("") + manaCost = manaCost.split("X").join("") + manaCost = manaCost.split("/").join("") + + for (var number = 0; number <= 9; number++) { + manaCost = manaCost.split(number).join(""); + } + + for (var char = 0; char < manaCost.length; char++) { + colorBias[CardColors[manaCost.charAt(char)]]++; + } + + return colorBias; + } + + /** + * @desc generatePackStats(pack) ... Examines and create an object for the statistics of a pack. + * @param {object} pack ... An object containing the name, UUID, CMC, and other relevant information about the pack. + * @returns {object} ... Returns an object containing information about the pack. + */ + function generatePackStats(packs) { + // colorBias, an array from [0, 1] reference enum CardColors, each pack is weighted by color. + var colorBias = [0, 0, 0, 0, 0, 0]; + var colorPipBias = [0, 0, 0, 0, 0]; + var typeBias = {}; + + var cmcBias = 0; + var totalCount = packs.length; + var nonLandCount = 0; + + for (var pack in packs) { + // packObj used to make my life easier regarding referencing. + var packObj = packs[pack]; + + var manaCost = packObj.manaCost; + var type = packObj.type; + var color = packObj.color; + var CMC = packObj.cmc; + + if (type != "Land") { + nonLandCount++; + colorBias[CardColorNames[color]]++; + + if (CMC > 0) { + cmcBias += CMC; + } + + if (color != "Colorless") { + newColorBias = eatColorPips(manaCost); + + for (var val = 0; val < colorPipBias.length; val++) { + colorPipBias[val] += newColorBias[val]; + } + } + } + + typeBias[type] = (typeBias[type] || 0) + 1; // Increase the number for whatever type it is or initialize the value. + } + + for (var val = 0; val < colorPipBias.length; val++) { + colorPipBias[val] /= nonLandCount; + } + + for (var val = 0; val < colorBias.length; val++) { + colorBias[val] /= nonLandCount; + } + + cmcBias /= nonLandCount; + + for (var type in typeBias) { + typeBias[type] /= totalCount; + } + + var packStats = {"colorBias": colorBias, "colorPipBias": colorPipBias, "typeBias": typeBias, "cmcBias": cmcBias} + + return packStats; + } \ No newline at end of file diff --git a/backend/bot.js b/backend/bot.js index 533abe0c..cd467568 100644 --- a/backend/bot.js +++ b/backend/bot.js @@ -1,115 +1,6 @@ const {sample, pull} = require("lodash"); const Player = require("./player"); -const CardColors = { - "W": 0, - "U": 1, - "B": 2, - "R": 3, - "G": 4, - "C": 5 -}; - -const CardColorNames = { - "White": 0, - "Blue": 1, - "Black": 2, - "Red": 3, - "Green": 4, - "Colorless": 5, -} - -/** - * @desc eatColorPips ... Separates out the colored pips {4}{W} --> W for bias analysis. - * @param {String} manaCost ... String of the manacost with generic and colored costs. - * @returns {Array} ... Returns an array of the color pip bias. - */ -function eatColorPips(manaCost) { - colorBias = [0, 0, 0, 0, 0]; - - /** - * Eventually will replace this with a proper RegEX. - */ - - manaCost = manaCost.split("{").join(""); - manaCost = manaCost.split("}").join("") - manaCost = manaCost.split("X").join("") - manaCost = manaCost.split("/").join("") - - for (var number = 0; number <= 9; number++) { - manaCost = manaCost.split(number).join(""); - } - - for (var char = 0; char < manaCost.length; char++) { - colorBias[CardColors[manaCost.charAt(char)]]++; - } - - return colorBias; -} - -/** - * @desc generatePackStats(pack) ... Examines and create an object for the statistics of a pack. - * @param {object} pack ... An object containing the name, UUID, CMC, and other relevant information about the pack. - * @returns {object} ... Returns an object containing information about the pack. - */ -function generatePackStats(packs) { - // colorBias, an array from [0, 1] reference enum CardColors, each pack is weighted by color. - var colorBias = [0, 0, 0, 0, 0, 0]; - var colorPipBias = [0, 0, 0, 0, 0]; - var typeBias = {}; - - var cmcBias = 0; - var totalCount = packs.length; - var nonLandCount = 0; - - for (var pack in packs) { - // packObj used to make my life easier regarding referencing. - var packObj = packs[pack]; - - var manaCost = packObj.manaCost; - var type = packObj.type; - var color = packObj.color; - var CMC = packObj.cmc; - - if (type != "Land") { - nonLandCount++; - colorBias[CardColorNames[color]]++; - - if (CMC > 0) { - cmcBias += CMC; - } - - if (color != "Colorless") { - newColorBias = eatColorPips(manaCost); - - for (var val = 0; val < colorPipBias.length; val++) { - colorPipBias[val] += newColorBias[val]; - } - } - } - - typeBias[type] = (typeBias[type] || 0) + 1; // Increase the number for whatever type it is or initialize the value. - } - - for (var val = 0; val < colorPipBias.length; val++) { - colorPipBias[val] /= nonLandCount; - } - - for (var val = 0; val < colorBias.length; val++) { - colorBias[val] /= nonLandCount; - } - - cmcBias /= nonLandCount; - - for (var type in typeBias) { - typeBias[type] /= totalCount; - } - - var packStats = {"colorBias": colorBias, "colorPipBias": colorPipBias, "typeBias": typeBias, "cmcBias": cmcBias} - - return packStats; -} - module.exports = class extends Player { constructor() { super({ From 4a5a57ab25d9c38fdcf6d1e564f05125c62c9d23 Mon Sep 17 00:00:00 2001 From: May Date: Sat, 11 Apr 2020 14:34:38 -0700 Subject: [PATCH 03/12] Removed bot.js old analytics stuff. --- backend/bot.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/backend/bot.js b/backend/bot.js index cd467568..b3b89c23 100644 --- a/backend/bot.js +++ b/backend/bot.js @@ -12,8 +12,6 @@ module.exports = class extends Player { } getPack(pack) { - //var stats = generatePackStats(pack); - const randomPick = sample(pack); this.picks.push(randomPick.name); pull(pack, randomPick); From 601593cc5ece9d0ffdd091b48e0f9f03ec901aa9 Mon Sep 17 00:00:00 2001 From: May Date: Sat, 11 Apr 2020 14:50:54 -0700 Subject: [PATCH 04/12] Fixed analytics indentation. --- backend/analytics.js | 205 +++++++++++++++++++++---------------------- 1 file changed, 102 insertions(+), 103 deletions(-) diff --git a/backend/analytics.js b/backend/analytics.js index 4c9e1e0e..d393752d 100644 --- a/backend/analytics.js +++ b/backend/analytics.js @@ -1,108 +1,107 @@ const CardColors = { - "W": 0, - "U": 1, - "B": 2, - "R": 3, - "G": 4, - "C": 5 - }; - - const CardColorNames = { - "White": 0, - "Blue": 1, - "Black": 2, - "Red": 3, - "Green": 4, - "Colorless": 5, + "W": 0, + "U": 1, + "B": 2, + "R": 3, + "G": 4, + "C": 5 +}; + +const CardColorNames = { + "White": 0, + "Blue": 1, + "Black": 2, + "Red": 3, + "Green": 4, + "Colorless": 5, +}; + +/** + * @desc eatColorPips ... Separates out the colored pips {4}{W} --> W for bias analysis. + * @param {String} manaCost ... String of the manacost with generic and colored costs. + * @returns {Array} ... Returns an array of the color pip bias. + */ +function eatColorPips(manaCost) { + var colorBias = [0, 0, 0, 0, 0]; + + const toBeRemoved = ["{", "}", "X", "/"] + + for (var item in toBeRemoved) { + anaCost = manaCost.split(toBeRemoved[item]).join(""); } - - /** - * @desc eatColorPips ... Separates out the colored pips {4}{W} --> W for bias analysis. - * @param {String} manaCost ... String of the manacost with generic and colored costs. - * @returns {Array} ... Returns an array of the color pip bias. - */ - function eatColorPips(manaCost) { - colorBias = [0, 0, 0, 0, 0]; - - /** - * Eventually will replace this with a proper RegEX. - */ - - manaCost = manaCost.split("{").join(""); - manaCost = manaCost.split("}").join("") - manaCost = manaCost.split("X").join("") - manaCost = manaCost.split("/").join("") - - for (var number = 0; number <= 9; number++) { - manaCost = manaCost.split(number).join(""); - } - - for (var char = 0; char < manaCost.length; char++) { - colorBias[CardColors[manaCost.charAt(char)]]++; - } - - return colorBias; + + for (var number = 0; number <= 9; number++) { + manaCost = manaCost.split(number).join(""); } - - /** - * @desc generatePackStats(pack) ... Examines and create an object for the statistics of a pack. - * @param {object} pack ... An object containing the name, UUID, CMC, and other relevant information about the pack. - * @returns {object} ... Returns an object containing information about the pack. - */ - function generatePackStats(packs) { - // colorBias, an array from [0, 1] reference enum CardColors, each pack is weighted by color. - var colorBias = [0, 0, 0, 0, 0, 0]; - var colorPipBias = [0, 0, 0, 0, 0]; - var typeBias = {}; - - var cmcBias = 0; - var totalCount = packs.length; - var nonLandCount = 0; - - for (var pack in packs) { - // packObj used to make my life easier regarding referencing. - var packObj = packs[pack]; - - var manaCost = packObj.manaCost; - var type = packObj.type; - var color = packObj.color; - var CMC = packObj.cmc; - - if (type != "Land") { - nonLandCount++; - colorBias[CardColorNames[color]]++; - - if (CMC > 0) { - cmcBias += CMC; - } - - if (color != "Colorless") { - newColorBias = eatColorPips(manaCost); - - for (var val = 0; val < colorPipBias.length; val++) { - colorPipBias[val] += newColorBias[val]; - } - } + + for (var char = 0; char < manaCost.length; char++) { + colorBias[CardColors[manaCost.charAt(char)]]++; + } + + return colorBias; +} + +/** + * @desc generatePackStats(pack) ... Examines and create an object for the statistics of a pack. + * @param {object} pack ... An object containing the name, UUID, CMC, and other relevant information about the pack. + * @returns {object} ... Returns an object containing information about the pack. + */ +function generatePackStats(packs) { + // colorBias, an array from [0, 1] reference enum CardColors, each pack is weighted by color. + var colorBias = [0, 0, 0, 0, 0, 0]; + var colorPipBias = [0, 0, 0, 0, 0]; + var typeBias = {}; + + var cmcBias = 0; + var totalCount = packs.length; + var nonLandCount = 0; + + for (var pack in packs) { + // packObj used to make my life easier regarding referencing. + var packObj = packs[pack]; + + var manaCost = packObj.manaCost; + var type = packObj.type; + var color = packObj.color; + var CMC = packObj.cmc; + + if (type != "Land") { + nonLandCount++; + colorBias[CardColorNames[color]]++; + + if (CMC > 0) { + cmcBias += CMC; } - - typeBias[type] = (typeBias[type] || 0) + 1; // Increase the number for whatever type it is or initialize the value. - } - - for (var val = 0; val < colorPipBias.length; val++) { - colorPipBias[val] /= nonLandCount; - } - - for (var val = 0; val < colorBias.length; val++) { - colorBias[val] /= nonLandCount; - } - - cmcBias /= nonLandCount; - - for (var type in typeBias) { - typeBias[type] /= totalCount; + + if (color != "Colorless") { + newColorBias = eatColorPips(manaCost); + + for (var val = 0; val < colorPipBias.length; val++) { + colorPipBias[val] += newColorBias[val]; + } + } } - - var packStats = {"colorBias": colorBias, "colorPipBias": colorPipBias, "typeBias": typeBias, "cmcBias": cmcBias} - - return packStats; - } \ No newline at end of file + + typeBias[type] = (typeBias[type] || 0) + 1; // Increase the number for whatever type it is or initialize the value. + } + + for (var val = 0; val < colorPipBias.length; val++) { + colorPipBias[val] /= nonLandCount; + } + + for (var val = 0; val < colorBias.length; val++) { + colorBias[val] /= nonLandCount; + } + + cmcBias /= nonLandCount; + + for (var type in typeBias) { + typeBias[type] /= totalCount; + } + + var packStats = {"colorBias": colorBias, "colorPipBias": colorPipBias, "typeBias": typeBias, "cmcBias": cmcBias} + + return packStats; +} + +module.exports = generatePackStats; \ No newline at end of file From a0770ebd1d7ed8d2e52ad793070d4980f65ca607 Mon Sep 17 00:00:00 2001 From: May Date: Sat, 11 Apr 2020 15:36:27 -0700 Subject: [PATCH 05/12] Added rarity bias and fixed color pip bias. --- backend/analytics.js | 24 ++++++++++++++++++------ backend/bot.js | 3 +++ 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/backend/analytics.js b/backend/analytics.js index d393752d..bf82d260 100644 --- a/backend/analytics.js +++ b/backend/analytics.js @@ -22,12 +22,13 @@ const CardColorNames = { * @returns {Array} ... Returns an array of the color pip bias. */ function eatColorPips(manaCost) { - var colorBias = [0, 0, 0, 0, 0]; + var colorBias = [0, 0, 0, 0, 0, 0]; // The last value refers to the total number of color pips. - const toBeRemoved = ["{", "}", "X", "/"] + // Symbols to be removed from card mana costs. + const toBeRemoved = ["{", "}", "X", "/"]; for (var item in toBeRemoved) { - anaCost = manaCost.split(toBeRemoved[item]).join(""); + manaCost = manaCost.split(toBeRemoved[item]).join(""); } for (var number = 0; number <= 9; number++) { @@ -36,6 +37,7 @@ function eatColorPips(manaCost) { for (var char = 0; char < manaCost.length; char++) { colorBias[CardColors[manaCost.charAt(char)]]++; + colorBias[colorBias.length - 1]++; // Increment the total number of color pips. } return colorBias; @@ -51,10 +53,12 @@ function generatePackStats(packs) { var colorBias = [0, 0, 0, 0, 0, 0]; var colorPipBias = [0, 0, 0, 0, 0]; var typeBias = {}; + var rarityBias = {}; var cmcBias = 0; var totalCount = packs.length; var nonLandCount = 0; + var colorPips = 0; for (var pack in packs) { // packObj used to make my life easier regarding referencing. @@ -62,6 +66,7 @@ function generatePackStats(packs) { var manaCost = packObj.manaCost; var type = packObj.type; + var rarity = packObj.rarity; var color = packObj.color; var CMC = packObj.cmc; @@ -74,19 +79,22 @@ function generatePackStats(packs) { } if (color != "Colorless") { - newColorBias = eatColorPips(manaCost); + var newColorBias = eatColorPips(manaCost); for (var val = 0; val < colorPipBias.length; val++) { colorPipBias[val] += newColorBias[val]; } + + colorPips += newColorBias[newColorBias.length - 1]; } } typeBias[type] = (typeBias[type] || 0) + 1; // Increase the number for whatever type it is or initialize the value. + rarityBias[rarity] = (rarityBias[rarity] || 0) + 1; } for (var val = 0; val < colorPipBias.length; val++) { - colorPipBias[val] /= nonLandCount; + colorPipBias[val] /= colorPips; } for (var val = 0; val < colorBias.length; val++) { @@ -99,7 +107,11 @@ function generatePackStats(packs) { typeBias[type] /= totalCount; } - var packStats = {"colorBias": colorBias, "colorPipBias": colorPipBias, "typeBias": typeBias, "cmcBias": cmcBias} + for (var rarity in rarityBias) { + rarityBias[rarity] /= totalCount; + } + + var packStats = {"colorBias": colorBias, "colorPipBias": colorPipBias, "typeBias": typeBias, "rarityBias": rarityBias, "cmcBias": cmcBias} return packStats; } diff --git a/backend/bot.js b/backend/bot.js index b3b89c23..262d2f2e 100644 --- a/backend/bot.js +++ b/backend/bot.js @@ -1,5 +1,6 @@ const {sample, pull} = require("lodash"); const Player = require("./player"); +const analyzePack = require("./analytics"); module.exports = class extends Player { constructor() { @@ -12,6 +13,8 @@ module.exports = class extends Player { } getPack(pack) { + console.log(analyzePack(pack)); + const randomPick = sample(pack); this.picks.push(randomPick.name); pull(pack, randomPick); From 2d3579605040829a081a8d4acffc8b49ce848f79 Mon Sep 17 00:00:00 2001 From: May Date: Sat, 11 Apr 2020 15:36:48 -0700 Subject: [PATCH 06/12] Working on specs for anayltics.js --- backend/analytics.spec.js | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 backend/analytics.spec.js diff --git a/backend/analytics.spec.js b/backend/analytics.spec.js new file mode 100644 index 00000000..8328be6b --- /dev/null +++ b/backend/analytics.spec.js @@ -0,0 +1,28 @@ +// List of potentially problematic cards to test over for pack analysis. +const NyleasIntervention = { + uuid: '576e9e04-acd7-5d24-bc19-1dd765e9d1b8', + name: "Purphoros's Intervention", + names: [], + color: 'Red', + colors: [ 'R' ], + colorIdentity: [ 'R' ], + setCode: 'THB', + scryfallId: 'ecc911ee-0e12-4b10-add7-9a9d63c29443', + cmc: 1, + number: '151', + type: 'Sorcery', + manaCost: '{X}{R}', + rarity: 'Rare', + url: 'https://api.scryfall.com/cards/ecc911ee-0e12-4b10-add7-9a9d63c29443?format=image', + layout: 'normal', + isDoubleFaced: false, + flippedCardURL: '', + supertypes: [], + subtypes: [], + text: 'Choose one —\n' + + '• Create an X/1 red Elemental creature token with trample and haste. Sacrifice it at the beginning of the next end step.\n' + + "• Purphoros's Intervention deals twice X damage to target creature or planeswalker.", + foil: true +}; + +const cardExamples = []; \ No newline at end of file From c4d0ac75ea01dc209f2455b9ed41e5d9f014c665 Mon Sep 17 00:00:00 2001 From: May Date: Sat, 11 Apr 2020 16:03:25 -0700 Subject: [PATCH 07/12] Create unit test code for analytics. --- backend/analytics.spec.js | 57 +++++++++++++++++++++++++++++++++++++-- backend/bot.js | 2 -- 2 files changed, 55 insertions(+), 4 deletions(-) diff --git a/backend/analytics.spec.js b/backend/analytics.spec.js index 8328be6b..00d6f1a0 100644 --- a/backend/analytics.spec.js +++ b/backend/analytics.spec.js @@ -1,5 +1,11 @@ +const {describe, it} = require("mocha"); +const assert = require("assert"); +const cardStats = require("./analytics.js"); +const boosterGenerator = require("./boosterGenerator"); +const {range} = require("lodash"); + // List of potentially problematic cards to test over for pack analysis. -const NyleasIntervention = { +const TestCard = { uuid: '576e9e04-acd7-5d24-bc19-1dd765e9d1b8', name: "Purphoros's Intervention", names: [], @@ -25,4 +31,51 @@ const NyleasIntervention = { foil: true }; -const cardExamples = []; \ No newline at end of file +/** + * @desc withinRange ... Returns a boolean whether or not a number is within range of a set tolerance +/-. + * @param {float} val + * @param {float} tolerance + * @returns {boolean} ... Is the value within range. + */ +function withinRange(val, expected, tolerance) { + return (val <= tolerance + expected && val >= tolerance - expected); +} + +var monoGreenTest = () => { + var arr = []; + + range(15).forEach(() => { + arr.append(TestCard); + }); +}; + +describe("Acceptance tests for card analytics generation", () => { + it("Should return the bias of a single card.", () => { + var stats = cardStats(monoGreenTest); + + assert(stats.colorBias == [0, 0, 0, 1, 0]); + assert(stats.colorPipBias == [0, 0, 0, 0, 1]); + assert(cmcBias == 2); + }); + + it("Should return statistics near 100% +/- 2", () => { + range(100).forEach(() => { + var randomBooster = boosterGenerator("MH1"); + var stats = cardStats(randomBooster); + + var colorBias = stats.colorBias; + var colorPipBias = statis.colorPipBias; + + var colorBiasPercentage = 0; + var colorPipBiasPercentage = 0; + + for (var val in colorBias) { + colorBiasPercentage += colorBias[val]; + colorPipBiasPercentage += colorBIas[val]; + } + + assert(withinRange(colorBiasPercentage, 1, 2)); + assert(withinRange(colorPipBiasPercentage, 1, 2)); + }); + }); +}); \ No newline at end of file diff --git a/backend/bot.js b/backend/bot.js index 262d2f2e..2c2d3589 100644 --- a/backend/bot.js +++ b/backend/bot.js @@ -13,8 +13,6 @@ module.exports = class extends Player { } getPack(pack) { - console.log(analyzePack(pack)); - const randomPick = sample(pack); this.picks.push(randomPick.name); pull(pack, randomPick); From 432bc7b6b14ecacbc4eb9843aa3ce6bccf57b2d0 Mon Sep 17 00:00:00 2001 From: May Date: Sat, 11 Apr 2020 17:17:25 -0700 Subject: [PATCH 08/12] Created unit testing for analytics. --- backend/analytics.js | 24 ++++++----- backend/analytics.spec.js | 89 +++++++++++++++++++++++++-------------- backend/bot.js | 1 - 3 files changed, 71 insertions(+), 43 deletions(-) diff --git a/backend/analytics.js b/backend/analytics.js index bf82d260..ab0bddae 100644 --- a/backend/analytics.js +++ b/backend/analytics.js @@ -14,6 +14,7 @@ const CardColorNames = { "Red": 3, "Green": 4, "Colorless": 5, + "Multicolor": 6 }; /** @@ -50,7 +51,7 @@ function eatColorPips(manaCost) { */ function generatePackStats(packs) { // colorBias, an array from [0, 1] reference enum CardColors, each pack is weighted by color. - var colorBias = [0, 0, 0, 0, 0, 0]; + var colorBias = [0, 0, 0, 0, 0, 0, 0]; var colorPipBias = [0, 0, 0, 0, 0]; var typeBias = {}; var rarityBias = {}; @@ -72,6 +73,7 @@ function generatePackStats(packs) { if (type != "Land") { nonLandCount++; + colorBias[CardColorNames[color]]++; if (CMC > 0) { @@ -93,25 +95,27 @@ function generatePackStats(packs) { rarityBias[rarity] = (rarityBias[rarity] || 0) + 1; } - for (var val = 0; val < colorPipBias.length; val++) { - colorPipBias[val] /= colorPips; + for (var pipVal = 0; pipVal < colorPipBias.length; pipVal++) { + colorPipBias[pipVal] /= colorPips; } - for (var val = 0; val < colorBias.length; val++) { - colorBias[val] /= nonLandCount; + for (var biasVal = 0; biasVal < colorBias.length; biasVal++) { + colorBias[biasVal] /= nonLandCount; } cmcBias /= nonLandCount; - for (var type in typeBias) { - typeBias[type] /= totalCount; + for (var types in typeBias) { + typeBias[types] /= totalCount; } - for (var rarity in rarityBias) { - rarityBias[rarity] /= totalCount; + for (var rarities in rarityBias) { + rarityBias[rarities] /= totalCount; } - var packStats = {"colorBias": colorBias, "colorPipBias": colorPipBias, "typeBias": typeBias, "rarityBias": rarityBias, "cmcBias": cmcBias} + var packStats = {"colorBias": colorBias, "colorPipBias": colorPipBias, "typeBias": typeBias, "rarityBias": rarityBias, "cmcBias": cmcBias}; + + return packStats; } diff --git a/backend/analytics.spec.js b/backend/analytics.spec.js index 00d6f1a0..30f32a9a 100644 --- a/backend/analytics.spec.js +++ b/backend/analytics.spec.js @@ -6,27 +6,27 @@ const {range} = require("lodash"); // List of potentially problematic cards to test over for pack analysis. const TestCard = { - uuid: '576e9e04-acd7-5d24-bc19-1dd765e9d1b8', + uuid: "576e9e04-acd7-5d24-bc19-1dd765e9d1b8", name: "Purphoros's Intervention", names: [], - color: 'Red', - colors: [ 'R' ], - colorIdentity: [ 'R' ], - setCode: 'THB', - scryfallId: 'ecc911ee-0e12-4b10-add7-9a9d63c29443', + color: "Red", + colors: [ "R" ], + colorIdentity: [ "R" ], + setCode: "THB", + scryfallId: "ecc911ee-0e12-4b10-add7-9a9d63c29443", cmc: 1, - number: '151', - type: 'Sorcery', - manaCost: '{X}{R}', - rarity: 'Rare', - url: 'https://api.scryfall.com/cards/ecc911ee-0e12-4b10-add7-9a9d63c29443?format=image', - layout: 'normal', + number: "151", + type: "Sorcery", + manaCost: "{X}{R}", + rarity: "Rare", + url: "https://api.scryfall.com/cards/ecc911ee-0e12-4b10-add7-9a9d63c29443?format=image", + layout: "normal", isDoubleFaced: false, - flippedCardURL: '', + flippedCardURL: "", supertypes: [], subtypes: [], - text: 'Choose one —\n' + - '• Create an X/1 red Elemental creature token with trample and haste. Sacrifice it at the beginning of the next end step.\n' + + text: "Choose one —\n" + + "• Create an X/1 red Elemental creature token with trample and haste. Sacrifice it at the beginning of the next end step.\n" + "• Purphoros's Intervention deals twice X damage to target creature or planeswalker.", foil: true }; @@ -41,41 +41,66 @@ function withinRange(val, expected, tolerance) { return (val <= tolerance + expected && val >= tolerance - expected); } -var monoGreenTest = () => { - var arr = []; +/** + * @desc Compares an arr/obj and returns if they're the same. + * @param {arr/obj} val0 ... val0 to compare. + * @param {arr/obj} val1 ... val1 to compare. + * @returns {boolean} ... Are the values the same? + */ +function compareArray(val0, val1) { + for (var val in val0) { + if (val0[val] != val1[val]) { + return false; + } + } + + return true; +} + +var monoGreenTest = []; - range(15).forEach(() => { - arr.append(TestCard); - }); -}; +for (var val = 0; val < 15; val++) { + monoGreenTest.push(TestCard); +} describe("Acceptance tests for card analytics generation", () => { - it("Should return the bias of a single card.", () => { + it("Should return the known bias of a single card", () => { var stats = cardStats(monoGreenTest); + var statsLength = 0; + + for (var output in stats) { + if (stats[output] != undefined) { + statsLength++; + } + } - assert(stats.colorBias == [0, 0, 0, 1, 0]); - assert(stats.colorPipBias == [0, 0, 0, 0, 1]); - assert(cmcBias == 2); + assert(statsLength == 5); + assert(compareArray(stats.colorBias, [0, 0, 0, 1, 0, 0, 0])); + assert(compareArray(stats.colorPipBias, [0, 0, 0, 1, 0])); + assert(stats.cmcBias == 1); }); it("Should return statistics near 100% +/- 2", () => { - range(100).forEach(() => { + range(20).forEach(() => { var randomBooster = boosterGenerator("MH1"); var stats = cardStats(randomBooster); var colorBias = stats.colorBias; - var colorPipBias = statis.colorPipBias; + var colorPipBias = stats.colorPipBias; var colorBiasPercentage = 0; var colorPipBiasPercentage = 0; - for (var val in colorBias) { - colorBiasPercentage += colorBias[val]; - colorPipBiasPercentage += colorBIas[val]; + for (var colorBiasVal in colorBias) { + colorBiasPercentage += colorBias[colorBiasVal]; + } + + for (var colorPipVal in colorPipBias) { + colorPipBiasPercentage += colorPipBias[colorPipVal]; } - assert(withinRange(colorBiasPercentage, 1, 2)); - assert(withinRange(colorPipBiasPercentage, 1, 2)); + assert(withinRange(colorBiasPercentage, 1, .02)); + assert(withinRange(colorPipBiasPercentage, 1, .02)); }); }); }); \ No newline at end of file diff --git a/backend/bot.js b/backend/bot.js index 2c2d3589..b3b89c23 100644 --- a/backend/bot.js +++ b/backend/bot.js @@ -1,6 +1,5 @@ const {sample, pull} = require("lodash"); const Player = require("./player"); -const analyzePack = require("./analytics"); module.exports = class extends Player { constructor() { From 9dd338420b5ca545d2050e061e9ea75c1f512adc Mon Sep 17 00:00:00 2001 From: May Date: Sun, 12 Apr 2020 18:05:33 -0700 Subject: [PATCH 09/12] Created basic ranking system. --- backend/analytics.js | 58 +++++++++++++++++++++++++++++++++++++++++--- backend/bot.js | 3 +++ 2 files changed, 57 insertions(+), 4 deletions(-) diff --git a/backend/analytics.js b/backend/analytics.js index ab0bddae..2f111dd2 100644 --- a/backend/analytics.js +++ b/backend/analytics.js @@ -1,3 +1,9 @@ +const fs = require('fs'); +const readline = require('readline'); +const path = require('path'); + +const rankingsPath = path.join(__dirname, "../data/picks/results.txt"); + const CardColors = { "W": 0, "U": 1, @@ -17,6 +23,47 @@ const CardColorNames = { "Multicolor": 6 }; +/** + * @desc ... Searches our rankings file for a card and returns what rank it is. + * @param {Object} card ... The card object. + * @returns {Int} ... Returns the card rank, -1 if not found. + */ +async function pullCardRank(card) { + const fileStream = fs.createReadStream(rankingsPath); + + const rl = readline.createInterface({ + input: fileStream, + crlfDelay: Infinity + }); + + var rank = 0; + + console.log(card.name); + + for await (var line of rl) { + var card_line; + card_line = line.split(" "); + card_line.shift(); + card_line = card_line.join(" "); + + if (card.name == card_line) { + return rank; + } + + rank++ + } + + return -1; +} + + +async function getCardRank(card) { + promise = pullCardRank(card); + result = await promise; + + return result; +} + /** * @desc eatColorPips ... Separates out the colored pips {4}{W} --> W for bias analysis. * @param {String} manaCost ... String of the manacost with generic and colored costs. @@ -60,6 +107,7 @@ function generatePackStats(packs) { var totalCount = packs.length; var nonLandCount = 0; var colorPips = 0; + var bestPick; for (var pack in packs) { // packObj used to make my life easier regarding referencing. @@ -70,6 +118,10 @@ function generatePackStats(packs) { var rarity = packObj.rarity; var color = packObj.color; var CMC = packObj.cmc; + var rank = getCardRank(packObj); + + console.log(rank); + if (type != "Land") { nonLandCount++; @@ -95,6 +147,7 @@ function generatePackStats(packs) { rarityBias[rarity] = (rarityBias[rarity] || 0) + 1; } + // Adjust the weights of everything. for (var pipVal = 0; pipVal < colorPipBias.length; pipVal++) { colorPipBias[pipVal] /= colorPips; } @@ -113,10 +166,7 @@ function generatePackStats(packs) { rarityBias[rarities] /= totalCount; } - var packStats = {"colorBias": colorBias, "colorPipBias": colorPipBias, "typeBias": typeBias, "rarityBias": rarityBias, "cmcBias": cmcBias}; - - - + var packStats = {"colorBias": colorBias, "colorPipBias": colorPipBias, "typeBias": typeBias, "rarityBias": rarityBias, "cmcBias": cmcBias} return packStats; } diff --git a/backend/bot.js b/backend/bot.js index b3b89c23..64eabc5d 100644 --- a/backend/bot.js +++ b/backend/bot.js @@ -1,5 +1,6 @@ const {sample, pull} = require("lodash"); const Player = require("./player"); +const PackStats = require("./analytics") module.exports = class extends Player { constructor() { @@ -12,6 +13,8 @@ module.exports = class extends Player { } getPack(pack) { + var packStats = PackStats(pack); + const randomPick = sample(pack); this.picks.push(randomPick.name); pull(pack, randomPick); From ec6f2ae4f0dff38d8108e9d78386f9adbb1f1e87 Mon Sep 17 00:00:00 2001 From: May Date: Sun, 12 Apr 2020 18:07:42 -0700 Subject: [PATCH 10/12] Fixed Travis from being angry. --- backend/analytics.js | 68 ++++++++++++++++++++++---------------------- backend/bot.js | 2 -- 2 files changed, 34 insertions(+), 36 deletions(-) diff --git a/backend/analytics.js b/backend/analytics.js index 2f111dd2..f422b1cb 100644 --- a/backend/analytics.js +++ b/backend/analytics.js @@ -2,7 +2,7 @@ const fs = require('fs'); const readline = require('readline'); const path = require('path'); -const rankingsPath = path.join(__dirname, "../data/picks/results.txt"); +// const rankingsPath = path.join(__dirname, "../data/picks/results.txt"); const CardColors = { "W": 0, @@ -23,46 +23,46 @@ const CardColorNames = { "Multicolor": 6 }; -/** - * @desc ... Searches our rankings file for a card and returns what rank it is. - * @param {Object} card ... The card object. - * @returns {Int} ... Returns the card rank, -1 if not found. - */ -async function pullCardRank(card) { - const fileStream = fs.createReadStream(rankingsPath); +// /** +// * @desc ... Searches our rankings file for a card and returns what rank it is. +// * @param {Object} card ... The card object. +// * @returns {Int} ... Returns the card rank, -1 if not found. +// */ +// async function pullCardRank(card) { +// const fileStream = fs.createReadStream(rankingsPath); - const rl = readline.createInterface({ - input: fileStream, - crlfDelay: Infinity - }); +// const rl = readline.createInterface({ +// input: fileStream, +// crlfDelay: Infinity +// }); - var rank = 0; +// var rank = 0; - console.log(card.name); +// console.log(card.name); - for await (var line of rl) { - var card_line; - card_line = line.split(" "); - card_line.shift(); - card_line = card_line.join(" "); +// for await (var line of rl) { +// var card_line; +// card_line = line.split(" "); +// card_line.shift(); +// card_line = card_line.join(" "); - if (card.name == card_line) { - return rank; - } +// if (card.name == card_line) { +// return rank; +// } - rank++ - } +// rank++ +// } - return -1; -} +// return -1; +// } -async function getCardRank(card) { - promise = pullCardRank(card); - result = await promise; +// async function getCardRank(card) { +// promise = pullCardRank(card); +// result = await promise; - return result; -} +// return result; +// } /** * @desc eatColorPips ... Separates out the colored pips {4}{W} --> W for bias analysis. @@ -107,7 +107,7 @@ function generatePackStats(packs) { var totalCount = packs.length; var nonLandCount = 0; var colorPips = 0; - var bestPick; + // var bestPick; for (var pack in packs) { // packObj used to make my life easier regarding referencing. @@ -118,9 +118,9 @@ function generatePackStats(packs) { var rarity = packObj.rarity; var color = packObj.color; var CMC = packObj.cmc; - var rank = getCardRank(packObj); + // var rank = getCardRank(packObj); - console.log(rank); + // console.log(rank); if (type != "Land") { diff --git a/backend/bot.js b/backend/bot.js index 64eabc5d..288a2517 100644 --- a/backend/bot.js +++ b/backend/bot.js @@ -13,8 +13,6 @@ module.exports = class extends Player { } getPack(pack) { - var packStats = PackStats(pack); - const randomPick = sample(pack); this.picks.push(randomPick.name); pull(pack, randomPick); From 439e8e732d170e2ef80a40e05e048aa5558a7707 Mon Sep 17 00:00:00 2001 From: May Date: Sun, 12 Apr 2020 18:08:10 -0700 Subject: [PATCH 11/12] Comment out requires. --- backend/analytics.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/analytics.js b/backend/analytics.js index f422b1cb..0ae6baf3 100644 --- a/backend/analytics.js +++ b/backend/analytics.js @@ -1,6 +1,6 @@ -const fs = require('fs'); -const readline = require('readline'); -const path = require('path'); +// const fs = require('fs'); +// const readline = require('readline'); +// const path = require('path'); // const rankingsPath = path.join(__dirname, "../data/picks/results.txt"); From 75572e3fa8f7275d754d3b49000689a03fbf5bc7 Mon Sep 17 00:00:00 2001 From: May Date: Sun, 12 Apr 2020 18:09:46 -0700 Subject: [PATCH 12/12] Fixed syntax and typings. --- backend/analytics.js | 2 +- backend/bot.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/analytics.js b/backend/analytics.js index 0ae6baf3..d9f71a72 100644 --- a/backend/analytics.js +++ b/backend/analytics.js @@ -166,7 +166,7 @@ function generatePackStats(packs) { rarityBias[rarities] /= totalCount; } - var packStats = {"colorBias": colorBias, "colorPipBias": colorPipBias, "typeBias": typeBias, "rarityBias": rarityBias, "cmcBias": cmcBias} + var packStats = {"colorBias": colorBias, "colorPipBias": colorPipBias, "typeBias": typeBias, "rarityBias": rarityBias, "cmcBias": cmcBias}; return packStats; } diff --git a/backend/bot.js b/backend/bot.js index 288a2517..2f7ce0da 100644 --- a/backend/bot.js +++ b/backend/bot.js @@ -1,6 +1,6 @@ const {sample, pull} = require("lodash"); const Player = require("./player"); -const PackStats = require("./analytics") +const PackStats = require("./analytics"); module.exports = class extends Player { constructor() {