Example #1
0
func ScheduleUnresolvedPhases(c common.SkinnyContext) (err error) {
	unresolved := Phases{}
	if err = c.DB().Query().Where(kol.Equals{"Resolved", false}).All(&unresolved); err != nil {
		return
	}
	for index, _ := range unresolved {
		(&unresolved[index]).Schedule(c)
	}
	return
}
Example #2
0
func (self *Phase) emailTo(c common.SkinnyContext, game *Game, member *Member, user *user.User) (err error) {
	to := fmt.Sprintf("%v <%v>", member.Nation, user.Email)
	unsubTag := &common.UnsubscribeTag{
		T: common.UnsubscribePhaseEmail,
		U: user.Id,
	}
	unsubTag.H = unsubTag.Hash(c.Secret())
	encodedUnsubTag, err := unsubTag.Encode()
	if err != nil {
		return
	}
	contextLink, err := user.I("To see this in context: http://%v/games/%v", user.DiplicityHost, self.GameId)
	if err != nil {
		return
	}
	unsubLink, err := user.I("To unsubscribe: http://%v/unsubscribe/%v", user.DiplicityHost, encodedUnsubTag)
	if err != nil {
		return
	}
	text, err := user.I("A new phase has been created")
	if err != nil {
		return
	}
	subject, err := game.Describe(c, user)
	if err != nil {
		return
	}
	body := fmt.Sprintf(common.EmailTemplate, text, contextLink, unsubLink)
	go c.SendMail("diplicity", c.ReceiveAddress(), subject, body, []string{to})
	return
}
Example #3
0
func (self *Phase) autoResolve(c common.SkinnyContext) (err error) {
	c.Infof("Auto resolving %v/%v due to timeout", self.GameId, self.Id)
	if err = c.Transact(func(c common.SkinnyContext) (err error) {
		if err = c.DB().Get(self); err != nil {
			err = fmt.Errorf("While trying to load %+v: %v", self, err)
			return
		}
		if self.Resolved {
			c.Infof("%+v was already resolved", self)
			return
		}
		game := &Game{Id: self.GameId}
		if err = c.DB().Get(game); err != nil {
			err = fmt.Errorf("While trying to load %+v's game: %v", self, err)
			return
		}
		return game.resolve(c, self)
	}); err != nil {
		return
	}
	return
}
Example #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
}
Example #5
0
func (self *Game) Describe(c common.SkinnyContext, trans common.Translator) (result string, err error) {
	switch self.State {
	case common.GameStateCreated:
		return trans.I(string(common.BeforeGamePhaseType))
	case common.GameStateStarted:
		var phase *Phase
		if _, phase, err = self.Phase(c.DB(), 0); err != nil {
			return
		}
		season := ""
		if season, err = trans.I(string(phase.Season)); err != nil {
			return
		}
		typ := ""
		if typ, err = trans.I(string(phase.Type)); err != nil {
			return
		}
		return trans.I("game_phase_description", season, phase.Year, typ)
	case common.GameStateEnded:
		return trans.I(string(common.AfterGamePhaseType))
	}
	err = fmt.Errorf("Unknown game state for %+v", self)
	return
}
Example #6
0
func Start(c common.SkinnyContext) (err error) {
	startedAt, err := Get(c.DB())
	if err != nil {
		return
	}
	c.Infof("Started at epoch %v", startedAt)
	startedTime := time.Now()
	var currently time.Duration
	go func() {
		for {
			time.Sleep(time.Minute)
			currently = time.Now().Sub(startedTime) + startedAt
			atomic.StoreInt64(&deltaPoint, int64(time.Now().UnixNano()))
			if err = Set(c.DB(), currently); err != nil {
				panic(err)
			}
			c.Debugf("Epoch %v", currently)
		}
	}()
	return
}
Example #7
0
func (self *Message) Send(c common.SkinnyContext, game *Game, sender *Member) (err error) {
	c.Debugf("Sending %#v from %#v in %#v", self.Body, sender.Nation, game.Id.String())
	// make sure the sender is correct
	self.SenderId = sender.Id

	senderUser := &user.User{Id: sender.UserId}
	if err = c.DB().Get(senderUser); err != nil {
		return
	}

	// make sure the sender is one of the recipients
	self.RecipientIds[sender.Id.String()] = true

	// The sender but nobody else saw it...
	self.SeenBy = map[string]bool{
		sender.Id.String(): true,
	}

	// See what phase type the game is in
	var phaseType dip.PhaseType
	switch game.State {
	case common.GameStateCreated:
		phaseType = common.BeforeGamePhaseType
	case common.GameStateStarted:
		var phase *Phase
		if _, phase, err = game.Phase(c.DB(), 0); err != nil {
			return
		}
		phaseType = phase.Type
	case common.GameStateEnded:
		phaseType = common.AfterGamePhaseType
	default:
		err = fmt.Errorf("Unknown game state for %+v", game)
		return
	}

	// Find what chats are allowed during this phase type
	allowedFlags := game.ChatFlags[phaseType]

	// See if the recipient count is allowed
	recipients := len(self.RecipientIds)
	if recipients == 2 {
		if (allowedFlags & common.ChatPrivate) == 0 {
			err = IllegalMessageError{
				Description: fmt.Sprintf("%+v does not allow %+v during %+v", game, self, phaseType),
				Phrase:      "This kind of message is not allowed at this stage of the game",
			}
			return
		}
	} else if recipients == len(common.VariantMap[game.Variant].Nations) {
		if (allowedFlags & common.ChatConference) == 0 {
			err = IllegalMessageError{
				Description: fmt.Sprintf("%+v does not allow %+v during %+v", game, self, phaseType),
				Phrase:      "This kind of message is not allowed at this stage of the game",
			}
			return
		}
	} else if recipients > 2 {
		if (allowedFlags & common.ChatGroup) == 0 {
			err = IllegalMessageError{
				Description: fmt.Sprintf("%+v does not allow %+v during %+v", game, self, phaseType),
				Phrase:      "This kind of message is not allowed at this stage of the game",
			}
			return
		}
	} else {
		err = fmt.Errorf("%+v doesn't have any recipients", self)
		return
	}

	members, err := game.Members(c.DB())
	if err != nil {
		return
	}
	if err = c.DB().Set(self); err != nil {
		return
	}

	recipNations := sort.StringSlice{}
	for memberId, _ := range self.RecipientIds {
		for _, member := range members {
			if memberId == member.Id.String() {
				if member.Nation != "" {
					recipNations = append(recipNations, string(member.Nation))
				}
			}
		}
	}
	sort.Sort(recipNations)
	recipName := strings.Join(recipNations, ", ")
	subKey := fmt.Sprintf("/games/%v/messages", game.Id)
	for memberId, _ := range self.RecipientIds {
		for _, member := range members {
			if memberId == member.Id.String() && self.SenderId.String() != memberId {
				user := &user.User{Id: member.UserId}
				if err = c.DB().Get(user); err == nil {
					if !user.MessageEmailDisabled {
						if !c.IsSubscribing(user.Email, subKey) {
							memberCopy := member
							gameDescription := ""
							if gameDescription, err = game.Describe(c, user); err == nil {
								go self.EmailTo(c, game, sender, senderUser, &memberCopy, user, gameDescription, recipName)
							} else {
								c.Errorf("Trying to describe %+v to %+v: %v", game, user, err)
							}
						} else {
							c.Infof("Not sending to %#v, already subscribing to %#v", user.Id.String(), subKey)
						}
					} else {
						c.Infof("Not sending to %#v, message email disabled", user.Id.String())
					}
				} else {
					c.Errorf("Trying to load user %#v: %v", member.UserId.String(), err)
				}
			}
		}
	}

	return
}
Example #8
0
func IncomingMail(c common.SkinnyContext, msg *enmime.MIMEBody) (err error) {
	text := gmail.DecodeText(msg.Text, msg.GetHeader("Content-Type"))
	c.Debugf("Incoming mail to %#v\n%v", msg.GetHeader("To"), text)
	if match := gmail.AddrReg.FindString(msg.GetHeader("To")); match != "" {
		lines := []string{}
		mailUser := strings.Split(c.SendAddress(), "@")[0]
		for _, line := range strings.Split(text, "\n") {
			if !strings.Contains(line, mailUser) && strings.Index(line, ">") != 0 {
				lines = append(lines, line)
			}
		}
		for len(lines) > 0 && strings.TrimSpace(lines[0]) == "" {
			lines = lines[1:]
		}
		for len(lines) > 0 && strings.TrimSpace(lines[len(lines)-1]) == "" {
			lines = lines[:len(lines)-1]
		}
		if len(lines) > 0 {
			if match2 := emailPlusReg.FindStringSubmatch(match); match2 != nil {
				var tag *MailTag
				if tag, err = DecodeMailTag(c.Secret(), match2[1]); err == nil {
					sender := &Member{Id: tag.R}
					if err = c.DB().Get(sender); err != nil {
						return
					}
					parent := &Message{Id: tag.M}
					if err = c.DB().Get(parent); err != nil {
						return
					}
					game := &Game{Id: parent.GameId}
					if err = c.DB().Get(game); err != nil {
						return
					}
					message := &Message{
						Body:         strings.TrimSpace(strings.Join(lines, "\n")),
						GameId:       game.Id,
						RecipientIds: parent.RecipientIds,
					}
					c.Infof("Mail resulted in %+v from %+v", message, sender.Nation)
					return message.Send(c, game, sender)
				}
			}
		}
	}
	return nil
}
Example #9
0
func (self *Message) EmailTo(c common.SkinnyContext, game *Game, sender *Member, senderUser *user.User, recip *Member, recipUser *user.User, subject, recipName string) {
	mailTag := &MailTag{
		M: self.Id,
		R: recip.Id,
	}
	mailTag.H = mailTag.Hash(c.Secret())
	encodedMailTag, err := mailTag.Encode()
	if err != nil {
		c.Errorf("Failed to encode %+v: %v", mailTag, err)
		return
	}

	unsubTag := &common.UnsubscribeTag{
		T: common.UnsubscribeMessageEmail,
		U: recipUser.Id,
	}
	unsubTag.H = unsubTag.Hash(c.Secret())
	encodedUnsubTag, err := unsubTag.Encode()
	if err != nil {
		c.Errorf("Failed to encode %+v: %v", unsubTag, err)
		return
	}

	parts := strings.Split(c.ReceiveAddress(), "@")
	if len(parts) != 2 {
		if c.Env() == common.Development {
			parts = []string{"user", "host.tld"}
		} else {
			c.Errorf("Failed parsing %#v as an email address", c.ReceiveAddress())
			return
		}
	}
	senderName := sender.ShortName(game, senderUser)
	replyTo := fmt.Sprintf("%v+%v@%v", parts[0], encodedMailTag, parts[1])
	to := fmt.Sprintf("%v <%v>", recipName, recipUser.Email)
	memberIds := []string{}
	for memberId, _ := range self.RecipientIds {
		memberIds = append(memberIds, memberId)
	}
	sort.Sort(sort.StringSlice(memberIds))
	contextLink, err := recipUser.I("To see this message in context: http://%v/games/%v/messages/%v", recipUser.DiplicityHost, self.GameId, self.ChannelId())
	if err != nil {
		c.Errorf("Failed translating context link: %v", err)
		return
	}
	unsubLink, err := recipUser.I("To unsubscribe: http://%v/unsubscribe/%v", recipUser.DiplicityHost, encodedUnsubTag)
	if err != nil {
		c.Errorf("Failed translating unsubscribe link: %v", err)
		return
	}
	body := fmt.Sprintf(common.EmailTemplate, self.Body, contextLink, unsubLink)
	c.SendMail(senderName, replyTo, subject, body, []string{to})
}
Example #10
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
}
Example #11
0
func (self *Game) end(c common.SkinnyContext, phase *Phase, members Members, winner *Member, reason common.EndReason) (err error) {
	self.EndReason = reason
	self.State = common.GameStateEnded
	if err = c.DB().Set(self); err != nil {
		return
	}
	phase.Resolved = true
	if err = c.DB().Set(phase); err != nil {
		return
	}
	if self.Ranking && winner != nil {
		pot := 0.0
		spend := 0.0
		for index, _ := range members {
			if !members[index].Id.Equals(winner.Id) {
				user := &user.User{Id: members[index].UserId}
				if err = c.DB().Get(user); err != nil {
					return
				}
				spend = user.Ranking * RankingBlind
				pot += spend
				user.Ranking -= spend
				if err = c.DB().Set(user); err != nil {
					return
				}
			}
		}
		winnerUser := &user.User{Id: winner.UserId}
		if err = c.DB().Get(winnerUser); err != nil {
			return
		}
		winnerUser.Ranking += pot
		if err = c.DB().Set(winnerUser); err != nil {
			return
		}
	}
	return
}
Example #12
0
func (self *Game) endPhaseConsequences(c common.SkinnyContext, phase *Phase, member *Member, opts dip.Options, waitFor, active, nonSurrendering *[]*Member) (err error) {
	surrender := false
	if !member.Committed {
		alreadyHitReliability := false
		if (self.NonCommitConsequences & common.ReliabilityHit) == common.ReliabilityHit {
			if err = member.ReliabilityDelta(c.DB(), -1); err != nil {
				return
			}
			c.Infof("Increased MISSED deadlines for %#v by one because %+v and %+v", string(member.UserId), self, phase)
			alreadyHitReliability = true
		}
		if (self.NonCommitConsequences & common.NoWait) == common.NoWait {
			c.Infof("Setting %#v to NoWait because of %+v and %+v", string(member.UserId), self, phase)
			member.NoWait = true
		}
		if (self.NonCommitConsequences & common.Surrender) == common.Surrender {
			c.Infof("Setting %#v to Surrender because of %+v and %+v", string(member.UserId), self, phase)
			surrender = true
		}
		if len(phase.Orders[member.Nation]) == 0 {
			if !alreadyHitReliability && (self.NMRConsequences&common.ReliabilityHit) == common.ReliabilityHit {
				if err = member.ReliabilityDelta(c.DB(), -1); err != nil {
					return
				}
				c.Infof("Increased MISSED deadlines for %#v by one because %+v and %+v", string(member.UserId), self, phase)
			}
			if (self.NMRConsequences & common.NoWait) == common.NoWait {
				c.Infof("Setting %#v to NoWait because of %+v and %+v", string(member.UserId), self, phase)
				member.NoWait = true
			}
			if (self.NMRConsequences & common.Surrender) == common.Surrender {
				c.Infof("Setting %#v to Surrender because of %+v and %+v", string(member.UserId), self, phase)
				surrender = true
			}
		}
	} else {
		if (self.NonCommitConsequences & common.ReliabilityHit) == common.ReliabilityHit {
			if err = member.ReliabilityDelta(c.DB(), 1); err != nil {
				return
			}
			c.Infof("Increased HELD deadlines for %#v by one because %+v and %+v", string(member.UserId), self, phase)
		}
	}
	if !surrender {
		*nonSurrendering = append(*nonSurrendering, member)
	}
	member.Options = opts
	if member.NoWait {
		member.Committed = false
		member.NoOrders = false
	} else {
		*active = append(*active, member)
		if len(opts) == 0 {
			member.Committed = true
			member.NoOrders = true
		} else {
			*waitFor = append(*waitFor, member)
			member.Committed = false
			member.NoOrders = false
		}
	}
	if err = c.DB().Set(member); err != nil {
		return
	}
	return
}
Example #13
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
}
Example #14
0
func (self *Phase) SendStartedEmails(c common.SkinnyContext, game *Game) (err error) {
	members, err := game.Members(c.DB())
	if err != nil {
		return
	}
	for _, member := range members {
		user := &user.User{Id: member.UserId}
		if err = c.DB().Get(user); err != nil {
			return
		}
		if !user.PhaseEmailDisabled {
			subKey := fmt.Sprintf("/games/%v", game.Id)
			if !c.IsSubscribing(user.Email, subKey, common.SubscriptionTimeout) {
				if err = self.emailTo(c, game, &member, user); err != nil {
					c.Errorf("Failed sending to %#v: %v", user.Id.String(), err)
					return
				}
			} else {
				c.Infof("Not sending to %#v, already subscribing to %#v", user.Email, subKey)
			}
		} else {
			c.Infof("Not sending to %#v, phase email disabled", user.Email)
		}
	}

	return
}
Example #15
0
func (self *Phase) SendScheduleEmails(c common.SkinnyContext, game *Game) {
	members, err := game.Members(c.DB())
	for _, member := range members {
		user := &user.User{Id: member.UserId}
		if err = c.DB().Get(user); err != nil {
			return
		}
		to := fmt.Sprintf("%v <%v>", member.Nation, user.Email)
		if !user.PhaseEmailDisabled && !c.IsSubscribing(user.Email, fmt.Sprintf("/games/%v", game.Id)) {
			unsubTag := &common.UnsubscribeTag{
				T: common.UnsubscribePhaseEmail,
				U: user.Id,
			}
			unsubTag.H = unsubTag.Hash(c.Secret())
			encodedUnsubTag, err := unsubTag.Encode()
			if err != nil {
				c.Errorf("Failed to encode %+v: %v", unsubTag, err)
				return
			}
			contextLink, err := user.I("To see this in context: http://%v/games/%v", user.DiplicityHost, self.GameId)
			if err != nil {
				c.Errorf("Failed translating context link: %v", err)
				return
			}
			unsubLink, err := user.I("To unsubscribe: http://%v/unsubscribe/%v", user.DiplicityHost, encodedUnsubTag)
			if err != nil {
				c.Errorf("Failed translating unsubscribe link: %v", err)
				return
			}
			text, err := user.I("A new phase has been created")
			if err != nil {
				c.Errorf("Failed translating: %v", err)
				return
			}
			subject, err := game.Describe(c, user)
			if err != nil {
				c.Errorf("Failed describing: %v", err)
				return
			}
			body := fmt.Sprintf(common.EmailTemplate, text, contextLink, unsubLink)
			c.SendMail("diplicity", c.ReceiveAddress(), subject, body, []string{to})
		}
	}
}