func advanceState(c appengine.Context, h *hunt.Hunt, currentState int) { err := datastore.RunInTransaction(c, func(c appengine.Context) error { h := hunt.ID(c, h.ID) if h == nil || h.State != currentState { // TODO(dneal): Return a real error. return nil } switch h.State { case hunt.StatePreLaunch: teams := team.All(c, h) nonPaperOrder := rand.Perm(len(teams)) paperOrder := rand.Perm(len(teams)) for i := range teams { puzzle.New(c, h, teams[i], nonPaperOrder[i]+1, false) puzzle.New(c, h, teams[i], len(teams)+paperOrder[i]+1, true) } case hunt.StateSurveying: tally.BuildFinalTally(c, h) } h.State++ h.Write(c) broadcast.SendRefresh(c, h) return nil }, nil) if err != nil { c.Errorf("Error: %v", err) } }
func BuildFinalTally(c appengine.Context, h *hunt.Hunt) { teams := team.All(c, h) puzzles := puzzle.All(c, h, nil) teamMap := map[string]*FinalScore{} for _, t := range teams { fs := &FinalScore{Name: t.Name} for _, p := range puzzles { if solve := puzzle.GetSolve(c, h, p, t); solve != nil { fs.SolvePoints = append(fs.SolvePoints, solve.Points) fs.TotalSolvePoints += solve.Points } else { fs.SolvePoints = append(fs.SolvePoints, 0) } } teamMap[t.Name] = fs } puzzleMap := map[int]*VotePoints{} for _, p := range puzzles { for _, t := range teams { if p.Team.Equal(t.Key) { if p.Paper { puzzleMap[p.Number] = &teamMap[t.Name].PaperPoints } else { puzzleMap[p.Number] = &teamMap[t.Name].NonPaperPoints } } } } numVotes := len(puzzles) - 2 for _, t := range teams { var votes []float32 if len(t.Survey) != 3*numVotes { for i := 0; i < 3*numVotes; i++ { votes = append(votes, 1.0) } } else { for _, c := range t.Survey { points, _ := strconv.Atoi(string(c)) if points < 1 { points = 1 } if points > 5 { points = 5 } votes = append(votes, float32(points)) } } normalize(votes[0 : len(votes)/2]) normalize(votes[len(votes)/2 : len(votes)]) voteIndex := 0 for _, p := range puzzles { if p.Team.Equal(t.Key) { continue } // Don't score points for Viscosity Breakdown, // since they dropped out. // // TODO(dneal): This is a totally brutal hack // and makes the normalization not exactly // accurate, but it's good enough. Remove as // soon as this hunt is done and make it // possible for a team to drop out mid-way. if p.Number != 2 && p.Number != 10 { puzzleMap[p.Number].Fun += votes[voteIndex] puzzleMap[p.Number].Presentation += votes[voteIndex+1] puzzleMap[p.Number].Ingredients += votes[voteIndex+2] } voteIndex += 3 } } for _, fs := range teamMap { fs.TotalVotingPoints = fs.NonPaperPoints.Fun + fs.NonPaperPoints.Presentation + fs.NonPaperPoints.Ingredients + fs.PaperPoints.Fun + fs.PaperPoints.Presentation + fs.PaperPoints.Ingredients fs.FinalScore = float32(fs.TotalSolvePoints) + fs.TotalVotingPoints k := datastore.NewIncompleteKey(c, finalScoreKind, h.Key) k, err := datastore.Put(c, k, fs) if err != nil { c.Errorf("Error: %v", err) } } }
func HuntHandler(w http.ResponseWriter, r *http.Request) { path := strings.Split(r.URL.Path, "/") if len(path) != 3 { http.Error(w, "Not found", 404) return } c := appengine.NewContext(r) w.Header().Set("Content-Type", "application/json") enc := json.NewEncoder(w) var h *hunt.Hunt if id := r.FormValue("hunt_id"); id != "" { h = hunt.ID(c, id) } if h == nil { return } t, badSignIn := team.SignIn(c, h, r) var p *puzzle.Puzzle if id := r.FormValue("puzzleid"); id != "" { p = puzzle.ID(c, id) } var err error switch path[2] { case "teaminfo": teams := TeamSelector{ BadSignIn: badSignIn, } if t != nil { teams.CurrentTeam = t.Name } else { for _, t := range team.All(c, h) { teams.Teams = append(teams.Teams, TeamInfo{t.Name, t.ID}) } } err = enc.Encode(teams) case "ingredients": if h.State >= hunt.StateIngredients || (t != nil && t.Novice && h.State == hunt.StateEarlyAccess) { err = enc.Encode(IngredientInfo{true, false, h.Ingredients}) } case "leaderboard": if h.State >= hunt.StateSolving { var l LeaderboardInfo fillLeaderboardInfo(c, h, t, &l) err = enc.Encode(l) } case "leaderboardupdate": if p != nil && h.State >= hunt.StateSolving { err = enc.Encode(p.UpdatableProgressInfo(c, h, t)) } case "puzzles": if t != nil && (h.State >= hunt.StateIngredients || (t.Novice && h.State == hunt.StateEarlyAccess)) && h.State < hunt.StateSolving { puzzles := puzzle.All(c, h, t) var admin []*puzzle.AdminPuzzle for _, p := range puzzles { admin = append(admin, p.Admin(c)) } // TODO(dneal): Check state. err = enc.Encode(PuzzleInfo{true, admin}) } case "updatepuzzle": if p != nil && h.State < hunt.StateSolving { p.Name = r.FormValue("name") p.Answer = r.FormValue("answer") p.Write(c) broadcast.SendPuzzlesUpdate(c, h, t) } case "survey": if t != nil && h.State == hunt.StateSurveying { info := SurveyInfo{Display: true} if t.Survey != "" { info.Done = true } else { puzzles := puzzle.All(c, h, nil) for _, p := range puzzles { if p.Team.Equal(t.Key) { continue } info.Puzzles = append(info.Puzzles, &ProgressInfo{ Number: p.Number, Name: p.Name, ID: p.ID, }) } } err = enc.Encode(info) } case "submitsurvey": if t != nil && t.Survey == "" { t.Survey = r.FormValue("result") t.Write(c) broadcast.SendSurveyUpdate(c, h, t) } case "channel": err = enc.Encode(broadcast.GetToken(c, h, t, false)) case "submitanswer": if t == nil || p == nil || h.State != hunt.StateSolving { break } var throttled, correct bool err = datastore.RunInTransaction(c, func(c appengine.Context) error { t := t.ReRead(c) p := p.ReRead(c) if t.Throttle(c) { throttled = true } else { correct = p.SubmitAnswer(c, h, t, r.FormValue("answer")) } return nil }, nil) var outcome string if err != nil { outcome = "Error, try again!" } else if throttled { outcome = "Too many answer attempts, please wait a minute and try again" adminconsole.Log(c, h, fmt.Sprintf("%s attempts to answer but is throttled", t.Name)) } else if correct { outcome = "Correct!" broadcast.SendLeaderboardUpdate(c, h, p) adminconsole.Log(c, h, fmt.Sprintf("%s correctly answers (%d) %s", t.Name, p.Number, p.Name)) } else { outcome = fmt.Sprintf("\"%s\" is an incorrect guess for %s", r.FormValue("answer"), p.Name) adminconsole.Log(c, h, fmt.Sprintf("%s incorrectly answers [%s] for (%d) %s", t.Name, r.FormValue("answer"), p.Number, p.Name)) } err = enc.Encode(outcome) case "finalscores": finalScores := tally.Get(c, h, false) err = enc.Encode(finalScores) } if err != nil { c.Errorf("Error: %v", err) } }
func AdminHandler(w http.ResponseWriter, r *http.Request) { path := strings.Split(r.URL.Path, "/") if len(path) != 4 { http.Error(w, "Not found", 404) return } c := appengine.NewContext(r) w.Header().Set("Content-Type", "application/json") enc := json.NewEncoder(w) var h *hunt.Hunt if id := r.FormValue("hunt_id"); id != "" { h = hunt.ID(c, id) } var t *team.Team if id := r.FormValue("team_id"); h != nil && id != "" { t = team.ID(c, id) } var p *puzzle.Puzzle if id := r.FormValue("puzzleid"); h != nil && id != "" { p = puzzle.ID(c, id) } var err error switch path[3] { case "hunts": hunts := hunt.All(c) err = enc.Encode(hunts) case "addhunt": hunt.New(c, r.FormValue("name"), r.FormValue("path")) case "deletehunt": h.Delete(c) case "updateingredients": if h != nil && h.State < hunt.StateEarlyAccess { h.Ingredients = r.FormValue("ingredients") h.Write(c) broadcast.SendIngredientsUpdate(c, h) } case "teams": if h != nil { err = enc.Encode(TeamsInfo{ Editable: true, Teams: team.All(c, h), }) } case "addteam": if h != nil && h.State < hunt.StateEarlyAccess { team.New(c, h, r.FormValue("name"), r.FormValue("password"), r.FormValue("novice") == "true") broadcast.SendTeamsUpdate(c, h) } case "deleteteam": if t != nil && h.State < hunt.StateEarlyAccess { t.Delete(c) broadcast.SendTeamsUpdate(c, h) } case "state": if h != nil { err = enc.Encode(h.State) } case "advancestate": if h != nil { currentState, err := strconv.Atoi(r.FormValue("currentstate")) if err == nil { advanceState(c, h, currentState) broadcast.SendRefresh(c, h) } } case "puzzles": if h != nil { puzzles := puzzle.All(c, h, nil) var admin []*puzzle.AdminPuzzle for _, p := range puzzles { admin = append(admin, p.Admin(c)) } err = enc.Encode(PuzzleInfo{false, admin}) } case "ingredients": err = enc.Encode(IngredientInfo{ Display: true, Editable: true, Ingredients: h.Ingredients, }) case "leaderboard": if h.State >= hunt.StateSolving { var l LeaderboardInfo fillLeaderboardInfo(c, h, nil, &l) err = enc.Encode(l) } case "leaderboardupdate": if p != nil { err = enc.Encode(p.UpdatableProgressInfo(c, h, nil)) } case "channel": if h != nil { err = enc.Encode(broadcast.GetToken(c, h, nil, true)) } case "console": if h != nil { err = enc.Encode(adminconsole.Logs(c, h)) } case "adminsurvey": if h != nil && h.State == hunt.StateSurveying { var info AdminSurveyInfo info.Display = true for _, t := range team.All(c, h) { info.Teams = append(info.Teams, TeamSurveyInfo{t.Name, t.Survey != ""}) } err = enc.Encode(info) } case "finalscores": finalScores := tally.Get(c, h, true) err = enc.Encode(finalScores) } if err != nil { c.Errorf("Error: %v", err) return } }