Basic rendering of open puzzles

This commit is contained in:
Neale Pickett 2018-09-18 03:32:24 +00:00
parent 5070c70d25
commit 5e4af17d57
5 changed files with 127 additions and 35 deletions

View File

@ -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
} }

View File

@ -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)

View File

@ -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)

View File

@ -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 {

View File

@ -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>
`,
) )
} }