aboutsummaryrefslogtreecommitdiff
path: root/src/spectate/main.ts
blob: 422a2c6146063441d924004da9bfbe66e1a93097 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
/// <reference lib="dom" />

const ws = new WebSocket("/events")

type Packet = "tick"
    | { pos: { id: number, x: number, y: number } }
    | { game: { width: number, height: number } }
    | { player: { id: number, name: string } }
    | { die: number[] }

class Snake {
    parts: { x: number, y: number, dx: number, dy: number }[] = []
    color: string
    dead = false
    constructor(public name: string) {
        this.color = name_color(name)
    }
    add_part(x: number, y: number) {
        if (!this.parts.length) return this.parts.push({ x, y, dx: 0, dy: 0 })
        const last = this.parts[this.parts.length - 1]
        let dx = x - last.x, dy = y - last.y;
        if (x > last.x + 1 || x < last.x - 1) dx *= -1, dx /= Math.abs(dx)
        if (y > last.y + 1 || y < last.y - 1) dy *= -1, dy /= Math.abs(dy)
        this.parts.push({ x, y, dx, dy })
    }
}

let size = 0
let tick_anim = 0;
const snakes = new Map<number, Snake>()

let canvas: HTMLCanvasElement
let ctx: CanvasRenderingContext2D

document.addEventListener("DOMContentLoaded", () => {
    canvas = document.createElement("canvas")
    ctx = canvas.getContext("2d")!
    document.body.append(canvas)
    canvas.width = 1000;
    canvas.height = 1000;
    redraw()
})

function redraw() {
    ctx.fillStyle = "black"
    ctx.fillRect(0, 0, canvas.width, canvas.height);

    ctx.save()
    const scale = canvas.width / size
    ctx.scale(scale, scale)
    for (let x = 0; x < size; x++) {
        for (let y = 0; y < size; y++) {
            ctx.strokeStyle = "grey"
            ctx.lineWidth = 0.02
            ctx.strokeRect(x, y, 1, 1)
        }
    }
    ctx.translate(0.5, 0.5)
    for (const [xo, yo] of [[0, 0], [-size, 0], [size, 0], [0, -size], [0, size]]) {
        ctx.save()
        ctx.translate(xo, yo)
        for (const snake of snakes.values()) {
            ctx.beginPath();
            for (let i = 0; i < snake.parts.length; i++) {
                const p = snake.parts[i];
                ctx.moveTo(p.x - p.dx, p.y - p.dy)
                if (i == snake.parts.length - 1)
                    ctx.lineTo(tick_lerp(p.x - p.dx, p.x), tick_lerp(p.y - p.dy, p.y))
                else ctx.lineTo(p.x, p.y)
            }
            ctx.lineCap = "round"
            ctx.lineJoin = "round"
            if (snake.dead) ctx.lineWidth = tick_lerp(0.6, 0)
            else ctx.lineWidth = 0.6;
            ctx.strokeStyle = snake.color
            ctx.stroke()
        }
        ctx.restore()
    }
    ctx.restore()
    tick_anim += 0.1
    tick_anim = Math.min(1, tick_anim)
    requestAnimationFrame(redraw)
}

function tick_lerp(f: number, t: number) {
    return (1 - tick_anim) * f + tick_anim * t
}

function name_color(name: string): string {
    let hash = 0;
    for (let i = 0; i < name.length; i++) {
        hash = ((hash << 5) - hash) + name.charCodeAt(i);
        hash |= 0;
    }
    return `hsl(${hash % 360}deg 100% 50%)`;
}

ws.onerror = console.error
ws.onmessage = message => {
    const p = JSON.parse(message.data) as Packet
    console.log(p);
    if (p == "tick") {
        tick_anim = 0
        const d = []
        for (const [k, s] of snakes) {
            if (s.dead) d.push(k)
        }
        d.forEach(k => snakes.delete(k))
    } else if ("game" in p) {
        snakes.clear()
        size = p.game.width
    } else if ("player" in p) {
        snakes.set(p.player.id, new Snake(p.player.name))
    } else if ("pos" in p) {
        snakes.get(p.pos.id)?.add_part(p.pos.x, p.pos.y)
    } else if ("die" in p) {
        for (const d of p.die) {
            const s = snakes.get(d)
            if (s) s.dead = true
        }
    }
}