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);