|
|
|
@@ -0,0 +1,114 @@ |
|
|
|
const initCards = () => { |
|
|
|
const cards = new Map() |
|
|
|
for(const color of ['red', 'green', 'blue', 'yellow', 'white']) { |
|
|
|
for(const value of [1, 1, 1, 2, 2, 3, 3, 4, 4, 5]) { |
|
|
|
const id = cards.size |
|
|
|
const pos = Hanabi.nrows * Hanabi.ncols - 1 |
|
|
|
const ts = Math.random() |
|
|
|
cards.set(id, {id, pos, color, value, ts, faceUp: false}) |
|
|
|
} |
|
|
|
} |
|
|
|
return cards |
|
|
|
} |
|
|
|
|
|
|
|
const Counter = { |
|
|
|
step: (name, delta) => () => { |
|
|
|
Hanabi.counters[name] += delta |
|
|
|
Hanabi.sync() |
|
|
|
}, |
|
|
|
view: ({attrs}) => m('.counter', |
|
|
|
m('span', attrs.name), |
|
|
|
m('button', {onclick: Counter.step(attrs.name, -1)}, '-'), |
|
|
|
m('span', Hanabi.counters[attrs.name]), |
|
|
|
m('button', {onclick: Counter.step(attrs.name, +1)}, '+'), |
|
|
|
), |
|
|
|
} |
|
|
|
|
|
|
|
const Hanabi = { |
|
|
|
nrows: 8, |
|
|
|
ncols: 7, |
|
|
|
counters: {clues: 8, bombs: 3}, |
|
|
|
players: new Map(), |
|
|
|
cards: [], |
|
|
|
oninit: () => { |
|
|
|
Hanabi.cards = initCards() |
|
|
|
listen('hanabi', ({detail}) => { |
|
|
|
Hanabi.counters = detail.value.counters |
|
|
|
Hanabi.cards = new Map(detail.value.cards) |
|
|
|
}) |
|
|
|
listen('join', () => setTimeout(Hanabi.sync, 100)) |
|
|
|
doNotLog.add('hanabi') |
|
|
|
}, |
|
|
|
sync: () => { |
|
|
|
wire({kind: 'hanabi', value: { |
|
|
|
counters: Hanabi.counters, |
|
|
|
cards: [...Hanabi.cards.entries()], |
|
|
|
}}) |
|
|
|
}, |
|
|
|
getStacks: () => { |
|
|
|
const totalCount = Hanabi.nrows * Hanabi.ncols |
|
|
|
const stacks = new Array(totalCount).fill(null).map(_ => []) |
|
|
|
Hanabi.cards.forEach((card) => stacks[card.pos].push(card)) |
|
|
|
return stacks |
|
|
|
}, |
|
|
|
renderCard: (card, i) => { |
|
|
|
const attrs = { |
|
|
|
id: card.id, |
|
|
|
value: card.value, |
|
|
|
style: {top: `${-3 * i}px`}, |
|
|
|
ondragstart: (ev) => { |
|
|
|
ev.dataTransfer.setData('idx', card.id) |
|
|
|
ev.dataTransfer.dropEffect = 'move' |
|
|
|
}, |
|
|
|
class: ['card', card.color, card.faceUp && 'face-up'].join(' '), |
|
|
|
draggable: true, |
|
|
|
} |
|
|
|
return m('div', attrs) |
|
|
|
}, |
|
|
|
renderStack: (pos, stacks) => { |
|
|
|
const stack = stacks[pos] |
|
|
|
stack.sort((a, b) => a.ts - b.ts) |
|
|
|
const attrs = { |
|
|
|
ondrop: (ev) => { |
|
|
|
ev.preventDefault() |
|
|
|
const card = Hanabi.cards.get(+ev.dataTransfer.getData('idx')) |
|
|
|
card.pos = pos |
|
|
|
card.ts = +new Date() |
|
|
|
Hanabi.sync() |
|
|
|
}, |
|
|
|
ondragover: (ev) => { |
|
|
|
ev.preventDefault() |
|
|
|
ev.dataTransfer.dropEffect = 'move' |
|
|
|
}, |
|
|
|
} |
|
|
|
const doMany = (fn) => { |
|
|
|
return {onclick: () => {stack.forEach(fn); Hanabi.sync()}} |
|
|
|
} |
|
|
|
const actions = [ |
|
|
|
m('button', doMany(card => card.faceUp = !card.faceUp), 'flip'), |
|
|
|
m('button', doMany(card => card.ts = card.id), 'sort'), |
|
|
|
m('button', doMany(card => card.ts = Math.random()), 'shuffle'), |
|
|
|
m('button', doMany(card => card.faceUp = true), 'reveal'), |
|
|
|
] |
|
|
|
return m('.stack', attrs, stack.map(Hanabi.renderCard), |
|
|
|
stack.length ? m('.actions', actions) : null, |
|
|
|
) |
|
|
|
}, |
|
|
|
renderStacks: () => { |
|
|
|
const stacks = Hanabi.getStacks() |
|
|
|
return m('.stacks', {style: {'--ncols': Hanabi.ncols}}, |
|
|
|
Array(Hanabi.nrows).fill(null).map((_, y) => |
|
|
|
Array(Hanabi.ncols).fill(null).map((_, x) => |
|
|
|
m('.col', |
|
|
|
{owner: State.online[y], class: y < 5 ? State.username === State.online[y] ? 'own' : 'rival' : ''}, |
|
|
|
Hanabi.renderStack(y * Hanabi.ncols + x, stacks)), |
|
|
|
) |
|
|
|
) |
|
|
|
) |
|
|
|
}, |
|
|
|
view: () => m('.hanabi', |
|
|
|
m(Counter, {name: 'clues'}), |
|
|
|
m(Counter, {name: 'bombs'}), |
|
|
|
Hanabi.renderStacks(), |
|
|
|
), |
|
|
|
} |