JSFiddle - React, Tailwind, and code Playground

author(s): Tao Xin

HTML

<script type="text/javascript" src="https://cdn.jsdelivr.net/gh/vanjs-org/van/public/van-1.6.1.nomodule.min.js"></script>
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/[email protected]/dist/van-x.nomodule.min.js"></script>

JavaScript

const {a, b, button, div, h1, li, p, span, ul} = van.tags

const Game = () => {
  const time = van.state(60), score = van.state(0), inGame = van.state(false), items = vanX.reactive([])
  const fps = 60, height = 400, frameFns = Array(fps * time.val).fill().map(() => [])
  let curFrame = 0

  const Item = ({val: v}, deleter) => {
    const x = Math.floor(Math.random() * (document.body.clientWidth - 42)), y = van.state(0)
    let deleted = false
    v.removing = false
    van.derive(() => v.removing &&
      nextFrames(Math.floor(0.3 * fps)).then(() => (deleted = true, deleter())))
    ;(async () => {
      do { await nextFrames(1) } while (!deleted && (y.val += v.speed) <= height)
      v.removing || deleter()
    })()
    return span({
      class: "item",
      style: () => `left: ${x}px; bottom: ${y.val}px; opacity: ${v.removing ? 0 : 1};`,
      onclick: () => inGame.val && !v.removing &&
        frameFns[curFrame].push(() => (v.removing = v.msg, v.action())),
    }, v.icon, () => v.removing ? span({class: "msg " + (v.bad ? "bad": "good")}, v.removing) : "")
  }

  const itemTypes = [
    {icon: "πŸ‘", speed: 5, n: 60, msg: "+1", action: () => ++score.val},
    {icon: "πŸš€", speed: 10, n: 12, msg: "+10", action: () => score.val += 10},
    {icon: "πŸ‘Ž", speed: 3, n: 60, msg: "-5", bad: true, action: () => score.val -= 5},
    {icon: "🐌", speed: 5, n: 6, msg: "Slowed", action: () => items.forEach(it => it.speed /= 2)},
    {icon: "πŸ’£", speed: 3, n: 60, msg: "BOOM!", bad: true, action: () =>
      items.forEach(it => it.removing = "BOOM!")},
  ]

  const begin = () => {
    setInterval(() => {
      if (!inGame.val) return
      for (const fn of frameFns[curFrame]) fn()
      ++curFrame % fps === 0 && --time.val
      curFrame === frameFns.length && end()
    }, 1000 / fps)
    inGame.val = true
    for (const type of itemTypes)
      for (let i = 0; i < type.n; ++i)
        frameFns[Math.floor(Math.random() * frameFns.length)].push(() => items.push({...type}))
  }
 ...