/* HTTPHandlerFunc returns an http.HandlerFunc to use when hosting an AI. */ func HTTPHandlerFunc(lf common.LoggerFactory, ai AI) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { logger := lf(r) defer func() { if e := recover(); e != nil { w.WriteHeader(500) logger.Printf("Error delivering orders: %v", e) } }() var req OrderRequest common.MustDecodeJSON(r.Body, &req) w.Header().Set("Content-Type", "application/json; charset=UTF-8") common.MustEncodeJSON(w, ai.Orders(lf(r), req.Me, req.State)) } }
func nextTurn(cont appengine.Context, id *datastore.Key, playerNames []string) { con := common.Context{Context: cont} self := getGameById(con, id) self.PlayerNames = playerNames if self.Length > maxGameDuration { self.State = StateFinished self.Save(con) con.Infof("Ended %v due to timeout", self.Id) return } errorSavers := []func(){} if err := common.Transaction(con, func(c common.Context) (err error) { lastTurn := GetLatestTurnByParent(c, self.Id) responses := make(chan orderResponse, len(self.Players)) for _, playerId := range self.Players { orderResp := orderResponse{ DatastorePlayerId: playerId, StatePlayerId: state.PlayerId(playerId.Encode()), } if foundAi := GetAIById(c, playerId); foundAi != nil { go func() { // Always deliver the order response defer func() { responses <- orderResp }() // create a request orderRequest := ai.OrderRequest{ Me: orderResp.StatePlayerId, State: lastTurn.State, GameId: state.GameId(self.Id.Encode()), } // encode it into a body, and remember its string representation sendBody := &bytes.Buffer{} aiCommon.MustEncodeJSON(sendBody, orderRequest) sendBodyString := sendBody.String() // get a client client := urlfetch.Client(c) // send the request to the ai req, err := http.NewRequest("POST", foundAi.URL, sendBody) var resp *http.Response if err == nil { req.Header.Set("Content-Type", "application/json; charset=UTF-8") resp, err = client.Do(req) } recvBody := &bytes.Buffer{} recvBodyString := "" if err == nil { // check what we received _, err = io.Copy(recvBody, resp.Body) recvBodyString = recvBody.String() } // if we have no other errors, but we got a non-200 if err == nil && resp.StatusCode != 200 { err = orderError{ Request: req, RequestBody: sendBodyString, Response: resp, ResponseBody: recvBodyString, } } // lets try to unserialize if err == nil { err = json.Unmarshal(recvBody.Bytes(), &orderResp.Orders) } // store the error, if any if err != nil { orderResp.Error = err } }() } else { responses <- orderResp } } orderMap := map[state.PlayerId]state.Orders{} for _, _ = range self.Players { // wait for the responses orderResp := <-responses // store it orderMap[orderResp.StatePlayerId] = orderResp.Orders // if we got an error if orderResp.Error != nil { // make sure to save it later errorSavers = append(errorSavers, func() { if ai := GetAIById(con, orderResp.DatastorePlayerId); ai != nil { ai.AddError(con, lastTurn.Id, orderResp.Error) } }) } } // execute the orders newTurn, winner := lastTurn.Next(c, orderMap) // save the new turn newTurn.Save(c, self.Id) // if we got a winner, end the game and store the winner if winner == nil { self.State = StatePlaying } else { self.Winner = common.MustDecodeKey(string(*winner)) self.State = StateFinished } // increase our length with the new turn self.Length += 1 // save us self.Save(c) // if we didn't end, queue the next turn if winner == nil { nextTurnFunc.Call(c, self.Id, playerNames) } return nil }); err != nil { panic(err) } // run any error savers we got for _, saver := range errorSavers { saver() } // store the new stats in the players if we ended if self.State == StateFinished { for _, playerId := range self.Players { common.Transaction(con, func(c common.Context) error { if ai := GetAIById(c, playerId); ai != nil { if playerId.Equal(self.Winner) { ai.Wins += 1 } else { ai.Losses += 1 } ai.Save(c) } return nil }) } } }
func (self Context) RenderJSON(i interface{}) { self.SetContentType("application/json; charset=UTF-8", false) common.MustEncodeJSON(self.Resp, i) }