tanks

Blow up enemy tanks using code
git clone https://git.woozle.org/neale/tanks.git

tanks / www
Neale Pickett  ·  2024-12-04

designer.mjs

  1import {Tank} from "./tank.mjs"
  2
  3Math.TAU = Math.PI * 2
  4const Millisecond = 1
  5const Second = 1000 * Millisecond
  6const Minute = 60 * Second
  7const TankRPM = 1
  8const TurretRPM = 4
  9const FPS = 12
 10
 11function deg2rad(angle) {
 12    return angle*Math.TAU/360;
 13}
 14
 15function debug(text) {
 16    let el = document.querySelector("#debug")
 17    if (el) {
 18        el.textContent = text
 19        setTimeout(() => el.textContent = "", 2 * Second)
 20    }
 21}
 22
 23function update(ctx) {
 24    let color = document.querySelector("[name=color]").value
 25    let sensors = []
 26
 27    for (let i = 0; i < 10; i += 1) {
 28        let range = document.querySelector(`[name=s${i}r]`).value
 29        let angle = document.querySelector(`[name=s${i}a]`).value
 30        let width = document.querySelector(`[name=s${i}w]`).value
 31        let turret = document.querySelector(`[name=s${i}t]`).checked
 32            
 33        sensors[i] = {
 34            range: Math.min(range, 100),
 35            angle: deg2rad(angle % 360),
 36            width: deg2rad(width % 360),
 37            turret: turret,
 38        }
 39    }
 40
 41    let tankRevs = -TankRPM * (Date.now() / Minute)
 42    let turretRevs = TurretRPM * (Date.now() / Minute)
 43    let tank = new Tank(ctx, color, sensors);
 44    tank.set_state(
 45        100, 100,
 46        (tankRevs * Math.TAU) % Math.TAU,
 47        (turretRevs * Math.TAU) % Math.TAU,
 48        0,
 49        0,
 50    )
 51    ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height)
 52    tank.draw_sensors()
 53    tank.draw_tank()
 54}
 55
 56async function formSubmit(event) {
 57    event.preventDefault()
 58
 59    let formData = new FormData(event.target)
 60    for (let [k, v] of formData.entries()) {
 61        localStorage.setItem(k, v)
 62    }
 63
 64    let files = {
 65        name: formData.get("name"),
 66        color: formData.get("color"),
 67        author: formData.get("author"),
 68        program: formData.get("program"),
 69    }
 70    for (let i = 0; i < 10; i++) {
 71        let r = formData.get(`s${i}r`) || 0
 72        let a = formData.get(`s${i}a`) || 0
 73        let w = formData.get(`s${i}w`) || 0
 74        let t = (formData.get(`s${i}t`) == "on") ? 1 : 0
 75        files[`sensor${i}`] = `${r} ${a} ${w} ${t}`
 76    }
 77
 78    let id = formData.get("id")
 79    let apiURL = new URL(`tanks/${id}/`, location)
 80
 81    // Did the submit button have a "data-script" attribute?
 82    if (event.submitter.dataset.script !== undefined) {
 83        await navigator.clipboard.writeText(`#! /bin/sh
 84
 85curl -X PUT -d '${files.name}' ${apiURL}name
 86curl -X PUT -d '${files.color}' ${apiURL}color
 87curl -X PUT -d '${files.author}' ${apiURL}author
 88curl -X PUT -d '${files.sensor0}' ${apiURL}sensor0
 89curl -X PUT -d '${files.sensor1}' ${apiURL}sensor1
 90curl -X PUT -d '${files.sensor2}' ${apiURL}sensor2
 91curl -X PUT -d '${files.sensor3}' ${apiURL}sensor3
 92curl -X PUT -d '${files.sensor4}' ${apiURL}sensor4
 93curl -X PUT -d '${files.sensor5}' ${apiURL}sensor5
 94curl -X PUT -d '${files.sensor6}' ${apiURL}sensor6
 95curl -X PUT -d '${files.sensor7}' ${apiURL}sensor7
 96curl -X PUT -d '${files.sensor8}' ${apiURL}sensor8
 97curl -X PUT -d '${files.sensor9}' ${apiURL}sensor9
 98curl -X PUT --data-binary @- ${apiURL}program <<'EOD'
 99${files.program}
100EOD
101`)
102        debug("Upload script copied to clipboard.")
103        return
104    }
105
106    // Upload files
107    let pending = 0
108    let errors = 0
109    let begin = performance.now()
110    for (let k in files) {
111        let url = new URL(k, apiURL)
112        let opts = {
113            method: "PUT",
114            body: files[k],
115        }
116        pending += 1
117        fetch(url, opts)
118        .then(resp => {
119            pending -= 1
120            if (!resp.ok) {
121                errors += 1
122            }
123            if (pending == 0) {
124                let duration = (performance.now() - begin).toPrecision(2)
125                let msg = `tank uploaded in ${duration}ms`
126                if (errors > 0) {
127                    msg = msg + `; ${errors} errors`
128                }
129                debug(msg)
130            }
131        })
132    }
133}
134
135export function init() {
136    let canvas = document.querySelector("#design")
137    let ctx = canvas.getContext("2d")
138    canvas.width = 200
139    canvas.height = 200
140
141    let form = document.querySelector("form#upload")
142    form.addEventListener("submit", formSubmit)
143
144    let tbody = document.querySelector("#sensors tbody")
145    for (let i = 0; i < 10; i++) {
146        let tr = tbody.appendChild(document.createElement("tr"))
147
148        tr.appendChild(document.createElement("td")).textContent = i
149
150        let range = tr.appendChild(document.createElement("td")).appendChild(document.createElement("input"))
151        range.name = `s${i}r`
152        range.type = "number"
153        range.min = 0
154        range.max = 100
155        range.value = 0
156
157        let angle = tr.appendChild(document.createElement("td")).appendChild(document.createElement("input"))
158        angle.name = `s${i}a`
159        angle.type = "number"
160        angle.min = -360
161        angle.max = 360
162
163        let width = tr.appendChild(document.createElement("td")).appendChild(document.createElement("input"))
164        width.name = `s${i}w`
165        width.type = "number"
166        width.min = -360
167        width.max = 360
168
169        let turret = tr.appendChild(document.createElement("td")).appendChild(document.createElement("input"))
170        turret.name = `s${i}t`
171        turret.type = "checkbox"
172    }
173
174    // Load in previous values
175    for (let e of form.querySelectorAll("[name]")) {
176        let v = localStorage.getItem(e.name)
177        if (v !== undefined) {
178            e.checked = (v == "on")
179            e.value = v
180        }
181    }
182
183    update(ctx)
184    setInterval(() => update(ctx), Second / FPS)
185}
186