|
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); |
Thank you @lhoang! I have incorporated your updates. ❤️ 👨🚀