Skip to content
Hara

Part I — Draw the board

Before there is a game, there is a canvas and a few drawing commands. Start by loading studio.draw: it is the small host-facing library that waits for a canvas frame and submits a list of drawing commands to it.

The program below follows the complete first drawing path:

  1. node/start keeps one live drawing process running.
  2. draw/next-frame gives that process its canvas turn.
  3. draw/render sends opening-commands to the named canvas.

A canvas command is just data: [:line x1 y1 x2 y2 color width alpha]. Read the first vector as: draw a vertical line from (220, 170) to (220, 470), muted blue, two pixels wide, fully opaque.

draw/render is the only point at which those values become pixels. There is no board widget hiding behind this example: the four :line vectors below are the board.

Practicals — draw with commands

studio.draw is the Hara API for a kernel to communicate with the browser's Canvas 2D API. It does not contain Tic Tac Toe rules. Instead, the kernel asks for a frame with draw/next-frame, then uses draw/render to send explicit command data through the declared canvas capability.

Run this first program. It imports studio.draw, starts one live kernel node, and draws the four lines that make the board. Then change one command and run it again.

(ns+
  (:require [studio.draw :as draw]))

;; A value describing pixels—not pixels yet.
(defn opening-commands [width height]
  [[:grid 48 "rgba(67,247,231,.12)" 1]
   [:line 220 170 220 470 "#386779" 2 1]
   [:line 380 170 380 470 "#386779" 2 1]
   [:line 60 270 540 270 "#386779" 2 1]
   [:line 60 370 540 370 "#386779" 2 1]])

;; This is where the command data becomes a visible board.
(node/start
  (fn []
    (loop []
      (let [frame (co/await (draw/next-frame "canvas/background"))]
        (co/await (draw/render "canvas/background"
          {:type :canvas-2d
           :background "#03070d"
           :commands (opening-commands 960 600)}))
        (recur)))))

Choose one exercise, or skip to organising the game.

MOVE A LINE

Change both 220 values in the first line to 250. Run the file. The shared x keeps it vertical.

CHANGE A COLOUR

Replace "#386779" with "#ff78b3", then run again. The command is the whole instruction.

ADD A LINE

Copy one :line vector. Give its endpoints a shared x or y and observe the result.

DRAW A CIRCLE

Add [:circle 300 320 24 "#7dfff3" 1]. This is the same command-list idea, with a different primitive.

Drawing reference

:line[:line x1 y1 x2 y2 color width alpha]

Draw a straight segment. Four lines make the board grid.

:circle[:circle x y radius color alpha]

Draw a filled circle. Later, two circles make an O.

:rect[:rect x y width height color alpha]

Fill a rectangle for a panel, button, or board background.

:text[:text label x y color size]

Put a status label or instruction onto the canvas.

:grid[:grid spacing color width]

Draw a regular guide grid behind the game.

:mist[:mist x y radius color alpha]

Add a soft radial glow behind a piece or winning line.

Continue: organise the game files →