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 }