152 lines
3.2 KiB
Go
152 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").
|
|
Preload("Solutions").
|
|
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
|
|
}
|