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