Skip to content

Instantly share code, notes, and snippets.

@metaflow
Last active November 21, 2020 16:40
Show Gist options
  • Save metaflow/363fda250c6593a671cd328ecd3bb630 to your computer and use it in GitHub Desktop.
Save metaflow/363fda250c6593a671cd328ecd3bb630 to your computer and use it in GitHub Desktop.
crew boardgamearena counter

BGA Crew discard counter

Bookmarklet

Create a new bookmark and replace its URL with one line down below. Click this bookmark when you are in a tab with a Crew game.

One time usage

  • Click in the address bar, replace it with "javascript:"

  • Paste one line contents, so the address looks like "javascript:colors...". Note that some browsers remove "javascript:" prefix and you have to type it yourself.

  • Press Enter

Notes

The table will be displayed when it's time to make turns. There is no need to do anything between the missions but you have to redo the steps after reloading the tab.

Source code is in the same gist (I use https://obfuscator.io/ to create one-liner).

"Missing colors" by player is only detected when one plays off-color.

Localization

Script parses localized log of your actions and works only with English, Russian and French locales. Will be happy to add more translations with your help, please just leave a comment under the gist with the appropriate entries for "textToType" map to be added.

example

javascript:colors=['blue','yellow','green','pink','black'];function parse(a){var b={'type':null,'player':'','cards':[]},d='';for(const f of a['childNodes']){if(f['nodeType']==0x3)d+=f['nodeValue'];colors['indexOf'](f['className'])>-0x1&&b['cards']['push']({'color':f['className'],'value':toint(f['innerText'])});}textToType=new Map([['plays','plays'],['new\x20mission','new\x20mission'],['wins\x20the\x20trick','wins\x20the\x20trick'],['играет','plays'],['новую\x20миссию','new\x20mission'],['выигрывает\x20взятку','wins\x20the\x20trick'],['joue','plays'],['nouvelle\x20mission','new\x20mission'],['remporte\x20le\x20pli','wins\x20the\x20trick']]);for(let [g,h]of textToType['entries']()){if(d['indexOf'](g)<0x0)continue;b['type']=h;break;}if(b['type']==null)return null;if(b['type']=='plays'&&b['cards']['length']==0x0)return null;return a['querySelector']('.playername')&&(b['player']=a['querySelector']('.playername')['innerText']),b;}class State{constructor(){this['players']=new Set(),this['reset']();}['reset'](){this['cards']=new Map(),this['missing']=new Map();for(const a of this['players'])this['missing']['set'](a,new Set());this['currentColor']='';for(const b of colors){this['cards']['set'](b,b=='black'?[0x1,0x2,0x3,0x4]:[0x1,0x2,0x3,0x4,0x5,0x6,0x7,0x8,0x9]);}}['run'](a){switch(a['type']){case'new\x20mission':this['reset']();break;case'plays':!this['players']['has'](a['player'])&&(this['missing']['set'](a['player'],new Set()),this['players']['add'](a['player']));const b=a['cards'][0x0];this['cards']['get'](b['color'])[b['value']-0x1]=0x0;if(this['currentColor']=='')this['currentColor']=b['color'];else{if(this['currentColor']!=b['color'])this['missing']['get'](a['player'])['add'](this['currentColor']);}break;case'wins\x20the\x20trick':this['currentColor']='';break;default:console['error']('unknown\x20action\x20type',a['type']);}}}function drawState(a){area=document['querySelector']('#customTable');!area&&(area=document['createElement']('div'),area['id']='customTable',area['style']='width:\x20280px;\x20height:\x20150px;\x20padding:\x205px;\x20font-weight:\x20bold;',document['getElementById']('playertable_central')['appendChild'](area));area['innerHTML']='';for(const b of colors){row=document['createElement']('div'),row['style']='display:\x20flex;';for(const d of a['cards']['get'](b)){tile=document['createElement']('div'),tile['innerText']=d,dc=d<0x1?'transparent':b,tile['style']='display:\x20block;\x20min-height:\x2025px;\x20min-width:\x2025px;\x20margin:\x202px;\x20line-height:\x2025px;\x20color:\x20'+dc+';\x20border:\x201px\x20solid\x20'+dc+';',row['appendChild'](tile);}area['appendChild'](row);}for(const e of a['players']){document['querySelectorAll']('.playertablename')['forEach'](f=>{if(f['innerText']['trim']()['indexOf'](e)==-0x1)return;m=f['querySelector']('.missing');!m&&(m=document['createElement']('div'),m['className']='missing',m['style']='display:\x20inline-block;\x20font-size:\x2014px;\x20',f['appendChild'](m));m['innerHTML']='';for(const g of a['missing']['get'](e)){tile=document['createElement']('div'),tile['innerText']='X',tile['style']='display:\x20inline-block;\x20min-height:\x2015px;\x20margin:\x202px;\x20min-width:\x2015px;\x20line-height:\x2015px;\x20color:\x20'+g+';\x20border:\x201px\x20solid\x20'+g+';',m['appendChild'](tile);}});}}function crewTable(){logs=Array['from'](document['querySelectorAll']('#logs\x20.log\x20>\x20div')),logs=logs['reverse']();const a=logs['map'](parse)['filter'](c=>c);let b=new State();a['forEach'](c=>{b['run'](c);}),drawState(b);};window['setInterval'](crewTable,0x1f4);
colors = ['blue', 'yellow', 'green', 'pink', 'black'];
function parse(e) {
var entry = {
'type': null,
'player': '',
'cards': [],
};
var text = '';
for (const c of e.childNodes) {
if (c.nodeType == 3 /*text*/) text += c.nodeValue;
if (colors.indexOf(c.className) > -1) {
entry.cards.push({ 'color': c.className, 'value': toint(c.innerText) });
}
}
textToType = new Map([
// English.
['plays', 'plays'],
['new mission', 'new mission'],
['wins the trick', 'wins the trick'],
// Russian.
['играет', 'plays'],
['новую миссию', 'new mission'],
['выигрывает взятку', 'wins the trick'],
// French.
['joue', 'plays'],
['nouvelle mission', 'new mission'],
['remporte le pli', 'wins the trick'],
]);
for (let [key, value] of textToType.entries()) {
if (text.indexOf(key) < 0) continue;
entry.type = value;
break;
}
if (entry.type == null) return null; // Unknown action.
if (entry.type == 'plays' && entry.cards.length == 0) return null; // Mismatch of 'play'.
if (e.querySelector('.playername')) {
entry.player = e.querySelector('.playername').innerText;
}
return entry;
}
class State {
constructor() {
this.players = new Set();
this.reset();
}
reset() {
this.cards = new Map();
this.missing = new Map();
for (const p of this.players) this.missing.set(p, new Set());
this.currentColor = '';
for (const c of colors) {
this.cards.set(c, c == 'black' ? [1, 2, 3, 4] : [1, 2, 3, 4, 5, 6, 7, 8, 9]);
}
}
run(action) {
switch (action.type) {
case 'new mission':
this.reset();
break;
case 'plays':
if (!this.players.has(action.player)) {
this.missing.set(action.player, new Set());
this.players.add(action.player);
}
const card = action.cards[0];
this.cards.get(card.color)[card.value - 1] = 0;
if (this.currentColor == '') {
this.currentColor = card.color;
} else {
if (this.currentColor != card.color) this.missing.get(action.player).add(this.currentColor);
}
break;
case 'wins the trick':
this.currentColor = '';
break;
default:
console.error('unknown action type', action.type);
}
}
}
function drawState(s) {
area = document.querySelector('#customTable');
if (!area) {
area = document.createElement('div');
area.id = 'customTable';
area.style = 'width: 280px; height: 150px; padding: 5px; font-weight: bold;';
document.getElementById('playertable_central').appendChild(area);
}
area.innerHTML = '';
for (const c of colors) {
row = document.createElement('div');
row.style = 'display: flex;';
for (const i of s.cards.get(c)) {
tile = document.createElement('div');
tile.innerText = i;
dc = (i < 1) ? 'transparent' : c;
tile.style = `display: block; min-height: 25px; min-width: 25px; margin: 2px; line-height: 25px; color: ${dc}; border: 1px solid ${dc};`;
row.appendChild(tile);
}
area.appendChild(row);
}
for (const p of s.players) {
document.querySelectorAll('.playertablename').forEach(c => {
if (c.innerText.trim().indexOf(p) == -1) return;
m = c.querySelector('.missing');
if (!m) {
m = document.createElement('div');
m.className = 'missing';
m.style = 'display: inline-block; font-size: 14px; ';
c.appendChild(m);
}
m.innerHTML = '';
for (const c of s.missing.get(p)) {
tile = document.createElement('div');
tile.innerText = 'X';
tile.style = `display: inline-block; min-height: 15px; margin: 2px; min-width: 15px; line-height: 15px; color: ${c}; border: 1px solid ${c};`;
m.appendChild(tile);
}
});
}
}
function crewTable() {
logs = Array.from(document.querySelectorAll('#logs .log > div'));
logs = logs.reverse();
const actions = logs.map(parse).filter(a => a);
let s = new State();
actions.forEach(a => {
s.run(a);
});
drawState(s);
};
window.setInterval(crewTable, 500);
@lhoang
Copy link

lhoang commented Nov 18, 2020

Hey, great script !
I localised it in French, but there was an error when a player takes too long to play (same keyword "plays", in French) . I fixed it by adding
line 30 :

if (entry.type == 'plays' && entry.cards.length ==0) return null;

https://gist.github.com/lhoang/6157785d3356f4d4c00a69e655c4f0b4

@metaflow
Copy link
Author

Thank you @lhoang! I have incorporated your updates. ❤️ 👨‍🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment