fuzzyworld

herd / files / src / game / Canvas.ts

Canvas.ts

Blame: the commit that last changed each line · file · history

1001e9ef 2026-05-17 Pavel Safronov1const GRASS_STEP = 40
2
3export class Canvas {
4 readonly el: HTMLCanvasElement
5 readonly ctx: CanvasRenderingContext2D
6 private _width = 0
7 private _height = 0
8 private _dpr = 1
9
10 // offscreen canvas for the static grass texture — regenerated on resize
11 private grassCanvas: HTMLCanvasElement | null = null
12
13 constructor(id: string) {
14 const el = document.getElementById(id)
15 if (!(el instanceof HTMLCanvasElement)) throw new Error(`#${id} is not a canvas`)
16 this.el = el
17 this.ctx = el.getContext('2d')!
18 this.resize()
19 window.addEventListener('resize', () => this.resize())
20 }
21
22 get width() { return this._width }
23 get height() { return this._height }
24 get dpr() { return this._dpr }
25
26 private resize() {
27 this._dpr = window.devicePixelRatio || 1
28 this._width = window.innerWidth
29 this._height = window.innerHeight
30 this.el.width = this._width * this._dpr
31 this.el.height = this._height * this._dpr
32 this.el.style.width = `${this._width}px`
33 this.el.style.height = `${this._height}px`
34 this.ctx.setTransform(this._dpr, 0, 0, this._dpr, 0, 0)
35 this.buildGrassTexture()
36 }
37
38 private buildGrassTexture() {
39 const oc = document.createElement('canvas')
40 oc.width = this._width
41 oc.height = this._height
42 const ctx = oc.getContext('2d')!
43
44 ctx.strokeStyle = 'rgba(60,110,30,0.3)'
45 ctx.lineWidth = 1
46 for (let x = 0; x < this._width; x += GRASS_STEP) {
47 for (let y = 0; y < this._height; y += GRASS_STEP) {
48 // two blades per cell, randomized once at build time
49 for (let b = 0; b < 2; b++) {
50 const bx = x + Math.random() * GRASS_STEP
51 const by = y + Math.random() * GRASS_STEP
52 ctx.beginPath()
53 ctx.moveTo(bx, by)
54 ctx.lineTo(bx + (Math.random() - 0.5) * 6, by - 4 - Math.random() * 5)
55 ctx.stroke()
56 }
57 }
58 }
59 this.grassCanvas = oc
60 }
61
62 drawField() {
63 const { ctx } = this
64 ctx.fillStyle = '#4a8c2a'
65 ctx.fillRect(0, 0, this._width, this._height)
66 if (this.grassCanvas) {
67 ctx.drawImage(this.grassCanvas, 0, 0)
68 }
69 }
70
71 clear() {
72 this.ctx.clearRect(0, 0, this._width, this._height)
73 }
74}