func AdminRollback(c *common.HTTPContext) (err error) { gameId, err := base64.URLEncoding.DecodeString(c.Vars()["game_id"]) if err != nil { return } epoch, err := epoch.Get(c.DB()) if err != nil { return } g := &Game{Id: gameId} if err = c.DB().Get(g); err != nil { return } ordinal, err := strconv.Atoi(c.Vars()["until"]) if err != nil { return } members, err := g.Members(c.DB()) if err != nil { return } phases, err := g.Phases(c.DB()) if err != nil { return } sort.Sort(phases) for index, _ := range phases { phase := &phases[index] if phase.Ordinal == ordinal { phase.Resolutions = map[dip.Province]string{} phase.Resolved = false phase.Deadline = epoch + (time.Minute * time.Duration(g.Deadlines[phase.Type])) for index, _ := range members { opts := dip.Options{} if opts, err = phase.Options(members[index].Nation); err != nil { return } members[index].Options = opts if len(opts) == 0 { members[index].Committed = true members[index].NoOrders = true } else { members[index].Committed = false members[index].NoOrders = false } if err = c.DB().Set(&members[index]); err != nil { return } } if err = c.DB().Set(phase); err != nil { return } } else if phase.Ordinal > ordinal { if err = c.DB().Del(phase); err != nil { return } } } return }
func (self *Game) toStateWithPhase(d *kol.DB, members Members, member *Member, phase *Phase, phases int) (result GameState, err error) { email := "" if member != nil { email = string(member.UserId) } memberStates, err := members.ToStates(d, self, email, false) if err != nil { return } unseen := map[string]int{} if member != nil { unseen, err = self.UnseenMessages(d, member.Id) if err != nil { return } } var timeLeft time.Duration if phase != nil { timeLeft, err = epoch.Get(d) if err != nil { return } timeLeft = phase.Deadline - timeLeft } result = GameState{ Game: self, UnseenMessages: unseen, Members: memberStates, TimeLeft: timeLeft, Phase: phase, Phases: phases, } return }
func (self *Phase) Schedule(c common.SkinnyContext) error { if !self.Resolved { ep, err := epoch.Get(c.DB()) if err != nil { return err } timeout := self.Deadline - ep c.BetweenTransactions(func(c common.SkinnyContext) { if timeout > 0 { time.AfterFunc(timeout, func() { if err := self.autoResolve(c); err != nil { c.Errorf("Failed resolving %+v after %v: %v", self, timeout, err) } }) c.Debugf("Scheduled resolution of %v/%v in %v at %v", self.GameId, self.Id, timeout, time.Now().Add(timeout)) } else { c.Debugf("Resolving %v/%v immediately, it is %v overdue", self.GameId, self.Id, -timeout) if err := self.autoResolve(c); err != nil { c.Errorf("Failed resolving %+v immediately: %v", self, err) } } }) } return nil }
func (self *Game) start(c common.SkinnyContext) (err error) { if self.State != common.GameStateCreated { err = fmt.Errorf("%+v is already started", self) return } self.State = common.GameStateStarted self.Closed = true if err = c.DB().Set(self); err != nil { return } var startState *state.State if self.Variant == common.ClassicalString { if startState, err = classical.Start(); err != nil { return } } else { err = fmt.Errorf("Unknown variant %v", self.Variant) return } startPhase := startState.Phase() epoch, err := epoch.Get(c.DB()) if err != nil { return } phase := &Phase{ GameId: self.Id, Ordinal: 0, Orders: map[dip.Nation]map[dip.Province][]string{}, Resolutions: map[dip.Province]string{}, Season: startPhase.Season(), Year: startPhase.Year(), Type: startPhase.Type(), Deadline: epoch + (time.Minute * time.Duration(self.Deadlines[startPhase.Type()])), } phase.Units, phase.SupplyCenters, phase.Dislodgeds, phase.Dislodgers, phase.Bounces, _ = startState.Dump() if err = c.DB().Set(phase); err != nil { return } if err = self.allocate(c.DB(), phase); err != nil { return } if err = phase.Schedule(c); err != nil { return } phase.SendStartedEmails(c, self) return }
func SubscribeMine(c common.WSContext) error { if c.Principal() == "" { return websocket.JSON.Send(c.Conn(), gosubs.Message{ Type: gosubs.FetchType, Object: &gosubs.Object{ URI: c.Match()[0], }, }) } s := c.Pack().New(c.Match()[0]) s.Query = s.DB().Query().Where(kol.Equals{"UserId", kol.Id(c.Principal())}) s.Call = func(i interface{}, op string) (err error) { members := i.([]*Member) var ep time.Duration ep, err = epoch.Get(c.DB()) if err != nil { return } states := GameStates{} for _, member := range members { if op == gosubs.DeleteType { states = append(states, GameState{ Game: &Game{Id: member.GameId}, Members: []MemberState{MemberState{Member: member}}, }) } else { game := &Game{Id: member.GameId} if err = s.DB().Get(game); err != nil { return } var gameMembers Members if gameMembers, err = game.Members(c.DB()); err != nil { return } var state GameState if state, err = game.ToState(c.DB(), gameMembers, member); err != nil { return } states = append(states, state) } } if op == gosubs.FetchType || len(states) > 0 { states = states.SortAndLimit(func(a, b GameState) bool { urgencyA := time.Hour * 24 * 365 urgencyB := time.Hour * 24 * 365 switch a.State { case common.GameStateStarted: _, phase, err := a.Game.Phase(c.DB(), 0) if err == nil { urgencyA = phase.Deadline - ep } case common.GameStateCreated: urgencyA -= 1 } switch b.State { case common.GameStateStarted: _, phase, err := b.Game.Phase(c.DB(), 0) if err == nil { urgencyB = phase.Deadline - ep } case common.GameStateCreated: urgencyB -= 1 } if urgencyA != urgencyB { return urgencyA < urgencyB } return a.CreatedAt.Before(b.CreatedAt) }, 1024*16) return s.Send(states, op) } return nil } return s.Subscribe(&Member{}) }
func (self *Game) resolve(c common.SkinnyContext, phase *Phase) (err error) { // Check that we are in a phase where we CAN resolve if self.State != common.GameStateStarted { err = fmt.Errorf("%+v is not started", self) return } // Load our members members, err := self.Members(c.DB()) if err != nil { return } // Load the godip state for the phase state, err := phase.State() if err != nil { return } // Load "now" epoch, err := epoch.Get(c.DB()) if err != nil { return } // Just to limit runaway resolution to 100 phases. for i := 0; i < 100; i++ { // Resolve the phase! if err = state.Next(); err != nil { return } // Load the new godip phase from the state nextDipPhase := state.Phase() // Create a diplicity phase for the new phase nextPhase := &Phase{ GameId: self.Id, Ordinal: phase.Ordinal + 1, Orders: map[dip.Nation]map[dip.Province][]string{}, Resolutions: map[dip.Province]string{}, Season: nextDipPhase.Season(), Year: nextDipPhase.Year(), Type: nextDipPhase.Type(), Deadline: epoch + (time.Minute * time.Duration(self.Deadlines[nextDipPhase.Type()])), } // Set the new phase positions var resolutions map[dip.Province]error nextPhase.Units, nextPhase.SupplyCenters, nextPhase.Dislodgeds, nextPhase.Dislodgers, nextPhase.Bounces, resolutions = state.Dump() // Store the results of the previous godip phase in the previous diplicity phase for _, nationOrders := range phase.Orders { for prov, _ := range nationOrders { if res, found := resolutions[prov]; found && res != nil { phase.Resolutions[prov] = res.Error() } else { phase.Resolutions[prov] = "OK" } } } // Commit everyone that doesn't have any orders to give waitFor := []*Member{} active := []*Member{} nonSurrendering := []*Member{} for index, _ := range members { opts := dip.Options{} if opts, err = nextPhase.Options(members[index].Nation); err != nil { return } if err = self.endPhaseConsequences(c, phase, &members[index], opts, &waitFor, &active, &nonSurrendering); err != nil { return } } // Mark the old phase as resolved, and save it phase.Resolved = true if err = c.DB().Set(phase); err != nil { return } // If we have a solo victor, end and return if winner := nextDipPhase.Winner(state); winner != nil { var winnerMember *Member for _, member := range members { if member.Nation == *winner { winnerMember = &member break } } if winnerMember == nil { err = fmt.Errorf("None of %+v has nation %#v??", members, *winner) return } if err = self.end(c, nextPhase, members, winnerMember, common.SoloVictory(*winner)); err != nil { return } return } // End the game now if nobody is active anymore if len(active) == 0 { if err = self.end(c, nextPhase, members, nil, common.ZeroActiveMembers); err != nil { return } return } // End the game now if only one player isn't surrendering if len(nonSurrendering) == 1 { if err = self.end(c, nextPhase, members, nonSurrendering[0], common.SoloVictory(nonSurrendering[0].Nation)); err != nil { return } return } // Store the next phase if err = c.DB().Set(nextPhase); err != nil { return } // If there is anyone we need to wait for, schedule an auto resolve and return here. if len(waitFor) > 0 { if err = nextPhase.Schedule(c); err != nil { return } nextPhase.SendScheduleEmails(c, self) return } phase = nextPhase } return }