adding validated services? patching forcad_local.py
This commit is contained in:
176
OmCTF-2025/services/block_game/backend/game/game.go
Normal file
176
OmCTF-2025/services/block_game/backend/game/game.go
Normal file
@@ -0,0 +1,176 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
136
OmCTF-2025/services/block_game/backend/game/tiles.go
Normal file
136
OmCTF-2025/services/block_game/backend/game/tiles.go
Normal file
@@ -0,0 +1,136 @@
|
||||
package game
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"omctf.ru/block-game-backend/schema"
|
||||
"omctf.ru/block-game-backend/utils/xy"
|
||||
)
|
||||
|
||||
type Tile struct {
|
||||
Kind string `json:"kind"`
|
||||
Pos xy.Point `json:"pos"`
|
||||
Data json.RawMessage `json:"data"`
|
||||
}
|
||||
|
||||
type Tiles struct {
|
||||
size int
|
||||
Tiles []Tile
|
||||
}
|
||||
|
||||
func NewTiles(size int, tiles []schema.Tile) Tiles {
|
||||
var result Tiles
|
||||
result.size = size
|
||||
for _, tile := range tiles {
|
||||
result.Tiles = append(result.Tiles, Tile{
|
||||
Kind: tile.Kind,
|
||||
Pos: tile.Pos,
|
||||
Data: tile.Data,
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (t *Tiles) Size() int {
|
||||
return t.size
|
||||
}
|
||||
|
||||
func (t *Tiles) inBounds(p xy.Point) bool {
|
||||
return p.X >= 0 && p.Y >= 0 && p.X < t.size && p.Y < t.size
|
||||
}
|
||||
|
||||
func (t *Tiles) GetPlayerTile() (*Tile, error) {
|
||||
playerIdx, err := t.GetTheOnlyIdx("player")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if playerIdx == -1 {
|
||||
return nil, fmt.Errorf("player not found")
|
||||
}
|
||||
return &t.Tiles[playerIdx], nil
|
||||
}
|
||||
|
||||
func (t *Tiles) Index(tile *Tile) int {
|
||||
for i := range t.Tiles {
|
||||
if &t.Tiles[i] == tile {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func (t *Tiles) GetTheOnlyIdx(kind string) (int, error) {
|
||||
result := -1
|
||||
for i, tile := range t.Tiles {
|
||||
if tile.Kind == kind {
|
||||
if result != -1 {
|
||||
return -1, ErrDuplicateType{}
|
||||
}
|
||||
result = i
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (t *Tiles) GetTheOnly(kind string) (*Tile, error) {
|
||||
i, err := t.GetTheOnlyIdx(kind)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if i == -1 {
|
||||
return nil, nil
|
||||
}
|
||||
return &t.Tiles[i], nil
|
||||
}
|
||||
|
||||
type TilesAt []*Tile
|
||||
|
||||
func (t *Tiles) At(p xy.Point) TilesAt {
|
||||
var result TilesAt
|
||||
for i := range t.Tiles {
|
||||
if t.Tiles[i].Pos == p {
|
||||
result = append(result, &t.Tiles[i])
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
type ErrDuplicateType struct{}
|
||||
|
||||
func (e ErrDuplicateType) Error() string {
|
||||
return "multiple tiles of the requested type"
|
||||
}
|
||||
|
||||
func (t TilesAt) Has(kind string) bool {
|
||||
for _, tile := range t {
|
||||
if tile.Kind == kind {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (t TilesAt) GetTheOnlyIdx(kind string) (int, error) {
|
||||
result := -1
|
||||
for i, tile := range t {
|
||||
if tile.Kind == kind {
|
||||
if result != -1 {
|
||||
return -1, ErrDuplicateType{}
|
||||
}
|
||||
result = i
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (t TilesAt) GetTheOnly(kind string) (*Tile, error) {
|
||||
i, err := t.GetTheOnlyIdx(kind)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if i == -1 {
|
||||
return nil, nil
|
||||
}
|
||||
return t[i], nil
|
||||
}
|
||||
Reference in New Issue
Block a user