Bladeren bron

Hanabi?

master
Roderic Day 5 jaren geleden
bovenliggende
commit
2d3c7ce2ae
4 gewijzigde bestanden met toevoegingen van 207 en 5 verwijderingen
  1. +80
    -0
      hanabi.css
  2. +114
    -0
      hanabi.js
  3. +2
    -0
      pico.html
  4. +11
    -5
      pico.js

+ 80
- 0
hanabi.css Bestand weergeven

@@ -0,0 +1,80 @@
.hanabi {
padding-top: 3em;
grid-area: media;
margin: 0 auto;
width: 40vh;
}
.stacks {
display: grid;
background-color: forestgreen;
padding: 1px;
grid-gap: 1px;
grid-template-columns: repeat(var(--ncols), 1fr);
height: 40vh;
}
.col:before {
position: relative;
}
.stack:after {
content: attr(owner);
position: absolute;
top: 0;
left: 0;
}
.stack {
position: relative;
height: 100%;
background-color: #eee;
}
.own .stack {
background-color: #aaa;
}
.rival .stack {
background-color: #ccc;
}
.hand {
position: absolute;
}
.stack .actions {
display: none;
}
.stack:hover .actions {
position: absolute;
display: block;
top: 100%;
z-index: 999;
}
.card {
position: absolute;
left: 0;
display: block;
width: 100%;
height: 100%;
outline: 1px solid black;
background-color: #555;
}
.card:after {
content: attr(value);
}
.card.red {
background-color: crimson;
}
.card.green {
background-color: forestgreen;
}
.card.blue {
background-color: royalblue;
}
.card.yellow {
background-color: gold;
}
.card.white {
background-color: ivory;
}
.col:not(.rival) .card:not(.face-up), .own .card {
color: transparent;
background-color: #555;
}
.counter {
font-family: monospace;
}

+ 114
- 0
hanabi.js Bestand weergeven

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

+ 2
- 0
pico.html Bestand weergeven

@@ -4,6 +4,8 @@
<script src="https://unpkg.com/mithril/mithril.min.js"></script>
<script src="https://unpkg.com/marked/marked.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/dompurify/2.0.2/purify.min.js"></script>
<link rel="stylesheet" href="/hanabi.css" />
<script src="/hanabi.js" defer></script>
<script src="/pico.js" defer></script>
<link rel="stylesheet" href="/pico.css" />
<link rel="icon" href="/pico.svg" />

+ 11
- 5
pico.js Bestand weergeven

@@ -27,7 +27,7 @@ listen('login', ({detail}) => State.username = detail.value)
listen('state', ({detail}) => Object.assign(State, detail))
listen('post', ({detail}) => State.posts.push(detail))
listen('peerInfo', (e) => onPeerInfo(e))
const doNotLog = new Set(['login', 'state', 'post', 'peerInfo'])
const doNotLog = new Set(['login', 'state', 'post', 'peerInfo', 'join', 'leave'])

/*
*
@@ -410,7 +410,9 @@ const Chat = {
),
)),
),
/* we could make these into like tabs .... */
m(Media),
m(Hanabi),
)
},
}
@@ -441,10 +443,14 @@ const connect = (username) => {

if(message.online) {
const difference = (l1, l2) => l1.filter(u => !l2.includes(u))
difference(message.online, State.online).forEach(username =>
State.posts.push({ts: message.ts, value: `${username} joined`}))
difference(State.online, message.online).forEach(username =>
State.posts.push({ts: message.ts, value: `${username} left`}))
difference(message.online, State.online).forEach(username => {
State.posts.push({ts: message.ts, value: `${username} joined`})
signal({kind: 'join', username: username})
})
difference(State.online, message.online).forEach(username => {
State.posts.push({ts: message.ts, value: `${username} left`})
signal({kind: 'leave', username: username})
})
}

if(!doNotLog.has(message.kind)) {

Laden…
Annuleren
Opslaan