package game import ( "context" "encoding/json" "fmt" "log" "omctf.ru/block-game-backend/db" "omctf.ru/block-game-backend/messaging" "omctf.ru/block-game-backend/utils" "omctf.ru/block-game-backend/utils/xy" ) type Session struct { Conn *messaging.Conn UserId int LevelId int Tiles Tiles } func (sess *Session) Start() { sess.Conn.Start() go sess.LoopReceive() } func (sess *Session) LoopReceive() { defer sess.Conn.Close() for { msg, ok := <-sess.Conn.Recv if !ok { return } err := sess.processMessage(msg) if _, ok := err.(messaging.ErrorBadRequest); ok { sess.Conn.Error(err) } else if err != nil { sess.Conn.Error(fmt.Errorf("internal server error")) return } } } type Move struct { Direction xy.Direction `json:"direction"` } type Update struct { Idx int `json:"idx"` NewTile Tile `json:"new_tile"` } func (sess *Session) MoveTile(tile *Tile, newPos xy.Point) { idx := sess.Tiles.Index(tile) if idx == -1 { log.Panicf("tile not found in session") } sess.MoveTileAt(idx, newPos) } func (sess *Session) MoveTileAt(tileIdx int, newPos xy.Point) { tile := &sess.Tiles.Tiles[tileIdx] tile.Pos = newPos sess.Conn.Send(messaging.Message{ Type: "update", Option: utils.MustMarshal(map[string]any{ "idx": tileIdx, "new_tile": tile, }), }) } func unpackMessageData(msg messaging.Message) (any, error) { switch msg.Type { case "move": var move Move err := json.Unmarshal(msg.Option, &move) if err != nil { return nil, messaging.ErrorBadRequestf("invalid option: %w", err) } if err := move.Direction.Validate(); err != nil { return nil, messaging.ErrorBadRequestf("invalid direction: %w", err) } return move, nil } return nil, messaging.ErrorBadRequestf("unknown message type") } func (sess *Session) processMessage(message messaging.Message) error { data, err := unpackMessageData(message) if err != nil { return err } switch data := data.(type) { case Move: player, err := sess.Tiles.GetPlayerTile() if err != nil || player == nil { return messaging.ErrorBadRequestf("can't get player tile: %w", err) } newPos, err := player.Pos.Go(data.Direction) if err != nil { return err } if !sess.MoveOutOfWay(newPos, data.Direction) { return nil } sess.MoveTile(player, newPos) if sess.Tiles.At(newPos).Has("exit") { level, err := db.Client.Level.Get(context.Background(), sess.LevelId) var prize string if level == nil || err != nil { prize = "can't get level prize" } else { prize = level.Prize } sess.Conn.Send(messaging.Message{ Type: "level_complete", Option: utils.MustMarshal(map[string]any{ "prize": prize, }), }) } default: return messaging.ErrorBadRequestf("unknown message type") } return nil } type DoorData struct { ButtonPos xy.Point `json:"button_position"` } func (sess *Session) isDoorOpen(door *Tile) bool { if door.Data == nil { return false } var doorData DoorData err := json.Unmarshal(door.Data, &doorData) if err != nil { return false } tiles := sess.Tiles.At(doorData.ButtonPos) return len(tiles) > 0 } func (sess *Session) MoveOutOfWay(p xy.Point, dir xy.Direction) bool { tiles := sess.Tiles.At(p) wall, err := tiles.GetTheOnly("wall") if err != nil || wall != nil { return false } door, err := tiles.GetTheOnly("door") if err != nil { return false } if door != nil && !sess.isDoorOpen(door) { return false } box, err := tiles.GetTheOnly("box") if err != nil { return false } if box == nil { return true } newPos, err := box.Pos.Go(dir) if err != nil { return false } if sess.MoveOutOfWay(newPos, dir) { sess.MoveTile(box, newPos) return true } else { return false } }