func (self *Game) Save(c common.Context) *Game { var err error if self.Id == nil { self.setPlayerNames(c) err = common.Transaction(c, func(c common.Context) (err error) { self.CreatedAt = time.Now() self.State = StateCreated self.Length = 1 self.Id, err = datastore.Put(c, datastore.NewKey(c, GameKind, "", 0, nil), self) if err != nil { return } playerIds := make([]state.PlayerId, 0, len(self.Players)) for _, id := range self.Players { playerIds = append(playerIds, state.PlayerId(id.Encode())) } turn := &Turn{ State: state.RandomState(common.GAELogger{c}, playerIds), } turn.Save(c, self.Id) self.Turns = Turns{*turn} nextTurnFunc.Call(c, self.Id, self.PlayerNames) return nil }) if err == nil { for _, playerId := range self.Players { common.Transaction(c, func(common.Context) error { if ai := GetAIById(c, playerId); ai != nil { ai.Games += 1 ai.Save(c) } return nil }) } } } else { _, err = datastore.Put(c, self.Id, self) } common.AssertOkError(err) common.MemDel(c, allGamesKey, gameKeyForId(self.Id)) return self.process(c) }
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 }) } } }