mirror of https://github.com/dirtbags/tanks.git
Go-based daemon w/HTTP; new designer
This commit is contained in:
parent
b1ab26e41f
commit
0b62a1ca25
|
@ -2,16 +2,6 @@
|
||||||
*#
|
*#
|
||||||
*.o
|
*.o
|
||||||
|
|
||||||
round-*.html
|
|
||||||
next-round
|
|
||||||
points
|
|
||||||
forftanks
|
forftanks
|
||||||
|
rounds
|
||||||
designer.cgi
|
tanks
|
||||||
upload.cgi
|
|
||||||
|
|
||||||
forf.html
|
|
||||||
summary.html
|
|
||||||
designer.html
|
|
||||||
intro.html
|
|
||||||
procs.html
|
|
||||||
|
|
16
ctanks.c
16
ctanks.c
|
@ -1,19 +1,3 @@
|
||||||
/*
|
|
||||||
* 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 <string.h>
|
#include <string.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
|
@ -445,10 +445,14 @@ print_standings(FILE *f,
|
||||||
fprintf(f, " \"tanks\": [\n");
|
fprintf(f, " \"tanks\": [\n");
|
||||||
for (int i = 0; i < ntanks; i += 1) {
|
for (int i = 0; i < ntanks; i += 1) {
|
||||||
int killer = -1;
|
int killer = -1;
|
||||||
|
int kills = 0;
|
||||||
for (int j = 0; j < ntanks; j += 1) {
|
for (int j = 0; j < ntanks; j += 1) {
|
||||||
if (tanks[i].killer == &(tanks[j])) {
|
if (tanks[i].killer == &(tanks[j])) {
|
||||||
killer = j;
|
killer = j;
|
||||||
}
|
}
|
||||||
|
if (tanks[j].killer == &(tanks[i])) {
|
||||||
|
kills += 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (i > 0) {
|
if (i > 0) {
|
||||||
|
@ -459,6 +463,7 @@ print_standings(FILE *f,
|
||||||
fprintf(f, " \"path\": \"%s\",\n", ftanks[i].path);
|
fprintf(f, " \"path\": \"%s\",\n", ftanks[i].path);
|
||||||
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, " \"errorPos\": %d,\n", ftanks[i].error_pos);
|
fprintf(f, " \"errorPos\": %d,\n", ftanks[i].error_pos);
|
||||||
fprintf(f, " \"error\": \"%s\",\n", forf_error_str[ftanks[i].env.error]);
|
fprintf(f, " \"error\": \"%s\",\n", forf_error_str[ftanks[i].env.error]);
|
||||||
fprintf(f, " \"sensors\": [\n");
|
fprintf(f, " \"sensors\": [\n");
|
||||||
|
|
73
run-tanks
73
run-tanks
|
@ -1,73 +0,0 @@
|
||||||
#! /bin/sh
|
|
||||||
|
|
||||||
if [ "$#" -gt 0 ]; then
|
|
||||||
tanks="$@"
|
|
||||||
else
|
|
||||||
echo "Usage: $0 tank1 tank2 [...]"
|
|
||||||
echo "Writes ./next-round and ./summary.html"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
TANKS_GAME=${TANKS_GAME:-forftanks}
|
|
||||||
NAV_HTML_INC=${NAV_HTML_INC:-./nav.html.inc} export NAV_HTML_INC
|
|
||||||
|
|
||||||
# Add wherever this lives to the search path
|
|
||||||
PATH=$PATH:$(dirname $0)
|
|
||||||
|
|
||||||
if [ -f next-round ]; then
|
|
||||||
next=$(cat next-round)
|
|
||||||
else
|
|
||||||
next=0
|
|
||||||
fi
|
|
||||||
expr $next + 1 > next-round
|
|
||||||
|
|
||||||
fn=$(printf "round-%04d.html" $next)
|
|
||||||
rfn=results$$.txt
|
|
||||||
|
|
||||||
# Clean up old games
|
|
||||||
ofn=$(printf "round-%04d.html" $(expr $next - 720))
|
|
||||||
echo "Removing $ofn"
|
|
||||||
rm -f $ofn
|
|
||||||
|
|
||||||
|
|
||||||
echo -n "Running round $next... "
|
|
||||||
cat <<EOF >$fn
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Tanks Round $next</title>
|
|
||||||
<link rel="stylesheet" href="//code.jquery.com/ui/1.11.0/themes/ui-darkness/jquery-ui.css" type="text/css">
|
|
||||||
<script src="//code.jquery.com/jquery-1.11.0.min.js"></script>
|
|
||||||
<script src="//code.jquery.com/ui/1.11.0/jquery-ui.min.js"></script>
|
|
||||||
<script type="application/javascript" src="tanks.js"></script>
|
|
||||||
<link rel="stylesheet" href="style.css" type="text/css">
|
|
||||||
<script type="application/javascript">
|
|
||||||
function go() {
|
|
||||||
start("battlefield",
|
|
||||||
// Start JSON data
|
|
||||||
EOF
|
|
||||||
$TANKS_GAME $tanks >>$fn 3>$rfn
|
|
||||||
cat <<EOF >>$fn
|
|
||||||
// end JSON data
|
|
||||||
);
|
|
||||||
}
|
|
||||||
window.onload = go;
|
|
||||||
</script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>Tanks Round $next</h1>
|
|
||||||
<div id="game_box"><canvas id="battlefield"></canvas></div>
|
|
||||||
<p><span id="fps">0</span> fps</p>
|
|
||||||
EOF
|
|
||||||
rank.awk $rfn >>$fn
|
|
||||||
rm -f $rfn
|
|
||||||
cat $NAV_HTML_INC >>$fn
|
|
||||||
cat <<EOF >>$fn
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
EOF
|
|
||||||
|
|
||||||
summary.awk $tanks > summary.html.$$ && mv summary.html.$$ summary.html
|
|
||||||
|
|
||||||
echo "done."
|
|
||||||
|
|
|
@ -0,0 +1,177 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var forftanksPath = flag.String("forftanks", "./forftanks", "path to forftanks executable")
|
||||||
|
var wwwDir = flag.String("www", "www", "path to www http content (ro)")
|
||||||
|
var tanksDir = flag.String("tanks", "tanks", "path to tanks state directories (rw)")
|
||||||
|
var roundsDir = flag.String("rounds", "rounds", "path to rounds storage (rw)")
|
||||||
|
var maxrounds = flag.Uint("maxrounds", 200, "number of rounds to store")
|
||||||
|
var maxSize = flag.Uint("maxsize", 8000 , "maximum uploaded file size")
|
||||||
|
var listenAddr = flag.String("listen", ":8080", "where to listen for incoming HTTP connections")
|
||||||
|
var roundDuration = flag.Duration("round", 1 * time.Minute, "Time to wait between each round")
|
||||||
|
|
||||||
|
type TankState struct {
|
||||||
|
dir string
|
||||||
|
roundsdir string
|
||||||
|
}
|
||||||
|
|
||||||
|
var validFilenames = []string{
|
||||||
|
"author",
|
||||||
|
"name",
|
||||||
|
"color",
|
||||||
|
"program",
|
||||||
|
"sensor0",
|
||||||
|
"sensor1",
|
||||||
|
"sensor2",
|
||||||
|
"sensor3",
|
||||||
|
"sensor4",
|
||||||
|
"sensor5",
|
||||||
|
"sensor6",
|
||||||
|
"sensor7",
|
||||||
|
"sensor8",
|
||||||
|
"sensor9",
|
||||||
|
}
|
||||||
|
func (ts *TankState) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
|
id := req.PathValue("id")
|
||||||
|
name := req.PathValue("name")
|
||||||
|
|
||||||
|
if req.ContentLength < 0 {
|
||||||
|
http.Error(w, "Length required", http.StatusLengthRequired)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if uint(req.ContentLength) > *maxSize {
|
||||||
|
http.Error(w, "Too large", http.StatusRequestEntityTooLarge)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tankDir := path.Join(ts.dir, id)
|
||||||
|
if tankDir == ts.dir {
|
||||||
|
http.Error(w, "Invalid tank ID", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
filename := path.Join(tankDir, name)
|
||||||
|
f, err := os.Create(filename)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
if _, err := io.Copy(f, req.Body); err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintln(w, "file written")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ts *TankState) WriteRound(now time.Time, round []byte) error {
|
||||||
|
dents, err := os.ReadDir(ts.roundsdir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for uint(len(dents)) > *maxrounds {
|
||||||
|
fn := path.Join(ts.roundsdir, dents[0].Name())
|
||||||
|
if err := os.Remove(fn); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
dents = dents[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
roundFn := fmt.Sprintf("%016x.json", now.Unix())
|
||||||
|
roundPath := path.Join(ts.roundsdir, roundFn)
|
||||||
|
if err := os.WriteFile(roundPath, round, 0644); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
rounds := make([]string, len(dents) + 1)
|
||||||
|
for i := 0; i < len(dents); i++ {
|
||||||
|
rounds[i] = dents[i].Name()
|
||||||
|
}
|
||||||
|
rounds[len(dents)] = roundFn
|
||||||
|
|
||||||
|
roundsJs, err := json.Marshal(rounds)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
idxFn := path.Join(ts.roundsdir, "index.json")
|
||||||
|
if err := os.WriteFile(idxFn, roundsJs, 0644); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ts *TankState) RunRound(now time.Time) error {
|
||||||
|
dents, err := os.ReadDir(ts.dir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
args := make([]string, 0, len(dents))
|
||||||
|
for _, dent := range dents {
|
||||||
|
if dent.IsDir() {
|
||||||
|
tankPath := path.Join(ts.dir, dent.Name())
|
||||||
|
args = append(args, tankPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(args) < 2 {
|
||||||
|
return fmt.Errorf("Not enough tanks for a round")
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := exec.Command(*forftanksPath, args...)
|
||||||
|
out, err := cmd.Output()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ts.WriteRound(now, out); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ts *TankState) RunForever() {
|
||||||
|
if err := ts.RunRound(time.Now()); err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for now := range time.Tick(*roundDuration) {
|
||||||
|
if err := ts.RunRound(now); err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
ts := &TankState{
|
||||||
|
dir: *tanksDir,
|
||||||
|
roundsdir: *roundsDir,
|
||||||
|
}
|
||||||
|
|
||||||
|
http.Handle("GET /", http.FileServer(http.Dir(*wwwDir)))
|
||||||
|
http.Handle("GET /rounds/", http.StripPrefix("/rounds/", http.FileServer(http.Dir(*roundsDir))))
|
||||||
|
http.Handle("PUT /tanks/{id}/{name}", ts)
|
||||||
|
|
||||||
|
go ts.RunForever()
|
||||||
|
|
||||||
|
log.Println("Listening on", *listenAddr)
|
||||||
|
http.ListenAndServe(*listenAddr, nil)
|
||||||
|
}
|
318
upload.cgi.c
318
upload.cgi.c
|
@ -1,318 +0,0 @@
|
||||||
/*
|
|
||||||
* 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/stat.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <ctype.h>
|
|
||||||
|
|
||||||
char *BASE_PATH = "";
|
|
||||||
|
|
||||||
struct {
|
|
||||||
char *name;
|
|
||||||
size_t size;
|
|
||||||
} entries[] = {
|
|
||||||
{
|
|
||||||
"name", 20}, {
|
|
||||||
"author", 80}, {
|
|
||||||
"color", 10}, {
|
|
||||||
"sensor0", 16}, {
|
|
||||||
"sensor1", 16}, {
|
|
||||||
"sensor2", 16}, {
|
|
||||||
"sensor3", 16}, {
|
|
||||||
"sensor4", 16}, {
|
|
||||||
"sensor5", 16}, {
|
|
||||||
"sensor6", 16}, {
|
|
||||||
"sensor7", 16}, {
|
|
||||||
"sensor8", 16}, {
|
|
||||||
"sensor9", 16}, {
|
|
||||||
"program", 16384}, {
|
|
||||||
NULL, 0}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
size_t inlen;
|
|
||||||
|
|
||||||
int
|
|
||||||
read_char()
|
|
||||||
{
|
|
||||||
if (inlen) {
|
|
||||||
inlen -= 1;
|
|
||||||
return getchar();
|
|
||||||
}
|
|
||||||
return EOF;
|
|
||||||
}
|
|
||||||
|
|
||||||
char
|
|
||||||
tonum(int c)
|
|
||||||
{
|
|
||||||
if ((c >= '0') && (c <= '9')) {
|
|
||||||
return c - '0';
|
|
||||||
}
|
|
||||||
if ((c >= 'a') && (c <= 'f')) {
|
|
||||||
return 10 + c - 'a';
|
|
||||||
}
|
|
||||||
if ((c >= 'A') && (c <= 'F')) {
|
|
||||||
return 10 + c - 'A';
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
char
|
|
||||||
read_hex()
|
|
||||||
{
|
|
||||||
int a = read_char();
|
|
||||||
int b = read_char();
|
|
||||||
|
|
||||||
return tonum(a) * 16 + tonum(b);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Read a key or a value. Since & and = aren't supposed to appear outside of boundaries, we can use the same function for both.
|
|
||||||
*/
|
|
||||||
size_t
|
|
||||||
read_item(char *str, size_t maxlen)
|
|
||||||
{
|
|
||||||
int c;
|
|
||||||
size_t pos = 0;
|
|
||||||
|
|
||||||
while (1) {
|
|
||||||
c = read_char();
|
|
||||||
switch (c) {
|
|
||||||
case EOF:
|
|
||||||
case '=':
|
|
||||||
case '&':
|
|
||||||
str[pos] = '\0';
|
|
||||||
return pos;
|
|
||||||
case '%':
|
|
||||||
c = read_hex();
|
|
||||||
break;
|
|
||||||
case '+':
|
|
||||||
c = ' ';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (pos < maxlen - 1) {
|
|
||||||
str[pos] = c;
|
|
||||||
pos += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t
|
|
||||||
copy_item(char *filename, size_t maxlen)
|
|
||||||
{
|
|
||||||
FILE *f;
|
|
||||||
char path[132];
|
|
||||||
int c;
|
|
||||||
size_t pos = 0;
|
|
||||||
|
|
||||||
snprintf(path, sizeof(path), "%s%05d.%s", BASE_PATH, getpid(), filename);
|
|
||||||
f = fopen(path, "w");
|
|
||||||
if (!f) {
|
|
||||||
/*
|
|
||||||
* Just send it to the bit bucket
|
|
||||||
*/
|
|
||||||
maxlen = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (1) {
|
|
||||||
c = read_char();
|
|
||||||
switch (c) {
|
|
||||||
case EOF:
|
|
||||||
case '=':
|
|
||||||
case '&':
|
|
||||||
if (f)
|
|
||||||
fclose(f);
|
|
||||||
return pos;
|
|
||||||
case '%':
|
|
||||||
c = read_hex();
|
|
||||||
break;
|
|
||||||
case '+':
|
|
||||||
c = ' ';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (pos < maxlen) {
|
|
||||||
fputc(c, f);
|
|
||||||
pos += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
croak(int code, char *msg)
|
|
||||||
{
|
|
||||||
int i;
|
|
||||||
char path[132];
|
|
||||||
|
|
||||||
for (i = 0; entries[i].name; i += 1) {
|
|
||||||
snprintf(path, sizeof(path), "%s%05d.%s", BASE_PATH, getpid(), entries[i].name);
|
|
||||||
unlink(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
printf("Status: %d %s\n", code, msg);
|
|
||||||
printf("Content-type: text/plain\n");
|
|
||||||
printf("\n");
|
|
||||||
printf("Error: %s\n", msg);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
main(int argc, char *argv[])
|
|
||||||
{
|
|
||||||
int sensor[10][5];
|
|
||||||
char key[20];
|
|
||||||
char token[40];
|
|
||||||
size_t len;
|
|
||||||
|
|
||||||
memset(sensor, 0, sizeof(sensor));
|
|
||||||
token[0] = '\0';
|
|
||||||
|
|
||||||
BASE_PATH = getenv("BASE_PATH");
|
|
||||||
if (!BASE_PATH) {
|
|
||||||
BASE_PATH = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
char *rm = getenv("REQUEST_METHOD");
|
|
||||||
|
|
||||||
if (!(rm && (0 == strcmp(rm, "POST")))) {
|
|
||||||
printf("405 Method not allowed\n");
|
|
||||||
printf("Allow: POST\n");
|
|
||||||
printf("Content-type: text/plain\n");
|
|
||||||
printf("\n");
|
|
||||||
printf("Error: I only speak POST\n");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
inlen = atoi(getenv("CONTENT_LENGTH"));
|
|
||||||
}
|
|
||||||
|
|
||||||
while (inlen) {
|
|
||||||
len = read_item(key, sizeof(key));
|
|
||||||
if (0 == strcmp(key, "token")) {
|
|
||||||
read_item(token, sizeof(token));
|
|
||||||
} else if ((3 == len) && ('s' == key[0])) {
|
|
||||||
/*
|
|
||||||
* sensor dealie, key = "s[0-9][rawt]"
|
|
||||||
*/
|
|
||||||
char val[5];
|
|
||||||
int n = key[1] - '0';
|
|
||||||
int i;
|
|
||||||
int p;
|
|
||||||
|
|
||||||
read_item(val, sizeof(val));
|
|
||||||
|
|
||||||
if (!(n >= 0) && (n <= 9)) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
i = atoi(val);
|
|
||||||
|
|
||||||
sensor[n][0] = 1;
|
|
||||||
switch (key[2]) {
|
|
||||||
case 'r':
|
|
||||||
p = 1;
|
|
||||||
break;
|
|
||||||
case 'a':
|
|
||||||
p = 2;
|
|
||||||
break;
|
|
||||||
case 'w':
|
|
||||||
p = 3;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
p = 4;
|
|
||||||
i = (val[0] != '\0');
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
sensor[n][p] = i;
|
|
||||||
} else {
|
|
||||||
int i;
|
|
||||||
|
|
||||||
for (i = 0; entries[i].name; i += 1) {
|
|
||||||
if (0 == strcmp(key, entries[i].name)) {
|
|
||||||
len = copy_item(key, entries[i].size);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Sanitize token
|
|
||||||
*/
|
|
||||||
{
|
|
||||||
char *p = token;
|
|
||||||
|
|
||||||
while (*p) {
|
|
||||||
if (!isalnum(*p)) {
|
|
||||||
*p = '_';
|
|
||||||
}
|
|
||||||
p += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ('\0' == token[0]) {
|
|
||||||
token[0] = '_';
|
|
||||||
token[1] = '\0';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Move files into their directory
|
|
||||||
*/
|
|
||||||
{
|
|
||||||
char path[132];
|
|
||||||
char dest[132];
|
|
||||||
struct stat st;
|
|
||||||
int i;
|
|
||||||
|
|
||||||
snprintf(path, sizeof(path), "%s%s/", BASE_PATH, token);
|
|
||||||
if (-1 == stat(path, &st))
|
|
||||||
return croak(422, "Invalid token");
|
|
||||||
if (!S_ISDIR(st.st_mode))
|
|
||||||
return croak(422, "Invalid token");
|
|
||||||
for (i = 0; entries[i].name; i += 1) {
|
|
||||||
snprintf(path, sizeof(path), "%s%05d.%s", BASE_PATH, getpid(), entries[i].name);
|
|
||||||
snprintf(dest, sizeof(dest), "%s%s/%s", BASE_PATH, token, entries[i].name);
|
|
||||||
rename(path, dest);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (i = 0; i < 10; i += 1) {
|
|
||||||
FILE *f;
|
|
||||||
|
|
||||||
if (sensor[i][0]) {
|
|
||||||
snprintf(dest, sizeof(dest), "%s%s/sensor%d", BASE_PATH, token, i);
|
|
||||||
f = fopen(dest, "w");
|
|
||||||
if (!f) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
fprintf(f, "%d %d %d %d\n", sensor[i][1], sensor[i][2], sensor[i][3], sensor[i][4]);
|
|
||||||
fclose(f);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
printf("Content-type: text/plain\n");
|
|
||||||
printf("\n");
|
|
||||||
printf("OK: Tank submitted!\n");
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
|
@ -0,0 +1,151 @@
|
||||||
|
import {Tank} from "./tank.mjs"
|
||||||
|
|
||||||
|
Math.TAU = Math.PI * 2
|
||||||
|
const Millisecond = 1
|
||||||
|
const Second = 1000 * Millisecond
|
||||||
|
const Minute = 60 * Second
|
||||||
|
const TankRPM = 1
|
||||||
|
const TurretRPM = 4
|
||||||
|
|
||||||
|
function deg2rad(angle) {
|
||||||
|
return angle*Math.TAU/360;
|
||||||
|
}
|
||||||
|
|
||||||
|
function update(ctx) {
|
||||||
|
let color = document.querySelector("[name=color]").value
|
||||||
|
let sensors = []
|
||||||
|
|
||||||
|
for (let i = 0; i < 10; i += 1) {
|
||||||
|
let range = document.querySelector(`[name=s${i}r]`).value
|
||||||
|
let angle = document.querySelector(`[name=s${i}a]`).value
|
||||||
|
let width = document.querySelector(`[name=s${i}w]`).value
|
||||||
|
let turret = document.querySelector(`[name=s${i}t]`).checked
|
||||||
|
|
||||||
|
sensors[i] = [
|
||||||
|
Math.min(range, 100),
|
||||||
|
deg2rad(angle % 360),
|
||||||
|
deg2rad(width % 360),
|
||||||
|
turret,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
let tankRevs = -TankRPM * (Date.now() / Minute)
|
||||||
|
let turretRevs = TurretRPM * (Date.now() / Minute)
|
||||||
|
let tank = new Tank(ctx, color, sensors);
|
||||||
|
tank.set_state(
|
||||||
|
100, 100,
|
||||||
|
(tankRevs * Math.TAU) % Math.TAU,
|
||||||
|
(turretRevs * Math.TAU) % Math.TAU,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height)
|
||||||
|
tank.draw_sensors()
|
||||||
|
tank.draw_tank()
|
||||||
|
}
|
||||||
|
|
||||||
|
function formSubmit(event) {
|
||||||
|
event.preventDefault()
|
||||||
|
|
||||||
|
let formData = new FormData(event.target)
|
||||||
|
for (let [k, v] of formData.entries()) {
|
||||||
|
localStorage.setItem(k, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
let files = {
|
||||||
|
name: formData.get("name"),
|
||||||
|
color: formData.get("color"),
|
||||||
|
author: formData.get("author"),
|
||||||
|
program: formData.get("program"),
|
||||||
|
}
|
||||||
|
for (let i = 0; i < 10; i++) {
|
||||||
|
let r = formData.get(`s${i}r`) || 0
|
||||||
|
let a = formData.get(`s${i}a`) || 0
|
||||||
|
let w = formData.get(`s${i}w`) || 0
|
||||||
|
let t = (formData.get(`s${i}t`) == "on") ? 1 : 0
|
||||||
|
files[`sensor${i}`] = `${r} ${a} ${w} ${t}`
|
||||||
|
}
|
||||||
|
|
||||||
|
let id = formData.get("id")
|
||||||
|
let apiURL = new URL(`tanks/${id}/`, location)
|
||||||
|
|
||||||
|
// Fill slots
|
||||||
|
for (let k in files) {
|
||||||
|
let v = files[k]
|
||||||
|
for (let e of document.querySelectorAll(`slot[name="${k}"]`)) {
|
||||||
|
e.textContent = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (let e of document.querySelectorAll("slot[name=apiurl]")) {
|
||||||
|
e.textContent = apiURL
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upload files
|
||||||
|
for (let k in files) {
|
||||||
|
let url = new URL(k, apiURL)
|
||||||
|
let opts = {
|
||||||
|
method: "PUT",
|
||||||
|
body: files[k],
|
||||||
|
}
|
||||||
|
fetch(url, opts)
|
||||||
|
.then(resp => {
|
||||||
|
if (!resp.ok) {
|
||||||
|
console.error("OH NO")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
let canvas = document.querySelector("#design")
|
||||||
|
let ctx = canvas.getContext("2d")
|
||||||
|
canvas.width = 200
|
||||||
|
canvas.height = 200
|
||||||
|
|
||||||
|
let form = document.querySelector("form#upload")
|
||||||
|
form.addEventListener("submit", formSubmit)
|
||||||
|
|
||||||
|
let tbody = document.querySelector("#sensors tbody")
|
||||||
|
for (let i = 0; i < 10; i++) {
|
||||||
|
let tr = tbody.appendChild(document.createElement("tr"))
|
||||||
|
|
||||||
|
tr.appendChild(document.createElement("td")).textContent = i
|
||||||
|
|
||||||
|
let range = tr.appendChild(document.createElement("td")).appendChild(document.createElement("input"))
|
||||||
|
range.name = `s${i}r`
|
||||||
|
range.type = "number"
|
||||||
|
range.min = 0
|
||||||
|
range.max = 100
|
||||||
|
range.value = 0
|
||||||
|
|
||||||
|
let angle = tr.appendChild(document.createElement("td")).appendChild(document.createElement("input"))
|
||||||
|
angle.name = `s${i}a`
|
||||||
|
angle.type = "number"
|
||||||
|
angle.min = -360
|
||||||
|
angle.max = 360
|
||||||
|
|
||||||
|
let width = tr.appendChild(document.createElement("td")).appendChild(document.createElement("input"))
|
||||||
|
width.name = `s${i}w`
|
||||||
|
width.type = "number"
|
||||||
|
width.min = -360
|
||||||
|
width.max = 360
|
||||||
|
|
||||||
|
let turret = tr.appendChild(document.createElement("td")).appendChild(document.createElement("input"))
|
||||||
|
turret.name = `s${i}t`
|
||||||
|
turret.type = "checkbox"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load in previous values
|
||||||
|
for (let e of form.querySelectorAll("[name]")) {
|
||||||
|
let v = localStorage.getItem(e.name)
|
||||||
|
if (v !== undefined) {
|
||||||
|
e.checked = (v == "on")
|
||||||
|
e.value = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
update(ctx)
|
||||||
|
setInterval(() => update(ctx), Second / 20)
|
||||||
|
}
|
||||||
|
|
||||||
|
init()
|
|
@ -0,0 +1,99 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Tanks</title>
|
||||||
|
<link rel="stylesheet" href="style.css">
|
||||||
|
<script src="designer.mjs" type="module"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Tanks</h1>
|
||||||
|
|
||||||
|
<h2>Upload a tank</h2>
|
||||||
|
<form id="upload">
|
||||||
|
<fieldset>
|
||||||
|
<legend><input type="text" name="name" placeholder="My tank name" required></legend>
|
||||||
|
<fieldset>
|
||||||
|
<legend>Program</legend>
|
||||||
|
<textarea name="program">30 40 set-speed!
|
||||||
|
0 sensor? { fire! } if
|
||||||
|
</textarea>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<fieldset>
|
||||||
|
<legend>Sensors</legend>
|
||||||
|
<table id="sensors">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<td>№</td>
|
||||||
|
<td>Range</td>
|
||||||
|
<td>Angle</td>
|
||||||
|
<td>Width</td>
|
||||||
|
<td>Turret?</td>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody></tbody>
|
||||||
|
</table>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<fieldset id="preview">
|
||||||
|
<legend>Preview</legend>
|
||||||
|
<div>
|
||||||
|
<canvas id="design"></canvas>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<label>ID:
|
||||||
|
<input type="text" name="id" placeholder="ID provided by game owner" required>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label>Author:
|
||||||
|
<input type="text" name="author" placeholder="Your name" required>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<input type="color" name="color" value="#cccccc" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<input type="submit" value="Submit">
|
||||||
|
|
||||||
|
<div id="debug"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
|
||||||
|
<h2>Upload using curl</h2>
|
||||||
|
<p>
|
||||||
|
You can use <samp>curl</samp> to upload your tank,
|
||||||
|
if you want.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
The server does no syntax checking on what you upload.
|
||||||
|
You'll have to wait until the next round to see if you made a mistake.
|
||||||
|
</p>
|
||||||
|
<pre lang="sh">
|
||||||
|
#! /bin/sh
|
||||||
|
|
||||||
|
curl -X PUT -d '<slot name="name">fred the tank</slot>' <slot name="apiurl">https://server/tanks/a1b2c3d4/</slot>name
|
||||||
|
curl -X PUT -d '<slot name="color">#ff0000</slot>' <slot name="apiurl">https://server/tanks/a1b2c3d4/</slot>color
|
||||||
|
curl -X PUT -d '<slot name="sensor0">50 0 10 1</slot>' <slot name="apiurl">https://server/tanks/a1b2c3d4/</slot>sensor0
|
||||||
|
curl -X PUT -d '<slot name="sensor1">360 0 5 0</slot>' <slot name="apiurl">https://server/tanks/a1b2c3d4/</slot>sensor1
|
||||||
|
curl -X PUT -d '<slot name="sensor2">0 0 0 0</slot>' <slot name="apiurl">https://server/tanks/a1b2c3d4/</slot>sensor2
|
||||||
|
curl -X PUT -d '<slot name="sensor3">0 0 0 0</slot>' <slot name="apiurl">https://server/tanks/a1b2c3d4/</slot>sensor3
|
||||||
|
curl -X PUT -d '<slot name="sensor4">0 0 0 0</slot>' <slot name="apiurl">https://server/tanks/a1b2c3d4/</slot>sensor4
|
||||||
|
curl -X PUT -d '<slot name="sensor5">0 0 0 0</slot>' <slot name="apiurl">https://server/tanks/a1b2c3d4/</slot>sensor5
|
||||||
|
curl -X PUT -d '<slot name="sensor6">0 0 0 0</slot>' <slot name="apiurl">https://server/tanks/a1b2c3d4/</slot>sensor6
|
||||||
|
curl -X PUT -d '<slot name="sensor7">0 0 0 0</slot>' <slot name="apiurl">https://server/tanks/a1b2c3d4/</slot>sensor7
|
||||||
|
curl -X PUT -d '<slot name="sensor8">0 0 0 0</slot>' <slot name="apiurl">https://server/tanks/a1b2c3d4/</slot>sensor8
|
||||||
|
curl -X PUT -d '<slot name="sensor9">0 0 0 0</slot>' <slot name="apiurl">https://server/tanks/a1b2c3d4/</slot>sensor9
|
||||||
|
curl -X PUT --data-binary @- <slot name="apiurl">https://server/tanks/a1b2c3d4/</slot>program <<'EOD'
|
||||||
|
<slot name="program">20 30 set-speed!</slot>
|
||||||
|
EOD
|
||||||
|
</pre>
|
||||||
|
</html>
|
|
@ -7,9 +7,7 @@ html {
|
||||||
body {
|
body {
|
||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
margin: 50px 0 0 110px;
|
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
max-width: 700px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**** heading ****/
|
/**** heading ****/
|
||||||
|
@ -17,15 +15,8 @@ body {
|
||||||
h1:first-child {
|
h1:first-child {
|
||||||
text-transform: lowercase;
|
text-transform: lowercase;
|
||||||
font-size: 1.6em;
|
font-size: 1.6em;
|
||||||
padding: 3px;
|
|
||||||
color: #2a2;
|
color: #2a2;
|
||||||
margin: 0 0 1em 70px;
|
border-bottom: #2a2 solid 2px;
|
||||||
}
|
|
||||||
|
|
||||||
h1:first-child:before {
|
|
||||||
color: #fff;
|
|
||||||
letter-spacing: -0.1em;
|
|
||||||
content: "Dirtbags: ";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*** left side bar ***/
|
/*** left side bar ***/
|
||||||
|
@ -75,6 +66,28 @@ nav li .active {
|
||||||
border-right: 4px solid #444;
|
border-right: 4px solid #444;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**** designer ****/
|
||||||
|
#upload fieldset {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 2em;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
#upload [name="program"] {
|
||||||
|
min-width: 25em;
|
||||||
|
min-height: 15em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#upload fieldset fieldset {
|
||||||
|
border-color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
#preview {
|
||||||
|
background: #333;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
/**** body ****/
|
/**** body ****/
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,166 @@
|
||||||
|
const craterPoints = 7
|
||||||
|
const craterAngle = Math.PI / craterPoints
|
||||||
|
|
||||||
|
export class Tank {
|
||||||
|
constructor(ctx, color, sensors) {
|
||||||
|
this.ctx = ctx
|
||||||
|
this.color = color
|
||||||
|
|
||||||
|
// Do all the yucky math up front
|
||||||
|
this.maxlen = 0
|
||||||
|
this.sensors = []
|
||||||
|
for (let i in sensors) {
|
||||||
|
let s = sensors[i]
|
||||||
|
|
||||||
|
if (! s) {
|
||||||
|
this.sensors[i] = [0,0,0,0]
|
||||||
|
} else {
|
||||||
|
// r, angle, width, turret
|
||||||
|
this.sensors[i] = [
|
||||||
|
s[0], // Center angle
|
||||||
|
s[1] - s[2]/2, // Left border angle
|
||||||
|
s[1] + s[2]/2, // Right border angle
|
||||||
|
s[3]?1:0, // On turret?
|
||||||
|
]
|
||||||
|
if (s[0] > this.maxlen) {
|
||||||
|
this.maxlen = s[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.set_state(0, 0, 0, 0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up our state, for later interleaved draw requests
|
||||||
|
set_state(x, y, rotation, turret, flags, sensor_state) {
|
||||||
|
this.x = x
|
||||||
|
this.y = y
|
||||||
|
this.rotation = rotation
|
||||||
|
this.turret = turret
|
||||||
|
if (flags & 1) {
|
||||||
|
this.fire = 5
|
||||||
|
}
|
||||||
|
this.led = flags & 2
|
||||||
|
this.dead = flags & 4
|
||||||
|
this.sensor_state = sensor_state
|
||||||
|
}
|
||||||
|
|
||||||
|
draw_crater() {
|
||||||
|
if (!this.dead) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.ctx.save()
|
||||||
|
this.ctx.translate(this.x, this.y)
|
||||||
|
this.ctx.rotate(this.rotation)
|
||||||
|
|
||||||
|
if (this.fire == 5) {
|
||||||
|
this.ctx.save()
|
||||||
|
this.ctx.rotate(this.turret)
|
||||||
|
// one frame of cannon fire
|
||||||
|
this.draw_cannon()
|
||||||
|
this.fire = 0
|
||||||
|
this.ctx.restore()
|
||||||
|
}
|
||||||
|
|
||||||
|
this.ctx.lineWidth = 2
|
||||||
|
this.ctx.strokeStyle = `rgb(from ${this.color} r g b / 50%)`
|
||||||
|
this.ctx.fillStyle = `rgb(from ${this.color} r g b / 20%)`
|
||||||
|
this.ctx.beginPath()
|
||||||
|
this.ctx.moveTo(12, 0)
|
||||||
|
for (let i = 0; i < craterPoints; i += 1) {
|
||||||
|
this.ctx.rotate(craterAngle)
|
||||||
|
this.ctx.lineTo(6, 0)
|
||||||
|
this.ctx.rotate(craterAngle)
|
||||||
|
this.ctx.lineTo(12, 0)
|
||||||
|
}
|
||||||
|
this.ctx.closePath()
|
||||||
|
this.ctx.stroke()
|
||||||
|
this.ctx.fill()
|
||||||
|
|
||||||
|
this.ctx.restore()
|
||||||
|
}
|
||||||
|
|
||||||
|
draw_sensors() {
|
||||||
|
if (this.dead) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.ctx.save()
|
||||||
|
this.ctx.translate(this.x, this.y)
|
||||||
|
this.ctx.rotate(this.rotation)
|
||||||
|
|
||||||
|
this.ctx.lineWidth = 1
|
||||||
|
for (let i in this.sensors) {
|
||||||
|
var s = this.sensors[i]
|
||||||
|
var adj = this.turret * s[3]
|
||||||
|
|
||||||
|
if (this.sensor_state & (1 << i)) {
|
||||||
|
// Sensor is triggered
|
||||||
|
this.ctx.strokeStyle = "#000"
|
||||||
|
} else {
|
||||||
|
this.ctx.strokeStyle = `rgb(from ${this.color} r g b / 40%)`
|
||||||
|
}
|
||||||
|
this.ctx.beginPath()
|
||||||
|
this.ctx.moveTo(0, 0)
|
||||||
|
this.ctx.arc(0, 0, s[0], s[1] + adj, s[2] + adj, false)
|
||||||
|
this.ctx.closePath()
|
||||||
|
this.ctx.stroke()
|
||||||
|
}
|
||||||
|
|
||||||
|
this.ctx.restore()
|
||||||
|
}
|
||||||
|
|
||||||
|
draw_tank() {
|
||||||
|
if (this.dead) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.ctx.save()
|
||||||
|
this.ctx.translate(this.x, this.y)
|
||||||
|
this.ctx.rotate(this.rotation)
|
||||||
|
|
||||||
|
this.ctx.fillStyle = this.color
|
||||||
|
this.ctx.fillRect(-5, -4, 10, 8)
|
||||||
|
this.ctx.fillStyle = "#777"
|
||||||
|
this.ctx.fillRect(-7, -9, 15, 5)
|
||||||
|
this.ctx.fillRect(-7, 4, 15, 5)
|
||||||
|
this.ctx.rotate(this.turret)
|
||||||
|
if (this.fire) {
|
||||||
|
this.draw_cannon()
|
||||||
|
this.fire -= 1
|
||||||
|
} else {
|
||||||
|
if (this.led) {
|
||||||
|
this.ctx.fillStyle = "#f00"
|
||||||
|
} else {
|
||||||
|
this.ctx.fillStyle = "#000"
|
||||||
|
}
|
||||||
|
this.ctx.fillRect(0, -1, 10, 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.ctx.restore()
|
||||||
|
}
|
||||||
|
|
||||||
|
draw_cannon() {
|
||||||
|
this.ctx.fillStyle = ("hsl(0, 100%, 100%, " + this.fire/5 + ")")
|
||||||
|
this.ctx.fillRect(0, -1, 45, 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
draw_wrap_sensors() {
|
||||||
|
let width = this.ctx.canvas.width
|
||||||
|
let height = this.ctx.canvas.height
|
||||||
|
let orig_x = this.x
|
||||||
|
let orig_y = this.y
|
||||||
|
for (let x = this.x - width; x < width + this.maxlen; x += width) {
|
||||||
|
for (let y = this.y - height; y < height + this.maxlen; y += height) {
|
||||||
|
if ((-this.maxlen < x) && (x < width + this.maxlen) &&
|
||||||
|
(-this.maxlen < y) && (y < height + this.maxlen)) {
|
||||||
|
this.x = x
|
||||||
|
this.y = y
|
||||||
|
this.draw_sensors()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.x = orig_x
|
||||||
|
this.y = orig_y
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
310
www/tanks.js
310
www/tanks.js
|
@ -1,310 +0,0 @@
|
||||||
function dbg(o) {
|
|
||||||
e = document.getElementById("debug");
|
|
||||||
e.innerHTML = o;
|
|
||||||
}
|
|
||||||
|
|
||||||
function torgba(color, alpha) {
|
|
||||||
var r = parseInt(color.substring(1,3), 16);
|
|
||||||
var g = parseInt(color.substring(3,5), 16);
|
|
||||||
var b = parseInt(color.substring(5,7), 16);
|
|
||||||
|
|
||||||
return "rgba(" + r + "," + g + "," + b + "," + alpha + ")";
|
|
||||||
}
|
|
||||||
|
|
||||||
function Tank(ctx, width, height, color, sensors) {
|
|
||||||
var craterStroke = torgba(color, 0.5);
|
|
||||||
var craterFill = torgba(color, 0.2);
|
|
||||||
var sensorStroke = torgba(color, 0.4);
|
|
||||||
var maxlen = 0;
|
|
||||||
|
|
||||||
this.x = 0;
|
|
||||||
this.y = 0;
|
|
||||||
this.rotation = 0;
|
|
||||||
this.turret = 0;
|
|
||||||
|
|
||||||
this.dead = 0;
|
|
||||||
|
|
||||||
// Do all the yucky math up front
|
|
||||||
this.sensors = new Array();
|
|
||||||
for (i in sensors) {
|
|
||||||
var s = sensors[i];
|
|
||||||
|
|
||||||
if (! s) {
|
|
||||||
this.sensors[i] = [0,0,0,0];
|
|
||||||
} else {
|
|
||||||
// r, angle, width, turret
|
|
||||||
this.sensors[i] = new Array();
|
|
||||||
this.sensors[i][0] = s[0];
|
|
||||||
this.sensors[i][1] = s[1] - s[2]/2;
|
|
||||||
this.sensors[i][2] = s[1] + s[2]/2;
|
|
||||||
this.sensors[i][3] = s[3]?1:0;
|
|
||||||
if (s[0] > maxlen) {
|
|
||||||
maxlen = s[0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set up our state, for later interleaved draw requests
|
|
||||||
this.set_state = function(x, y, rotation, turret, flags, sensor_state) {
|
|
||||||
this.x = x;
|
|
||||||
this.y = y;
|
|
||||||
this.rotation = rotation;
|
|
||||||
this.turret = turret;
|
|
||||||
if (flags & 1) {
|
|
||||||
this.fire = 5;
|
|
||||||
}
|
|
||||||
this.led = flags & 2;
|
|
||||||
this.dead = flags & 4;
|
|
||||||
this.sensor_state = sensor_state;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.draw_crater = function() {
|
|
||||||
if (!this.dead) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var points = 7;
|
|
||||||
var angle = Math.PI / points;
|
|
||||||
|
|
||||||
ctx.save();
|
|
||||||
ctx.translate(this.x, this.y);
|
|
||||||
ctx.rotate(this.rotation);
|
|
||||||
|
|
||||||
if (this.fire == 5) {
|
|
||||||
ctx.save();
|
|
||||||
ctx.rotate(this.turret);
|
|
||||||
// one frame of cannon fire
|
|
||||||
this.draw_cannon();
|
|
||||||
this.fire = 0;
|
|
||||||
ctx.restore();
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.lineWidth = 2;
|
|
||||||
ctx.strokeStyle = craterStroke;
|
|
||||||
ctx.fillStyle = craterFill;
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.moveTo(12, 0);
|
|
||||||
for (i = 0; i < points; i += 1) {
|
|
||||||
ctx.rotate(angle);
|
|
||||||
ctx.lineTo(6, 0);
|
|
||||||
ctx.rotate(angle);
|
|
||||||
ctx.lineTo(12, 0);
|
|
||||||
}
|
|
||||||
ctx.closePath()
|
|
||||||
ctx.stroke();
|
|
||||||
ctx.fill();
|
|
||||||
|
|
||||||
ctx.restore();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.draw_sensors = function() {
|
|
||||||
if (this.dead) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ctx.save();
|
|
||||||
ctx.translate(this.x, this.y);
|
|
||||||
ctx.rotate(this.rotation);
|
|
||||||
|
|
||||||
ctx.lineWidth = 1;
|
|
||||||
for (i in this.sensors) {
|
|
||||||
var s = this.sensors[i];
|
|
||||||
var adj = this.turret * s[3];
|
|
||||||
|
|
||||||
if (this.sensor_state & (1 << i)) {
|
|
||||||
// Sensor is triggered
|
|
||||||
ctx.strokeStyle = "#000";
|
|
||||||
} else {
|
|
||||||
ctx.strokeStyle = sensorStroke;
|
|
||||||
}
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.moveTo(0, 0);
|
|
||||||
ctx.arc(0, 0, s[0], s[1] + adj, s[2] + adj, false);
|
|
||||||
ctx.closePath();
|
|
||||||
ctx.stroke();
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.restore();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.draw_tank = function() {
|
|
||||||
if (this.dead) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ctx.save();
|
|
||||||
ctx.translate(this.x, this.y);
|
|
||||||
ctx.rotate(this.rotation);
|
|
||||||
|
|
||||||
ctx.fillStyle = color;
|
|
||||||
ctx.fillRect(-5, -4, 10, 8);
|
|
||||||
ctx.fillStyle = "#777";
|
|
||||||
ctx.fillRect(-7, -9, 15, 5);
|
|
||||||
ctx.fillRect(-7, 4, 15, 5);
|
|
||||||
ctx.rotate(this.turret);
|
|
||||||
if (this.fire) {
|
|
||||||
this.draw_cannon();
|
|
||||||
this.fire -= 1;
|
|
||||||
} else {
|
|
||||||
if (this.led) {
|
|
||||||
ctx.fillStyle = "#f00";
|
|
||||||
} else {
|
|
||||||
ctx.fillStyle = "#000";
|
|
||||||
}
|
|
||||||
ctx.fillRect(0, -1, 10, 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.restore();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.draw_cannon = function() {
|
|
||||||
ctx.fillStyle = ("rgba(255,255,64," + this.fire/5 + ")");
|
|
||||||
ctx.fillRect(0, -1, 45, 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.draw_wrap_sensors = function() {
|
|
||||||
var orig_x = this.x;
|
|
||||||
var orig_y = this.y;
|
|
||||||
for (x = this.x - width; x < width + maxlen; x += width) {
|
|
||||||
for (y = this.y - height; y < height + maxlen; y += height) {
|
|
||||||
if ((-maxlen < x) && (x < width + maxlen) &&
|
|
||||||
(-maxlen < y) && (y < height + maxlen)) {
|
|
||||||
this.x = x;
|
|
||||||
this.y = y;
|
|
||||||
this.draw_sensors();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.x = orig_x;
|
|
||||||
this.y = orig_y;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var loop_id;
|
|
||||||
var updateFunc = null;
|
|
||||||
function togglePlayback() {
|
|
||||||
if ($("#playing").prop("checked")) {
|
|
||||||
loop_id = setInterval(updateFunc, 66);
|
|
||||||
} else {
|
|
||||||
clearInterval(loop_id);
|
|
||||||
loop_id = null;
|
|
||||||
}
|
|
||||||
$("#pauselabel").toggleClass("ui-icon-play ui-icon-pause");
|
|
||||||
}
|
|
||||||
|
|
||||||
function start(id, game) {
|
|
||||||
var canvas = document.getElementById(id);
|
|
||||||
var ctx = canvas.getContext('2d');
|
|
||||||
|
|
||||||
canvas.width = game[0][0];
|
|
||||||
canvas.height = game[0][1];
|
|
||||||
// game[2] is tank descriptions
|
|
||||||
var turns = game[2];
|
|
||||||
|
|
||||||
// Set up tanks
|
|
||||||
var tanks = new Array();
|
|
||||||
for (i in game[1]) {
|
|
||||||
var desc = game[1][i];
|
|
||||||
tanks[i] = new Tank(ctx, game[0][0], game[0][1], desc[0], desc[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
var frame = 0;
|
|
||||||
var lastframe = 0;
|
|
||||||
var fps = document.getElementById('fps');
|
|
||||||
|
|
||||||
function update_fps() {
|
|
||||||
fps.innerHTML = (frame - lastframe);
|
|
||||||
lastframe = frame;
|
|
||||||
}
|
|
||||||
|
|
||||||
function drawFrame(idx) {
|
|
||||||
canvas.width = canvas.width;
|
|
||||||
turn = turns[idx];
|
|
||||||
|
|
||||||
// Update and draw craters first
|
|
||||||
for (i in turn) {
|
|
||||||
t = turn[i];
|
|
||||||
if (!t) {
|
|
||||||
// old data, force-kill it
|
|
||||||
tanks[i].fire = 0;
|
|
||||||
tanks[i].dead = 5;
|
|
||||||
} else {
|
|
||||||
tanks[i].set_state(t[0], t[1], t[2], t[3], t[4], t[5]);
|
|
||||||
}
|
|
||||||
tanks[i].draw_crater();
|
|
||||||
}
|
|
||||||
// Then sensors
|
|
||||||
for (i in turn) {
|
|
||||||
tanks[i].draw_wrap_sensors();
|
|
||||||
}
|
|
||||||
// Then tanks
|
|
||||||
for (i in turn) {
|
|
||||||
tanks[i].draw_tank()
|
|
||||||
}
|
|
||||||
|
|
||||||
document.getElementById('frameid').innerHTML = idx;
|
|
||||||
}
|
|
||||||
|
|
||||||
function update() {
|
|
||||||
var idx = frame % (turns.length + 20);
|
|
||||||
var turn;
|
|
||||||
|
|
||||||
frame += 1;
|
|
||||||
if (idx >= turns.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
drawFrame(idx);
|
|
||||||
|
|
||||||
$('#seekslider').slider('value', idx);
|
|
||||||
}
|
|
||||||
|
|
||||||
function seekToFrame(newidx) {
|
|
||||||
var idx = frame % (turns.length + 20);
|
|
||||||
if (idx !== newidx) {
|
|
||||||
frame = newidx;
|
|
||||||
drawFrame(newidx);
|
|
||||||
}
|
|
||||||
// make sure we're paused
|
|
||||||
if ($("#playing").prop("checked")) {
|
|
||||||
$("#playing").prop("checked", false);
|
|
||||||
togglePlayback();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
updateFunc = update;
|
|
||||||
loop_id = setInterval(update, 66);
|
|
||||||
//loop_id = setInterval(update, 400);
|
|
||||||
if (fps) {
|
|
||||||
setInterval(update_fps, 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (id === "battlefield") {
|
|
||||||
$("#game_box").append('<p><input type="checkbox" checked id="playing" onclick="togglePlayback();"><label for="playing"><span class="ui-icon ui-icon-pause" id="pauselabel"></class></label> <span id="frameid">0</span> <span id="seekslider" style="width: 75%; float: right;"></span></p>');
|
|
||||||
$('#playing').button();
|
|
||||||
var slider = $('#seekslider');
|
|
||||||
slider.slider({ max: turns.length-1, slide: function(event, ui) { seekToFrame(ui.value); } });
|
|
||||||
|
|
||||||
var spacing = 100 / turns.length;
|
|
||||||
var deaths = [];
|
|
||||||
for (i in turns[0]) {
|
|
||||||
deaths.push(false);
|
|
||||||
}
|
|
||||||
var percent = 0;
|
|
||||||
for (var f = 0; f < turns.length; f++) {
|
|
||||||
var turn = turns[f];
|
|
||||||
if (percent < (spacing * f)) {
|
|
||||||
percent = spacing * f;
|
|
||||||
}
|
|
||||||
for (var i = 0; i < turn.length; i++) {
|
|
||||||
if (deaths[i]) { continue; }
|
|
||||||
if (!turn[i] || (turn[i][4] & 4)) {
|
|
||||||
deaths[i] = true;
|
|
||||||
// http://stackoverflow.com/questions/8648963/add-tick-marks-to-jquery-slider
|
|
||||||
$('<span class="ui-slider-tick-mark"></span>').css('left', percent + '%').css('background-color', game[1][i][0]).appendTo(slider);
|
|
||||||
percent++;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in New Issue