Permalink
Browse files

Merge pull request #169 from dougmill/shuffler-stats

Record sideboarding and shuffler data, and show it in match history
  • Loading branch information...
Manuel-777 committed Jan 27, 2019
2 parents 29459b5 + 91afe8e commit 7cf1333634369ffdd8bafc8c8a58ea94f7b95236
Showing with 593 additions and 108 deletions.
  1. +109 −20 shared/util.js
  2. +47 −33 window_background/background.js
  3. +2 −1 window_background/http-api.js
  4. +156 −49 window_background/labels.js
  5. +124 −4 window_main/index.css
  6. +155 −1 window_main/renderer.js
@@ -8,6 +8,9 @@ const windowBackground = 0;
const windowRenderer = 1;
const windowOverlay = 2;

const math = require('mathjs');
math.config({precision: 2000});

const Database = require('../shared/database.js');
const cardsDb = new Database();

@@ -62,42 +65,46 @@ function addCardTile(grpId, indent, quantity, element) {
}
element.append(cont);
var card = cardsDb.get(grpId);
var div = $('<div id="t'+grpId+indent+'" style="min-width: calc(100% - '+ww+'px) !important;" class="card_tile '+get_frame_class(card.frame)+'"></div>');
var div = $('<div id="t'+grpId+indent+'" style="min-width: calc(100% - '+ww+'px) !important;" class="card_tile '+get_frame_class(card ? card.frame : [])+'"></div>');
cont.append(div);

// Glow hover
var glow = $('<div id="t'+grpId+indent+'" style="min-width: calc(100% - '+ww+'px) !important; left: calc(0px - 100% + '+ll+'px) !important" class="card_tile_glow"></div>');
cont.append(glow);

addCardHover(glow, card);
glow.on('mouseenter', function() {
var domid = $(this).attr('id');
$('#'+domid).css('margin-top', '0px');
});
if (card) {
addCardHover(glow, card);
glow.on('mouseenter', function() {
var domid = $(this).attr('id');
$('#'+domid).css('margin-top', '0px');
});

glow.on('click', function() {
if (card.dfc == 'SplitHalf') {
card = cardsDb.get(card.dfcId);
}
//let newname = card.name.split(' ').join('-');
shell.openExternal('https://scryfall.com/card/'+get_set_scryfall(card.set)+'/'+card.cid+'/'+card.name);
});
glow.on('click', function() {
if (card.dfc == 'SplitHalf') {
card = cardsDb.get(card.dfcId);
}
//let newname = card.name.split(' ').join('-');
shell.openExternal('https://scryfall.com/card/'+get_set_scryfall(card.set)+'/'+card.cid+'/'+card.name);
});

glow.on('mouseleave', function() {
var domid = $(this).attr('id');
//$('.main_hover').css("opacity", 0);
$('#'+domid).css('margin-top', '3px');
//$('.loader').css("opacity", 0);
});
glow.on('mouseleave', function() {
var domid = $(this).attr('id');
//$('.main_hover').css("opacity", 0);
$('#'+domid).css('margin-top', '3px');
//$('.loader').css("opacity", 0);
});
}

//
var fl = $('<div class="flex_item"></div>');
fl.append('<div class="card_tile_name">'+card.name+'</div>');
fl.append('<div class="card_tile_name">' + (card ? card.name : "Unknown") + '</div>');
div.append(fl);

fl = $('<div class="flex_item" style="line-height: 26px;"></div>"');
div.append(fl);

if (!card) return cont;

