Skip to content

Commit

Permalink
feat: implement room (#2)
Browse files Browse the repository at this point in the history
  • Loading branch information
ryanditjia authored Dec 16, 2024
2 parents f401e57 + e5736f5 commit 127e7ef
Show file tree
Hide file tree
Showing 11 changed files with 331 additions and 5 deletions.
2 changes: 1 addition & 1 deletion cardinal/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ golangci_version=v1.56.2
lint-install:
@if ! command -v golangci-lint &> /dev/null; then \
echo "--> golangci-lint is not installed. Installing..."; \
@go install github.com/golangci/golangci-lint/cmd/golangci-lint@$(golangci_version); \
go install github.com/golangci/golangci-lint/cmd/golangci-lint@$(golangci_version); \
fi

# Run golangci-lint on the project
Expand Down
13 changes: 13 additions & 0 deletions cardinal/component/room.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package component

type Room struct {
Owner string `json:"owner"`
PlayerX int `json:"player_x"`
PlayerY int `json:"player_y"`
GoalX int `json:"goal_x"`
GoalY int `json:"goal_y"`
}

func (Room) Name() string {
return "Room"
}
25 changes: 21 additions & 4 deletions cardinal/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ import (

"github.com/rs/zerolog/log"
"pkg.world.dev/world-engine/cardinal"

"argus-exercise-1-backend/component"
"argus-exercise-1-backend/msg"
"argus-exercise-1-backend/query"
"argus-exercise-1-backend/system"
)

func main() {
Expand All @@ -23,21 +28,33 @@ func main() {
func MustInitWorld(w *cardinal.World) {
// Register components
// NOTE: You must register your components here for it to be accessible.
// Must()
Must(
cardinal.RegisterComponent[component.Room](w),
)

// Register messages (user action)
// NOTE: You must register your transactions here for it to be executed.
// Must()
Must(
cardinal.RegisterMessage[msg.CreateRoomMsg, msg.CreateRoomResult](w, "create-room"),
cardinal.RegisterMessage[msg.DeleteRoomMsg, msg.DeleteRoomResult](w, "delete-room"),
cardinal.RegisterMessage[msg.MoveMsg, msg.MoveMsgReply](w, "move"),
)

// Register queries
// NOTE: You must register your queries here for it to be accessible.
// Must()
Must(
cardinal.RegisterQuery[query.RoomStateRequest, query.RoomStateResponse](w, "room-state", query.RoomState),
)

// Each system executes deterministically in the order they are added.
// This is a neat feature that can be strategically used for systems that depends on the order of execution.
// For example, you may want to run the attack system before the regen system
// so that the player's HP is subtracted (and player killed if it reaches 0) before HP is regenerated.
// Must(cardinal.RegisterSystems(w))
Must(cardinal.RegisterSystems(w,
system.RoomSpawnerSystem,
system.RoomDeleterSystem,
system.MoverSystem,
))

// Must(cardinal.RegisterInitSystems(w))
}
Expand Down
7 changes: 7 additions & 0 deletions cardinal/msg/create_room.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package msg

type CreateRoomMsg struct{}

type CreateRoomResult struct {
Success bool `json:"success"`
}
9 changes: 9 additions & 0 deletions cardinal/msg/delete_room.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package msg

type DeleteRoomMsg struct {
Owner string
}

type DeleteRoomResult struct {
Success bool `json:"success"`
}
18 changes: 18 additions & 0 deletions cardinal/msg/move.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package msg

type Direction string

const (
Up Direction = "up"
Down Direction = "down"
Left Direction = "left"
Right Direction = "right"
)

type MoveMsg struct {
Direction Direction
}

type MoveMsgReply struct {
Success bool `json:"success"`
}
66 changes: 66 additions & 0 deletions cardinal/query/room_state.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package query

import (
"fmt"

"pkg.world.dev/world-engine/cardinal/filter"
"pkg.world.dev/world-engine/cardinal/types"

comp "argus-exercise-1-backend/component"

"pkg.world.dev/world-engine/cardinal"
)

type RoomStateRequest struct {
Owner string
}

type RoomStateResponse struct {
PlayerX int `json:"player_x"`
PlayerY int `json:"player_y"`
GoalX int `json:"goal_x"`
GoalY int `json:"goal_y"`
}

func RoomState(world cardinal.WorldContext, req *RoomStateRequest) (*RoomStateResponse, error) {
var roomState *comp.Room
var err error
searchErr := cardinal.NewSearch().Entity(
filter.Exact(filter.Component[comp.Room]())).
Each(world, func(id types.EntityID) bool {
var room *comp.Room
room, err = cardinal.GetComponent[comp.Room](world, id)
if err != nil {
return false
}

// Terminates the search if the room is found
if room.Owner == req.Owner {
roomState, err = cardinal.GetComponent[comp.Room](world, id)
if err != nil {
return false
}
return false
}

// Continue searching if the room is not the target room
return true
})
if searchErr != nil {
return nil, searchErr
}
if err != nil {
return nil, err
}

if roomState == nil {
return nil, fmt.Errorf("room %s does not exist", req.Owner)
}

return &RoomStateResponse{
PlayerX: roomState.PlayerX,
PlayerY: roomState.PlayerY,
GoalX: roomState.GoalX,
GoalY: roomState.GoalY,
}, nil
}
46 changes: 46 additions & 0 deletions cardinal/system/mover.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package system

import (
"pkg.world.dev/world-engine/cardinal"

comp "argus-exercise-1-backend/component"
"argus-exercise-1-backend/msg"
)

func MoverSystem(world cardinal.WorldContext) error {
return cardinal.EachMessage[msg.MoveMsg, msg.MoveMsgReply](
world,
func(move cardinal.TxData[msg.MoveMsg]) (msg.MoveMsgReply, error) {
roomID, room, err := queryTargetRoom(world, move.Tx.PersonaTag)
if err != nil {
return msg.MoveMsgReply{}, err
}

switch move.Msg.Direction {
case msg.Up:
if room.PlayerY > 0 {
room.PlayerY--
}
case msg.Down:
if room.PlayerY < yLimit-1 {
room.PlayerY++
}
case msg.Left:
if room.PlayerX > 0 {
room.PlayerX--
}
case msg.Right:
if room.PlayerX < xLimit-1 {
room.PlayerX++
}
}

err = cardinal.SetComponent[comp.Room](world, roomID, room)

if err != nil {
return msg.MoveMsgReply{}, err
}

return msg.MoveMsgReply{Success: true}, nil
})
}
25 changes: 25 additions & 0 deletions cardinal/system/room_deleter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package system

import (
"pkg.world.dev/world-engine/cardinal"

"argus-exercise-1-backend/msg"
)

func RoomDeleterSystem(world cardinal.WorldContext) error {
return cardinal.EachMessage[msg.DeleteRoomMsg, msg.DeleteRoomResult](
world,
func(del cardinal.TxData[msg.DeleteRoomMsg]) (msg.DeleteRoomResult, error) {
roomID, _, err := queryTargetRoom(world, del.Msg.Owner)
if err != nil {
return msg.DeleteRoomResult{}, err
}

err = cardinal.Remove(world, roomID)
if err != nil {
return msg.DeleteRoomResult{}, err
}

return msg.DeleteRoomResult{Success: true}, nil
})
}
77 changes: 77 additions & 0 deletions cardinal/system/room_spawner.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package system

import (
"crypto/rand"
"fmt"
"math/big"

"pkg.world.dev/world-engine/cardinal"

comp "argus-exercise-1-backend/component"
"argus-exercise-1-backend/msg"
)

const (
xLimit = 9
yLimit = 9
// player starts at bottom center
playerInitialX = 4
playerInitialY = 8
)

func secureIntn(xLimit int) (int, error) {
n, err := rand.Int(rand.Reader, big.NewInt(int64(xLimit)))
if err != nil {
return 0, err
}
return int(n.Int64()), nil
}

func randomizeGoal() (int, int, error) {
goalX, err := secureIntn(xLimit)
if err != nil {
return 0, 0, err
}

goalY, err := secureIntn(yLimit)
if err != nil {
return 0, 0, err
}

if goalX == playerInitialX && goalY == playerInitialY {
return randomizeGoal() // Recursive call if the goal is same as player’s initial position
}

return goalX, goalY, nil
}

func RoomSpawnerSystem(world cardinal.WorldContext) error {
return cardinal.EachMessage[msg.CreateRoomMsg, msg.CreateRoomResult](
world,
func(create cardinal.TxData[msg.CreateRoomMsg]) (msg.CreateRoomResult, error) {
goalX, goalY, err := randomizeGoal()
if err != nil {
return msg.CreateRoomResult{}, fmt.Errorf("error randomizing goal: %w", err)
}
room := comp.Room{
Owner: create.Tx.PersonaTag,
PlayerX: playerInitialX,
PlayerY: playerInitialY,
GoalX: goalX,
GoalY: goalY,
}
id, err := cardinal.Create(world, room)
if err != nil {
return msg.CreateRoomResult{}, fmt.Errorf("error creating room: %w", err)
}

err = world.EmitEvent(map[string]any{
"event": "new_room",
"id": id,
})
if err != nil {
return msg.CreateRoomResult{}, err
}
return msg.CreateRoomResult{Success: true}, nil
})
}
48 changes: 48 additions & 0 deletions cardinal/system/utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package system

import (
"fmt"

"pkg.world.dev/world-engine/cardinal"
"pkg.world.dev/world-engine/cardinal/filter"
"pkg.world.dev/world-engine/cardinal/types"

comp "argus-exercise-1-backend/component"
)

// queryTargetRoom queries for the target room’s entity ID and room component.
func queryTargetRoom(world cardinal.WorldContext, targetOwner string) (types.EntityID, *comp.Room, error) {
var roomID types.EntityID
var room *comp.Room
var err error
searchErr := cardinal.NewSearch().Entity(
filter.Exact(filter.Component[comp.Room]())).Each(world,
func(id types.EntityID) bool {
var currentRoom *comp.Room
currentRoom, err = cardinal.GetComponent[comp.Room](world, id)
if err != nil {
return false
}

// Terminates the search if the room is found
if currentRoom.Owner == targetOwner {
roomID = id
room = currentRoom
return false
}

// Continue searching if the room is not the target room
return true
})
if searchErr != nil {
return 0, nil, err
}
if err != nil {
return 0, nil, err
}
if room == nil {
return 0, nil, fmt.Errorf("room %q does not exist", targetOwner)
}

return roomID, room, err
}

0 comments on commit 127e7ef

Please sign in to comment.