nquest/pkg/service/engine.go
2024-01-28 19:19:41 +00:00

151 lines
3.2 KiB
Go

package service
import (
"context"
"errors"
"strings"
"github.com/google/uuid"
"github.com/jackc/pgx/v5/pgconn"
"gitrepo.ru/neonxp/nquest/pkg/models"
"gorm.io/gorm"
)
var (
ErrGameNotStarted = errors.New("game not started")
ErrInvalidCode = errors.New("invalid code")
ErrOldCode = errors.New("old code")
ErrGameFinished = errors.New("game finished")
ErrNextLevel = errors.New("next level")
)
type Engine struct {
DB *gorm.DB
}
func NewEngine(db *gorm.DB) *Engine {
return &Engine{
DB: db,
}
}
func (e *Engine) GetState(ctx context.Context, game *models.Game, user *models.User) (*models.GameCursor, error) {
db := e.DB.WithContext(ctx)
// Пытаемся получить GameCursor
cursor := &models.GameCursor{
User: user,
Game: game,
Task: game.Tasks[0],
Status: models.TaskStarted,
Codes: make([]*models.Code, 0),
}
err := db.
Where(`user_id = ? and game_id = ? and status = ?`, user.ID, game.ID, models.TaskStarted).
Preload("Task").
Preload("Task.Codes").
Preload("Codes").
FirstOrCreate(cursor).
Error
if err != nil {
if err, ok := err.(*pgconn.PgError); ok {
if err.Code == "23505" {
return nil, ErrGameNotStarted
}
}
return nil, err
}
return cursor, nil
}
func (e *Engine) EnterCode(ctx context.Context, game *models.Game, user *models.User, code string) (*models.GameCursor, error) {
db := e.DB.WithContext(ctx)
st, err := e.GetState(ctx, game, user)
if err != nil {
return nil, err
}
return st, db.Transaction(func(tx *gorm.DB) error {
code = strings.Trim(code, " \n\t")
code = strings.ToLower(code)
var currentCode *models.Code
for _, c := range st.Task.Codes {
if c.Code == code {
currentCode = c
break
}
}
if currentCode == nil {
return ErrInvalidCode
}
for _, c := range st.Codes {
if c.ID == currentCode.ID {
return ErrOldCode
}
}
if err := db.Model(st).Association("Codes").Append(currentCode); err != nil {
return err
}
if len(st.Codes) != len(st.Task.Codes) {
return nil
}
// Уровень пройден. Выдаем следующий
if err := db.Model(st).UpdateColumn("Status", models.TaskFinished).Error; err != nil {
return err
}
nextTask, err := e.GetNext(ctx, game.ID, st.Task.TaskOrder)
if err != nil {
return err
}
if nextTask == nil {
user.Experience += st.Game.Points
if err := db.Model(user).UpdateColumn("Experience", user.Experience).Error; err != nil {
return err
}
if err := db.Model(st).UpdateColumn("Finish", true).Error; err != nil {
return err
}
return ErrGameFinished
}
st = &models.GameCursor{
User: user,
Game: game,
Task: nextTask,
Status: models.TaskStarted,
Codes: []*models.Code{},
}
if err := db.Create(st).Error; err != nil {
return err
}
return ErrNextLevel
})
}
func (e *Engine) GetNext(ctx context.Context, gameID uuid.UUID, currentOrder uint) (*models.Task, error) {
var t models.Task
err := e.DB.WithContext(ctx).
Preload("Codes").
Order("task_order ASC").
First(&t, `game_id = ? AND task_order > ?`, gameID, currentOrder).
Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, nil
}
return nil, err
}
return &t, nil
}