DEV Community

Tomasz Wegrzanowski
Tomasz Wegrzanowski

Posted on

3 2

Open Source Adventures: Episode 25: Imba 2 Game of Life

I want to port a few more Imba 1 apps to Imba 2.

This one is very simple, just another Game of Life program. It's fairly straightforward, but we'll need to adapt it a bit, as Imba 1 version used components inheriting from svg:g and Imba 2 can't do this, as web components don't support that yet.

Here's the original source code, and here you can see the app in action.

Imba 1 app.imba

let def countNeighbours(cells, x, y)
  let count = 0
  for i, cell of cells
    if !cell:state
      continue
    let dx = Math.abs(cell:x - x)
    let dy = Math.abs(cell:y - y)
    if Math.max(dx, dy) == 1
      count += 1
  return count

let def runStep(cells)
  let nextCells = []
  for i, cell of cells
    let n = countNeighbours(cells, cell:x, cell:y)
    let nextState = (n == 3 || (cell:state && n == 2))
    nextCells.push({x: cell:x, y: cell:y, state: nextState})
  return nextCells

tag CellTag < svg:g
  def onclick
    data:state = !data:state
    trigger("pause")

  def render
    let visualStartX = 20 * data:x + 1
    let visualStartY = 20 * data:y + 1

    <self>
      <svg:rect .alive=(data:state) .dead=(!data:state) x=visualStartX y=visualStartY height=18 width=18>

tag App
  def setup
    let sizex = 30
    let sizey = 30
    @cells = []
    for x in [0..sizex]
      for y in [0..sizey]
        @cells.push({ x: x, y: y, state: Math.random() < 0.2 })

  def step
    @cells = runStep(@cells)

  def mount
    setInterval(&,100) do
      if @playing
        step
      Imba.commit

  def play
    @playing = true

  def pause
    @playing = false

  def onpause
    pause

  def render
    <self>
      <header>
        "Game of Life"
      <svg:svg>
        for cell in @cells
          <CellTag[cell]>
      <div.buttons>
        if @playing
          <button :click.pause>
            "Pause"
        else
          <button :click.step>
            "Step"
          <button :click.play>
            "Play"

Imba.mount <App>
Enter fullscreen mode Exit fullscreen mode

Imba 1 app.scss

@import 'normalize-scss';
@include normalize();

.App {
  header {
    font-size: 64px;
    text-align: center;
  }

  svg {
    height: 600px;
    width: 600px;
    background-color: #aaa;
    margin: auto;
    display: block;
  }

  .dead {
    fill: #844;
  }

  .alive {
    fill: #3f3;
  }

  .buttons {
    text-align: center;
  }
  button {
    margin: 0.5em;
  }
}
Enter fullscreen mode Exit fullscreen mode

Imba 2 app.imba

def countNeighbours(cells, x, y)
  let count = 0
  for cell of cells
    if !cell.state
      continue
    let dx = Math.abs(cell.x - x)
    let dy = Math.abs(cell.y - y)
    if Math.max(dx, dy) == 1
      count += 1
  count

def runStep(cells)
  let nextCells = []
  for cell of cells
    let n = countNeighbours(cells, cell.x, cell.y)
    let nextState = (n == 3 || (cell.state && n == 2))
    nextCells.push({x: cell.x, y: cell.y, state: nextState})
  nextCells

tag cell
  prop data

  def onclick
    data.state = !data.state
    emit("pause")

  def render
    let visualStartX = 20 * data.x + 1
    let visualStartY = 20 * data.y + 1

    <self[left:{visualStartX}px top:{visualStartY}px] .alive=(data.state) .dead=(!data.state) @click.onclick>

  css
    position: absolute
    width: 18px
    height: 18px
    &.dead
      background-color: #864
    &.alive
      background-color: #3f3

tag app
  prop cells
  prop playing = true

  def setup
    let sizex = 30
    let sizey = 30
    cells = []
    for x in [0 ... sizex]
      for y in [0 ... sizey]
        cells.push({ x: x, y: y, state: Math.random() < 0.2 })

  def step
    cells = runStep(cells)

  def mount
    imba.setInterval(&,100) do
      if playing
        step()

  def play
    playing = true

  def pause
    playing = false

  def render
    <self>
      <header>
        "Game of Life"
      <div.board>
        for cell in cells
          <cell data=cell @pause.pause>
      <div.buttons>
        if playing
          <button @click.pause>
            "Pause"
        else
          <button @click.step>
            "Step"
          <button @click.play>
            "Play"

  css
    header
      font-size: 64px
      text-align: center
    .board
      position: relative
      height: 600px
      width: 600px
      background-color: #aaa
      margin: auto
    .buttons
      text-align: center
    button
      margin: 0.5em

imba.mount <app>
Enter fullscreen mode Exit fullscreen mode

The main issue here is that we can't use SVG directly. We could make a ton of tiny <svg>s, each with one <rect>, but at that point we might as well use <div>s for components, and position: absolute them.

Some minor things:

  • event model changed, and it doesn't look like we can simply define onX, we need to bind every such event manually
  • for syntax changed from Imba 1

Source code

Source code is in imba2-game-of-life repository.

You can also see the live version here.

Coming next

While we didn't do anything too fancy, I think it's a good time to stop. In the next episode I'll give some thoughts about Imba 2.

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

Top comments (0)

nextjs tutorial video

Youtube Tutorial Series πŸ“Ί

So you built a Next.js app, but you need a clear view of the entire operation flow to be able to identify performance bottlenecks before you launch. But how do you get started? Get the essentials on tracing for Next.js from @nikolovlazar in this video series πŸ‘€

Watch the Youtube series