mirror of https://github.com/dirtbags/moth.git
Basic rendering of open puzzles
This commit is contained in:
parent
5070c70d25
commit
5e4af17d57
|
@ -64,9 +64,9 @@ func (a *Award) Same(o *Award) bool {
|
||||||
case a.TeamId != o.TeamId:
|
case a.TeamId != o.TeamId:
|
||||||
return false
|
return false
|
||||||
case a.Category != o.Category:
|
case a.Category != o.Category:
|
||||||
return false
|
return false
|
||||||
case a.Points != o.Points:
|
case a.Points != o.Points:
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,32 +23,18 @@ func respond(w http.ResponseWriter, req *http.Request, status Status, short stri
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// anchoredSearch looks for needle in r,
|
// hasLine returns true if line appears in r.
|
||||||
// skipping the first skip space-delimited words
|
// The entire line must match.
|
||||||
func anchoredSearch(r io.Reader, needle string, skip int) bool {
|
func hasLine(r io.Reader, line string) bool {
|
||||||
scanner := bufio.NewScanner(r)
|
scanner := bufio.NewScanner(r)
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
line := scanner.Text()
|
if scanner.Text() == line {
|
||||||
parts := strings.SplitN(line, " ", skip+1)
|
|
||||||
if (len(parts) > skip) && (parts[skip] == needle) {
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// anchoredSearchFile performs an anchoredSearch on a given filename
|
|
||||||
func anchoredSearchFile(filename string, needle string, skip int) bool {
|
|
||||||
r, err := os.Open(filename)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
defer r.Close()
|
|
||||||
|
|
||||||
return anchoredSearch(r, needle, skip)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ctx Instance) registerHandler(w http.ResponseWriter, req *http.Request) {
|
func (ctx Instance) registerHandler(w http.ResponseWriter, req *http.Request) {
|
||||||
teamname := req.FormValue("name")
|
teamname := req.FormValue("name")
|
||||||
teamid := req.FormValue("id")
|
teamid := req.FormValue("id")
|
||||||
|
@ -69,7 +55,17 @@ func (ctx Instance) registerHandler(w http.ResponseWriter, req *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !anchoredSearchFile(ctx.StatePath("teamids.txt"), teamid, 0) {
|
teamids, err := os.Open(ctx.StatePath("teamids.txt"))
|
||||||
|
if err != nil {
|
||||||
|
respond(
|
||||||
|
w, req, Fail,
|
||||||
|
"Cannot read valid team IDs",
|
||||||
|
"An error was encountered trying to read valid teams IDs: %v", err,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer teamids.Close()
|
||||||
|
if !hasLine(teamids, teamid) {
|
||||||
respond(
|
respond(
|
||||||
w, req, Fail,
|
w, req, Fail,
|
||||||
"Invalid Team ID",
|
"Invalid Team ID",
|
||||||
|
@ -137,7 +133,7 @@ func (ctx Instance) tokenHandler(w http.ResponseWriter, req *http.Request) {
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
// Make sure the token is in the list
|
// Make sure the token is in the list
|
||||||
if !anchoredSearch(f, token, 0) {
|
if !hasLine(f, token) {
|
||||||
respond(
|
respond(
|
||||||
w, req, Fail,
|
w, req, Fail,
|
||||||
"Unrecognized token",
|
"Unrecognized token",
|
||||||
|
@ -190,7 +186,7 @@ func (ctx Instance) answerHandler(w http.ResponseWriter, req *http.Request) {
|
||||||
|
|
||||||
// Look for the answer
|
// Look for the answer
|
||||||
needle := fmt.Sprintf("%d %s", points, answer)
|
needle := fmt.Sprintf("%d %s", points, answer)
|
||||||
if !anchoredSearch(haystack, needle, 0) {
|
if !hasLine(haystack, needle) {
|
||||||
respond(
|
respond(
|
||||||
w, req, Fail,
|
w, req, Fail,
|
||||||
"Wrong answer",
|
"Wrong answer",
|
||||||
|
@ -215,23 +211,46 @@ func (ctx Instance) answerHandler(w http.ResponseWriter, req *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
type PuzzleMap struct {
|
type PuzzleMap struct {
|
||||||
Points int `json:"points"`
|
Points int
|
||||||
Path string `json:"path"`
|
Path string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pm *PuzzleMap) MarshalJSON() ([]byte, error) {
|
||||||
|
if pm == nil {
|
||||||
|
return []byte("null"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
jPath, err := json.Marshal(pm.Path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ret := fmt.Sprintf("[%d,%s]", pm.Points, string(jPath))
|
||||||
|
return []byte(ret), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctx Instance) puzzlesHandler(w http.ResponseWriter, req *http.Request) {
|
func (ctx Instance) puzzlesHandler(w http.ResponseWriter, req *http.Request) {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
|
|
||||||
|
maxByCategory := map[string]int{}
|
||||||
|
for _, a := range ctx.PointsLog() {
|
||||||
|
if a.Points > maxByCategory[a.Category] {
|
||||||
|
maxByCategory[a.Category] = a.Points
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
res := map[string][]PuzzleMap{}
|
res := map[string][]PuzzleMap{}
|
||||||
for catName, mb := range ctx.Categories {
|
for catName, mb := range ctx.Categories {
|
||||||
mf, err := mb.Open("map.txt")
|
mf, err := mb.Open("map.txt")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Print(err)
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
defer mf.Close()
|
defer mf.Close()
|
||||||
|
|
||||||
pm := make([]PuzzleMap, 0, 30)
|
pm := make([]PuzzleMap, 0, 30)
|
||||||
|
completed := true
|
||||||
scanner := bufio.NewScanner(mf)
|
scanner := bufio.NewScanner(mf)
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
line := scanner.Text()
|
line := scanner.Text()
|
||||||
|
@ -249,11 +268,17 @@ func (ctx Instance) puzzlesHandler(w http.ResponseWriter, req *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
pm = append(pm, PuzzleMap{pointval, dir})
|
pm = append(pm, PuzzleMap{pointval, dir})
|
||||||
log.Print(pm)
|
|
||||||
|
if pointval > maxByCategory[catName] {
|
||||||
|
completed = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if completed {
|
||||||
|
pm = append(pm, PuzzleMap{0, ""})
|
||||||
}
|
}
|
||||||
|
|
||||||
res[catName] = pm
|
res[catName] = pm
|
||||||
log.Print(res)
|
|
||||||
}
|
}
|
||||||
jres, _ := json.Marshal(res)
|
jres, _ := json.Marshal(res)
|
||||||
w.Write(jres)
|
w.Write(jres)
|
||||||
|
|
|
@ -121,10 +121,10 @@ func (ctx *Instance) PointsLog() []*Award {
|
||||||
// The maintenance task makes sure we never have duplicate points in the log.
|
// The maintenance task makes sure we never have duplicate points in the log.
|
||||||
func (ctx *Instance) AwardPoints(teamid, category string, points int) error {
|
func (ctx *Instance) AwardPoints(teamid, category string, points int) error {
|
||||||
a := Award{
|
a := Award{
|
||||||
When: time.Now(),
|
When: time.Now(),
|
||||||
TeamId: teamid,
|
TeamId: teamid,
|
||||||
Category: category,
|
Category: category,
|
||||||
Points: points,
|
Points: points,
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, e := range ctx.PointsLog() {
|
for _, e := range ctx.PointsLog() {
|
||||||
|
@ -132,7 +132,7 @@ func (ctx *Instance) AwardPoints(teamid, category string, points int) error {
|
||||||
return fmt.Errorf("Points already awarded to this team in this category")
|
return fmt.Errorf("Points already awarded to this team in this category")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn := fmt.Sprintf("%s-%s-%d", teamid, category, points)
|
fn := fmt.Sprintf("%s-%s-%d", teamid, category, points)
|
||||||
tmpfn := ctx.StatePath("points.tmp", fn)
|
tmpfn := ctx.StatePath("points.tmp", fn)
|
||||||
newfn := ctx.StatePath("points.new", fn)
|
newfn := ctx.StatePath("points.new", fn)
|
||||||
|
|
|
@ -90,7 +90,7 @@ func (ctx *Instance) CollectPoints() {
|
||||||
log.Printf("Can't parse award file %s: %s", filename, err)
|
log.Printf("Can't parse award file %s: %s", filename, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
duplicate := false
|
duplicate := false
|
||||||
for _, e := range ctx.PointsLog() {
|
for _, e := range ctx.PointsLog() {
|
||||||
if award.Same(e) {
|
if award.Same(e) {
|
||||||
|
@ -98,7 +98,7 @@ func (ctx *Instance) CollectPoints() {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if duplicate {
|
if duplicate {
|
||||||
log.Printf("Skipping duplicate points: %s", award.String())
|
log.Printf("Skipping duplicate points: %s", award.String())
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -119,6 +119,13 @@ input {
|
||||||
}
|
}
|
||||||
li {
|
li {
|
||||||
margin: 0.5em 0em;
|
margin: 0.5em 0em;
|
||||||
|
}
|
||||||
|
nav {
|
||||||
|
border: solid black 2px;
|
||||||
|
}
|
||||||
|
nav li {
|
||||||
|
display: inline;
|
||||||
|
margin: 2em;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
)
|
)
|
||||||
|
@ -158,8 +165,68 @@ func staticScoreboard(w http.ResponseWriter) {
|
||||||
func staticPuzzles(w http.ResponseWriter) {
|
func staticPuzzles(w http.ResponseWriter) {
|
||||||
ShowHtml(
|
ShowHtml(
|
||||||
w, Success,
|
w, Success,
|
||||||
"Puzzles",
|
"Open Puzzles",
|
||||||
"XXX: This would be the puzzles overview",
|
`
|
||||||
|
<div id="puzzles"></div>
|
||||||
|
<script>
|
||||||
|
|
||||||
|
function render(obj) {
|
||||||
|
let element = document.getElementById("puzzles");
|
||||||
|
let cats = [];
|
||||||
|
for (let cat in obj) {
|
||||||
|
cats.push(cat);
|
||||||
|
console.log(cat);
|
||||||
|
}
|
||||||
|
cats.sort();
|
||||||
|
|
||||||
|
for (let cat of cats) {
|
||||||
|
let puzzles = obj[cat];
|
||||||
|
|
||||||
|
let pdiv = document.createElement('div');
|
||||||
|
pdiv.className = 'category';
|
||||||
|
|
||||||
|
let h = document.createElement('h2');
|
||||||
|
pdiv.appendChild(h);
|
||||||
|
h.textContent = cat;
|
||||||
|
|
||||||
|
let l = document.createElement('ul');
|
||||||
|
pdiv.appendChild(l);
|
||||||
|
|
||||||
|
for (var puzzle of puzzles) {
|
||||||
|
var points = puzzle[0];
|
||||||
|
var id = puzzle[1];
|
||||||
|
|
||||||
|
var i = document.createElement('li');
|
||||||
|
l.appendChild(i);
|
||||||
|
|
||||||
|
if (points === 0) {
|
||||||
|
i.textContent = "‡";
|
||||||
|
} else {
|
||||||
|
var a = document.createElement('a');
|
||||||
|
i.appendChild(a);
|
||||||
|
a.textContent = points;
|
||||||
|
a.href = cat + "/" + id + "/index.html";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
element.appendChild(pdiv);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
fetch("puzzles.json")
|
||||||
|
.then(function(resp) {
|
||||||
|
return resp.json();
|
||||||
|
}).then(function(obj) {
|
||||||
|
render(obj);
|
||||||
|
}).catch(function(err) {
|
||||||
|
console.log("Error", err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener("load", init);
|
||||||
|
</script>
|
||||||
|
`,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue