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(), ), }