Пример #1
0
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
}
Пример #2
0
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
}
Пример #3
0
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
}
Пример #4
0
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
}
Пример #5
0
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{})
}
Пример #6
0
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
}