mirror of https://github.com/dirtbags/tanks.git
Add round visualizer
This commit is contained in:
parent
4441520048
commit
554d341d64
35
forftanks.c
35
forftanks.c
|
@ -1,20 +1,5 @@
|
||||||
/*
|
|
||||||
* This software has been authored by an employee or employees of Los
|
|
||||||
* Alamos National Security, LLC, operator of the Los Alamos National
|
|
||||||
* Laboratory (LANL) under Contract No. DE-AC52-06NA25396 with the U.S.
|
|
||||||
* Department of Energy. The U.S. Government has rights to use,
|
|
||||||
* reproduce, and distribute this software. The public may copy,
|
|
||||||
* distribute, prepare derivative works and publicly display this
|
|
||||||
* software without charge, provided that this Notice and any statement
|
|
||||||
* of authorship are reproduced on all copies. Neither the Government
|
|
||||||
* nor LANS makes any warranty, express or implied, or assumes any
|
|
||||||
* liability or responsibility for the use of this software. If
|
|
||||||
* software is modified to produce derivative works, such modified
|
|
||||||
* software should be clearly marked, so as not to confuse it with the
|
|
||||||
* version available from LANL.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
@ -40,6 +25,7 @@ struct forftank {
|
||||||
char color[8]; /* "#ff0088" */
|
char color[8]; /* "#ff0088" */
|
||||||
char name[50];
|
char name[50];
|
||||||
char *path;
|
char *path;
|
||||||
|
ino_t inode;
|
||||||
|
|
||||||
struct forf_stack _prog;
|
struct forf_stack _prog;
|
||||||
struct forf_value _progvals[CSTACK_SIZE];
|
struct forf_value _progvals[CSTACK_SIZE];
|
||||||
|
@ -244,7 +230,7 @@ ft_run_tank(struct tank *tank, struct forftank *ftank)
|
||||||
ret = forf_eval(&ftank->env);
|
ret = forf_eval(&ftank->env);
|
||||||
if (! ret) {
|
if (! ret) {
|
||||||
fprintf(stderr, "Error in %s: %s\n",
|
fprintf(stderr, "Error in %s: %s\n",
|
||||||
ftank->name,
|
ftank->path,
|
||||||
forf_error_str[ftank->env.error]);
|
forf_error_str[ftank->env.error]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -340,10 +326,20 @@ ft_read_tank(struct forftank *ftank,
|
||||||
|
|
||||||
ftank->path = path;
|
ftank->path = path;
|
||||||
|
|
||||||
|
/* Store inode */
|
||||||
|
{
|
||||||
|
struct stat s;
|
||||||
|
if (-1 == stat(path, &s)) {
|
||||||
|
ftank->inode = -1;
|
||||||
|
} else {
|
||||||
|
ftank->inode = s.st_ino;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* What is your name? */
|
/* What is your name? */
|
||||||
ret = ft_read_file(ftank->name, sizeof(ftank->name), path, "name");
|
ret = ft_read_file(ftank->name, sizeof(ftank->name), path, "name");
|
||||||
if (! ret) {
|
if (! ret) {
|
||||||
strncpy(ftank->name, path, sizeof(ftank->name));
|
snprintf(ftank->name, sizeof(ftank->name), "i:%lx", ftank->inode);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* What is your quest? */
|
/* What is your quest? */
|
||||||
|
@ -459,8 +455,9 @@ print_standings(FILE *f,
|
||||||
fprintf(f, ",\n");
|
fprintf(f, ",\n");
|
||||||
}
|
}
|
||||||
fprintf(f, " {\n");
|
fprintf(f, " {\n");
|
||||||
|
fprintf(f, " \"inode\": %ld,\n", ftanks[i].inode);
|
||||||
fprintf(f, " \"color\": \"%s\",\n", ftanks[i].color);
|
fprintf(f, " \"color\": \"%s\",\n", ftanks[i].color);
|
||||||
fprintf(f, " \"path\": \"%s\",\n", ftanks[i].path);
|
fprintf(f, " \"name\": \"%s\",\n", ftanks[i].name);
|
||||||
fprintf(f, " \"death\": \"%s\",\n", tanks[i].cause_death);
|
fprintf(f, " \"death\": \"%s\",\n", tanks[i].cause_death);
|
||||||
fprintf(f, " \"killer\": %d,\n", killer);
|
fprintf(f, " \"killer\": %d,\n", killer);
|
||||||
fprintf(f, " \"kills\": %d,\n", kills);
|
fprintf(f, " \"kills\": %d,\n", kills);
|
||||||
|
|
|
@ -6,6 +6,7 @@ const Second = 1000 * Millisecond
|
||||||
const Minute = 60 * Second
|
const Minute = 60 * Second
|
||||||
const TankRPM = 1
|
const TankRPM = 1
|
||||||
const TurretRPM = 4
|
const TurretRPM = 4
|
||||||
|
const FPS = 12
|
||||||
|
|
||||||
function deg2rad(angle) {
|
function deg2rad(angle) {
|
||||||
return angle*Math.TAU/360;
|
return angle*Math.TAU/360;
|
||||||
|
@ -21,12 +22,12 @@ function update(ctx) {
|
||||||
let width = document.querySelector(`[name=s${i}w]`).value
|
let width = document.querySelector(`[name=s${i}w]`).value
|
||||||
let turret = document.querySelector(`[name=s${i}t]`).checked
|
let turret = document.querySelector(`[name=s${i}t]`).checked
|
||||||
|
|
||||||
sensors[i] = [
|
sensors[i] = {
|
||||||
Math.min(range, 100),
|
range: Math.min(range, 100),
|
||||||
deg2rad(angle % 360),
|
angle: deg2rad(angle % 360),
|
||||||
deg2rad(width % 360),
|
width: deg2rad(width % 360),
|
||||||
turret,
|
turret: turret,
|
||||||
]
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let tankRevs = -TankRPM * (Date.now() / Minute)
|
let tankRevs = -TankRPM * (Date.now() / Minute)
|
||||||
|
@ -81,16 +82,34 @@ function formSubmit(event) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Upload files
|
// Upload files
|
||||||
|
let pending = 0
|
||||||
|
let errors = 0
|
||||||
|
let begin = performance.now()
|
||||||
for (let k in files) {
|
for (let k in files) {
|
||||||
let url = new URL(k, apiURL)
|
let url = new URL(k, apiURL)
|
||||||
let opts = {
|
let opts = {
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
body: files[k],
|
body: files[k],
|
||||||
}
|
}
|
||||||
|
pending += 1
|
||||||
fetch(url, opts)
|
fetch(url, opts)
|
||||||
.then(resp => {
|
.then(resp => {
|
||||||
|
pending -= 1
|
||||||
if (!resp.ok) {
|
if (!resp.ok) {
|
||||||
console.error("OH NO")
|
errors += 1
|
||||||
|
}
|
||||||
|
if (pending == 0) {
|
||||||
|
let duration = (performance.now() - begin).toPrecision(2)
|
||||||
|
let debug = document.querySelector("#debug")
|
||||||
|
if (debug) {
|
||||||
|
let msg = `tank uploaded in ${duration}ms`
|
||||||
|
if (errors > 0) {
|
||||||
|
msg = msg + `; ${errors} errors`
|
||||||
|
}
|
||||||
|
debug.textContent = msg
|
||||||
|
|
||||||
|
setTimeout(() => debug.textContent = "", 2 * Second)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -145,7 +164,7 @@ function init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
update(ctx)
|
update(ctx)
|
||||||
setInterval(() => update(ctx), Second / 20)
|
setInterval(() => update(ctx), Second / FPS)
|
||||||
}
|
}
|
||||||
|
|
||||||
init()
|
init()
|
||||||
|
|
|
@ -59,7 +59,7 @@
|
||||||
<input type="color" name="color" value="#cccccc" required>
|
<input type="color" name="color" value="#cccccc" required>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<input type="submit" value="Submit">
|
<input type="submit" value="Upload">
|
||||||
|
|
||||||
<div id="debug"></div>
|
<div id="debug"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -0,0 +1,70 @@
|
||||||
|
import {Tank} from "./tank.mjs"
|
||||||
|
|
||||||
|
const Millisecond = 1
|
||||||
|
const Second = 1000
|
||||||
|
const FPS = 12
|
||||||
|
|
||||||
|
export class Player {
|
||||||
|
constructor(ctx) {
|
||||||
|
this.ctx = ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
load(game) {
|
||||||
|
this.ctx.canvas.width = game.field[0]
|
||||||
|
this.ctx.canvas.height = game.field[1]
|
||||||
|
|
||||||
|
this.tanks = []
|
||||||
|
for (let tankDef of game.tanks) {
|
||||||
|
let tank = new Tank(this.ctx, tankDef.color, tankDef.sensors)
|
||||||
|
this.tanks.push(tank)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.rounds = game.rounds
|
||||||
|
|
||||||
|
this.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
start(frameno = 0) {
|
||||||
|
if (!this.loop_id) {
|
||||||
|
this.loop_id = setInterval(
|
||||||
|
() => this.update(),
|
||||||
|
Second / FPS,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
this.frameno = frameno
|
||||||
|
}
|
||||||
|
|
||||||
|
stop() {
|
||||||
|
if (this.loop_id) {
|
||||||
|
clearInterval(this.loop_id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
update() {
|
||||||
|
let frame = this.rounds[this.frameno]
|
||||||
|
if (!frame) {
|
||||||
|
this.stop()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height)
|
||||||
|
|
||||||
|
// Update and draw craters first
|
||||||
|
for (let i in frame) {
|
||||||
|
let tank = this.tanks[i]
|
||||||
|
tank.set_state(...frame[i])
|
||||||
|
tank.draw_crater()
|
||||||
|
}
|
||||||
|
// Then sensors
|
||||||
|
for (let tank of this.tanks) {
|
||||||
|
tank.draw_wrap_sensors()
|
||||||
|
}
|
||||||
|
// Then tanks
|
||||||
|
for (let tank of this.tanks) {
|
||||||
|
tank.draw_tank()
|
||||||
|
}
|
||||||
|
|
||||||
|
this.frameno += 1
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Tank Round</title>
|
||||||
|
<link rel="stylesheet" href="style.css">
|
||||||
|
<script src="round.mjs" type="module"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Tank Round</h1>
|
||||||
|
|
||||||
|
<div id="game_box">
|
||||||
|
<canvas id="battlefield"></canvas>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table id="results">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th></th>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Score</th>
|
||||||
|
<th>Death</th>
|
||||||
|
<th>Killer</th>
|
||||||
|
<th>Error</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody></tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,41 @@
|
||||||
|
import {Player} from "./player.mjs"
|
||||||
|
|
||||||
|
function results(round) {
|
||||||
|
let tbody = document.querySelector("#results tbody")
|
||||||
|
|
||||||
|
for (let tank of round.tanks) {
|
||||||
|
let tr = tbody.appendChild(document.createElement("tr"))
|
||||||
|
|
||||||
|
let tdSwatch = tr.appendChild(document.createElement("td"))
|
||||||
|
tdSwatch.class = "swatch"
|
||||||
|
tdSwatch.style.backgroundColor = tank.color
|
||||||
|
tdSwatch.textContent = "#"
|
||||||
|
|
||||||
|
let tdName = tr.appendChild(document.createElement("td"))
|
||||||
|
tdName.textContent = tank.name
|
||||||
|
|
||||||
|
tr.appendChild(document.createElement("td")).textContent = tank.kills
|
||||||
|
tr.appendChild(document.createElement("td")).textContent = tank.death
|
||||||
|
tr.appendChild(document.createElement("td")).textContent = round.tanks[tank.killer]?.name
|
||||||
|
tr.appendChild(document.createElement("td")).textContent = `${tank.error} @${tank.errorPos}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function init() {
|
||||||
|
let canvas = document.querySelector("#battlefield")
|
||||||
|
let ctx = canvas.getContext("2d")
|
||||||
|
let player = new Player(ctx)
|
||||||
|
|
||||||
|
let indexResp = await fetch("rounds/index.json")
|
||||||
|
let index = await indexResp.json()
|
||||||
|
|
||||||
|
let recentFn = index[index.length - 1]
|
||||||
|
console.log(recentFn)
|
||||||
|
|
||||||
|
let roundResp = await fetch(`rounds/${recentFn}`)
|
||||||
|
let round = await roundResp.json()
|
||||||
|
player.load(round)
|
||||||
|
results(round)
|
||||||
|
}
|
||||||
|
|
||||||
|
init()
|
|
@ -185,20 +185,14 @@ dd {
|
||||||
border: 2px solid green;
|
border: 2px solid green;
|
||||||
}
|
}
|
||||||
|
|
||||||
.solved {
|
table#results {
|
||||||
text-decoration: line-through;
|
border-collapse: collapse;
|
||||||
}
|
}
|
||||||
|
table#results td {
|
||||||
table.pollster {
|
padding: 0.4em 1em;
|
||||||
margin-left: 5em;
|
|
||||||
}
|
}
|
||||||
|
table#results tr:nth-child(even) {
|
||||||
table.pollster td {
|
background-color: #343;
|
||||||
padding: 2px 1em 2px 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.pollster thead {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.swatch {
|
.swatch {
|
||||||
|
|
25
www/tank.mjs
25
www/tank.mjs
|
@ -15,15 +15,16 @@ export class Tank {
|
||||||
if (! s) {
|
if (! s) {
|
||||||
this.sensors[i] = [0,0,0,0]
|
this.sensors[i] = [0,0,0,0]
|
||||||
} else {
|
} else {
|
||||||
|
let r = s.range
|
||||||
// r, angle, width, turret
|
// r, angle, width, turret
|
||||||
this.sensors[i] = [
|
this.sensors[i] = {
|
||||||
s[0], // Center angle
|
range: s.range,
|
||||||
s[1] - s[2]/2, // Left border angle
|
beg: s.angle - s.width/2,
|
||||||
s[1] + s[2]/2, // Right border angle
|
end: s.angle + s.width/2,
|
||||||
s[3]?1:0, // On turret?
|
turret: s.turret,
|
||||||
]
|
}
|
||||||
if (s[0] > this.maxlen) {
|
if (s.range > this.maxlen) {
|
||||||
this.maxlen = s[0]
|
this.maxlen = s.range
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -92,8 +93,11 @@ export class Tank {
|
||||||
this.ctx.lineWidth = 1
|
this.ctx.lineWidth = 1
|
||||||
for (let i in this.sensors) {
|
for (let i in this.sensors) {
|
||||||
var s = this.sensors[i]
|
var s = this.sensors[i]
|
||||||
var adj = this.turret * s[3]
|
|
||||||
|
|
||||||
|
this.ctx.save()
|
||||||
|
if (s.turret) {
|
||||||
|
this.ctx.rotate(this.turret)
|
||||||
|
}
|
||||||
if (this.sensor_state & (1 << i)) {
|
if (this.sensor_state & (1 << i)) {
|
||||||
// Sensor is triggered
|
// Sensor is triggered
|
||||||
this.ctx.strokeStyle = "#000"
|
this.ctx.strokeStyle = "#000"
|
||||||
|
@ -102,9 +106,10 @@ export class Tank {
|
||||||
}
|
}
|
||||||
this.ctx.beginPath()
|
this.ctx.beginPath()
|
||||||
this.ctx.moveTo(0, 0)
|
this.ctx.moveTo(0, 0)
|
||||||
this.ctx.arc(0, 0, s[0], s[1] + adj, s[2] + adj, false)
|
this.ctx.arc(0, 0, s.range, s.beg, s.end, false)
|
||||||
this.ctx.closePath()
|
this.ctx.closePath()
|
||||||
this.ctx.stroke()
|
this.ctx.stroke()
|
||||||
|
this.ctx.restore()
|
||||||
}
|
}
|
||||||
|
|
||||||
this.ctx.restore()
|
this.ctx.restore()
|
||||||
|
|
Loading…
Reference in New Issue