var prevc = true;
card.cost.forEach(function(cost) {
if (/^(x|\d)+$/.test(cost) && prevc == false) {
@@ -381,6 +388,16 @@ function deck_count_types(deck, type, side) {
return count;
}

//
function deck_count(deck) {
let count = 0;
deck.mainDeck.forEach((card) => {
if (card.quantity == 9999) count += 1;
else count+=card.quantity;
});
return count;
}

//
function compare_cards(a, b) {
// Yeah this is lazy.. I know
@@ -1278,4 +1295,76 @@ function createDivision(classNames, innerHTML) {
div.innerHTML = innerHTML;
}
return div;
}

//
function hypergeometric(exact, population, sample, hitsInPop, returnBig = false) {
return hypergeometricRange(exact, exact, population, sample, hitsInPop, returnBig);
}

//
function hypergeometricRange(lowerBound, upperBound, population, sample, hitsInPop, returnBig = false) {
if (lowerBound > upperBound || lowerBound > hitsInPop) {
return returnBig ? math.bignumber(0) : 0;
}

let _population = math.bignumber(population);
let _sample = math.bignumber(sample);
let _hitsInPop = math.bignumber(hitsInPop);
let matchingCombos = math.bignumber(0);
// Can't have more non-hits in the sample than exist in the population
for (let i = math.max(lowerBound, sample - (population - hitsInPop)); i <= upperBound && i <= sample; i++) {
let _hitsInSample = math.bignumber(i);
let _hitCombos = math.combinations(_hitsInPop, _hitsInSample);
let _missCombos = math.combinations(math.max(0, math.subtract(_population, _hitsInPop)), math.max(0, math.subtract(_sample, _hitsInSample)));
matchingCombos = math.add(matchingCombos, math.multiply(_hitCombos, _missCombos));
}

let totalCombos = math.combinations(_population, _sample);
let probability = math.divide(matchingCombos, totalCombos);
return returnBig ? probability : math.number(probability);
}

// This function is designed to assess the "significance" of a particular result by calculating an alternative to
// percentile designed to measure deviation from median in both directions the same way and ensure the average returned
// value (assuming true random) is always 50%. This is done by treating the given result as a range of values
// distributed evenly throughout the percentile range covered by the result. For example, a result of 0 that has a 20%
// chance of happening is treated as a composite distributed evenly by percentile through the 0% to 20% percentile
// range. The returned value is the probability that a result is at least as far from median as the given value. Return
// values close to 1 indicate the passed in value was very close to average, return values close to 0 indicate it was
// very far from average.
function hypergeometricSignificance(value, population, sample, hitsInPop, returnBig = false) {
let percentile = hypergeometricRange(0, value, population, sample, hitsInPop, true);
let chance = hypergeometric(value, population, sample, hitsInPop, true);
if (math.smallerEq(percentile, .5)) {
let midpoint = math.subtract(percentile, math.divide(chance, 2));
let retVal = math.multiply(midpoint, 2);
return returnBig ? retVal : math.number(retVal);
}
let reversePercentile = hypergeometricRange(value, math.min(hitsInPop, sample), population, sample, hitsInPop, true);
if (math.smallerEq(reversePercentile, .5)) {
let midpoint = math.subtract(reversePercentile, math.divide(chance, 2));
let retVal = math.multiply(midpoint, 2);
return returnBig ? retVal : math.number(retVal);
}
// If we get here, then value is the median and we need to weight things for how off-center its percentile range is.
let smaller, larger;
if (math.smallerEq(percentile, reversePercentile)) {
smaller = percentile;
larger = reversePercentile;
} else {
smaller = reversePercentile;
larger = percentile;
}
// Divide the range into a symmetric portion centered on .5, and another portion for the rest. Calculate the average
// distance from center for each, and use the average of that weighted by each portion's size.
let centeredSize = math.multiply(math.subtract(smaller, .5), 2);
let otherSize = math.subtract(larger, smaller);
let centeredAverage = math.divide(centeredSize, 4); // half for being centered, half again for average
// Average of the farther bound (otherSize + centeredSize/2) and the closer bound (centeredSize/2). Works out to
// ((otherSize + centeredSize/2) + (centeredSize/2)) / 2, simplified to (otherSize + centeredSize) / 2.
let otherAverage = math.divide(math.add(centeredSize, otherSize), 2);
let weightedAverage = math.divide(math.add(math.multiply(centeredSize, centeredAverage), math.multiply(otherSize, otherAverage)), chance);
let retVal = math.subtract(1, math.multiply(weightedAverage, 2));
return returnBig ? retVal : math.number(retVal);
}
@@ -28,8 +28,6 @@ global
onLabelInEventGetSeasonAndRankDetail
*/
var electron = require('electron');
const math = require('mathjs');
math.config({precision: 2000});

const {app, net, clipboard} = require('electron');
const path = require('path');
@@ -127,10 +125,14 @@ var originalDeck = {};
var currentDeck = {};
var currentDeckUpdated = {};
var currentMatchId = null;
var currentMatchBestOfNumber = 0;
var currentMatchTime = 0;
var currentEventId = null;
var duringMatch = false;
var matchBeginTime = 0;
var matchGameStats = [];
var matchCompletedOnGameNumber = 0;
var gameNumberCompleted = 0;

var arenaVersion = '';
var playerUsername = '';
@@ -175,6 +177,12 @@ var opponentLife = 20;

var zones = {};
var gameObjs = {};

var gameStage = "";
var initialLibraryInstanceIds = [];
var idChanges = {};
var instanceToCardIdMap = {};

var history = {};
var drafts = {};
var events = {};
@@ -1211,15 +1219,36 @@ function updateCustomDecks() {
}

//
function createMatch(arg) {
actionLog(-99, new Date(), "");
var obj = store.get('overlayBounds');

function resetGameState() {
zones = {};
gameObjs = {};
initialLibraryInstanceIds = [];
idChanges = {};
instanceToCardIdMap = {};
attackersDetected = [];
zoneTransfers = [];

playerLife = 20;
opponentLife = 20;
}

//
function checkForStartingLibrary() {
if (gameStage != "GameStage_Start") return;
let hand = zones["ZoneType_Hand" + playerSeat].objectInstanceIds || [];
let library = zones["ZoneType_Library" + playerSeat].objectInstanceIds || [];
// Check that a post-mulligan scry hasn't been done
if (library.length == 0 || library[library.length-1] < library[0]) return;
if (hand.length + library.length == deck_count(originalDeck)) {
if (hand.length >= 2 && hand[0] == hand[1] + 1) hand.reverse();
initialLibraryInstanceIds = [...hand, ...library];
}
}

//
function createMatch(arg) {
actionLog(-99, new Date(), "");
var obj = store.get('overlayBounds');

oppDeck = {mainDeck: [], sideboard: []};

if (!firstPass && store.get("settings").show_overlay == true) {
@@ -1229,18 +1258,20 @@ function createMatch(arg) {
ipc_send("overlay_show", 1);
ipc_send("overlay_set_bounds", obj);
}
playerLife = 20;
opponentLife = 20;
oppName = arg.opponentScreenName;
oppRank = arg.opponentRankingClass;
oppTier = arg.opponentRankingTier;
currentEventId = arg.eventId;
currentMatchId = null;
currentMatchId = arg.matchId;
currentMatchTime = 0;
playerWin = 0;
oppWin = 0;
priorityTimers = [0,0,0,0,0];
lastPriorityChangeTime = matchBeginTime;
matchGameStats = [];
matchCompletedOnGameNumber = 0;
gameNumberCompleted = 0;
gameStage = "";

ipc_send("ipc_log", "vs "+oppName);
ipc_send("set_timer", matchBeginTime, windowOverlay);
@@ -1516,8 +1547,8 @@ function saveCourse(json) {
}

//
function saveMatch(upload = true) {
if (currentMatchTime == 0) {
function saveMatch(matchId) {
if (currentMatchTime == 0 || currentMatchId != matchId) {
return;
}
var match = {};
@@ -1544,6 +1575,9 @@ function saveMatch(upload = true) {
match.playerDeck = originalDeck;
match.oppDeck = getOppDeck();
match.date = new Date();
match.bestOf = currentMatchBestOfNumber;

match.gameStats = matchGameStats;

console.log("Save match:", match);
var matches_index = store.get('matches_index');
@@ -1565,7 +1599,7 @@ function saveMatch(upload = true) {

history[currentMatchId] = match;
history[currentMatchId].type = "match";
if (upload) {
if (matchCompletedOnGameNumber == gameNumberCompleted) {
httpApi.httpSetMatch(match);
}
requestHistorySend(0);
@@ -1685,23 +1719,3 @@ function parseWotcTime(str) {
}

}

//
function hypergeometric(arg0, arg1, arg2, arg3) {
if (arg0 > arg3) {
return 0;
}

let _x, _N, _n, _k;

_x = math.bignumber(arg0);// Number of successes in sample (x) <=
_N = math.bignumber(arg1);// Population size
_n = math.bignumber(arg2);// Sample size
_k = math.bignumber(arg3);// Number of successes in population

let _a = math.combinations(_k, _x)
let _b = math.combinations(math.max(0, math.subtract(_N,_k)), math.max(0, math.subtract(_n,_x)));
let _c = math.combinations(_N, _n);

return math.number(math.divide(math.multiply(_a, _b) , _c));
}
@@ -37,6 +37,7 @@ function httpBasic() {
if (store.get("settings").send_data == false && _headers.method != 'auth' && _headers.method != 'delete_data' && _headers.method != 'get_database' && _headers.method != 'get_status' && debugLog == false) {
callback({message: "Settings dont allow sending data! > "+_headers.method});
removeFromHttp(_headers.reqId);
return;
}

_headers.token = tokenAuth;
@@ -69,7 +70,7 @@ function httpBasic() {
var post_data = qs.stringify(_headers);
options.headers = { 'Content-Type': 'application/x-www-form-urlencoded', 'Content-Length': post_data.length};

var results = '';
var results = '';
var req = http.request(options, function(res) {
res.on('data', function (chunk) {
results = results + chunk;
Oops, something went wrong.

0 comments on commit 7cf1333

Please sign in to comment.