Skip to content
47 changes: 47 additions & 0 deletions .github/workflows/check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name: Lint

on:
workflow_call:

jobs:
bot2-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: astral-sh/setup-uv@v4

- uses: arduino/setup-task@v2

- name: Run bot2 linting
run: task bot2:lint

- name: Check bot2 formatting
run: task bot2:format:check

- name: Run bot2 type checking
run: task bot2:typecheck

backend-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-go@v5
with:
go-version: "1.24"

- uses: golangci/golangci-lint-action@v6
with:
working-directory: backend

- uses: arduino/setup-task@v2

- name: Check backend formatting
run: task be:format:check

- name: Run go vet
run: task be:vet

- name: Run build
run: task be:build
16 changes: 16 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
name: CI

on:
pull_request:
types: [opened, synchronize, reopened]

jobs:
lint:
uses: ./.github/workflows/check.yml

test:
uses: ./.github/workflows/test.yml

integration:
uses: ./.github/workflows/integration.yml
needs: [lint, test]
37 changes: 37 additions & 0 deletions .github/workflows/integration.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: Integration

on:
workflow_call:

jobs:
bot2-integration:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: astral-sh/setup-uv@v4

- uses: arduino/setup-task@v2

- name: Start services with docker compose
run: task compose

# TODO working health check endpoint
- name: Wait for services to be ready
run: |
echo "Waiting for services to be ready..."
for i in {1..30}; do
if curl -s http://localhost:4000/api/maps > /dev/null 2>&1; then
echo "Services are ready!"
break
fi
echo "Waiting... ($i/3)"
sleep 2
done

- name: Run bot2 integration tests (not slow)
run: task bot2:test:integration:fast

- name: Cleanup
if: always()
run: docker compose down
31 changes: 31 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: Test

on:
workflow_call:

jobs:
bot2-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: astral-sh/setup-uv@v4

- uses: arduino/setup-task@v2

- name: Run bot2 unit tests
run: task bot2:test:unit

backend-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-go@v5
with:
go-version: "1.24"

- uses: arduino/setup-task@v2

- name: Run backend tests
run: task be:test
14 changes: 14 additions & 0 deletions Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,14 @@ tasks:
deps:
- bot2:install

bot2:format:check:
dir: bot2
desc: Check bot2 code formatting (no fix)
cmds:
- uv run ruff format --check .
deps:
- bot2:install

bot2:typecheck:
dir: bot2
desc: Run bot2 type checking with ty
Expand Down Expand Up @@ -132,6 +140,12 @@ tasks:
cmds:
- go fmt ./...

be:format:check:
dir: backend
desc: Check backend Go code formatting (no fix)
cmds:
- test -z "$(gofmt -l .)"

be:vet:
dir: backend
desc: Run go vet on backend code
Expand Down
4 changes: 2 additions & 2 deletions backend/pkg/server/game_maps/map_metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,8 @@ func GetValidMapTypes() ([]string, error) {
for _, md := range metadata {
// Extract map type name from "meta/name.json" format
mapType := string(md.MapType)
mapType = mapType[5:] // Remove "meta/" prefix
mapType = mapType[:len(mapType)-5] // Remove ".json" suffix
mapType = mapType[5:] // Remove "meta/" prefix
mapType = mapType[:len(mapType)-5] // Remove ".json" suffix
mapTypes = append(mapTypes, mapType)
}
return mapTypes, nil
Expand Down
20 changes: 11 additions & 9 deletions backend/pkg/server/game_room_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -639,25 +639,27 @@ func TestGameRoomReset(t *testing.T) {
}

// Verify player state is reset
state := playerObj.GetState()
// Note: We use GetStateValue() instead of GetState() because GetState() applies
// extrapolation (including gravity) based on time elapsed since lastLocUpdateTime,
// which would cause flaky tests due to timing differences between local and CI.

// Velocity should be 0
if dx, ok := state["dx"].(float64); !ok || dx != 0.0 {
t.Errorf("Player dx should be 0 after reset, got %v", state["dx"])
if dx, exists := playerObj.GetStateValue("dx"); !exists || dx.(float64) != 0.0 {
t.Errorf("Player dx should be 0 after reset, got %v", dx)
}
if dy, ok := state["dy"].(float64); !ok || dy != 0.0 {
t.Errorf("Player dy should be 0 after reset, got %v", state["dy"])
if dy, exists := playerObj.GetStateValue("dy"); !exists || dy.(float64) != 0.0 {
t.Errorf("Player dy should be 0 after reset, got %v", dy)
}

// Arrows should be reset to starting count (4)
// Note: Arrow count is stored as int, not float64
if arrows, ok := state["ac"].(int); !ok || arrows != 4 {
t.Errorf("Player arrows should be 4 after reset, got %v", state["ac"])
if arrows, exists := playerObj.GetStateValue("ac"); !exists || arrows.(int) != 4 {
t.Errorf("Player arrows should be 4 after reset, got %v", arrows)
}

// Dead should be false
if dead, ok := state["dead"].(bool); !ok || dead {
t.Errorf("Player should not be dead after reset, got %v", state["dead"])
if dead, exists := playerObj.GetStateValue("dead"); !exists || dead.(bool) {
t.Errorf("Player should not be dead after reset, got %v", dead)
}

// Player should still exist in the room
Expand Down
24 changes: 12 additions & 12 deletions backend/pkg/server/geo/shape.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,13 @@ func (l *Line) GetCenter() *Point {
}

func (l *Line) CollidesWith(other Shape) (bool, []*Point) {
switch other.(type) {
switch o := other.(type) {
case *Line:
return checkLineLineCollision(l, other.(*Line))
return checkLineLineCollision(l, o)
case *Circle:
return checkLineCircleCollision(l, other.(*Circle))
return checkLineCircleCollision(l, o)
case *Polygon:
return checkLinePolygonCollision(l, other.(*Polygon))
return checkLinePolygonCollision(l, o)
}
return false, nil
}
Expand All @@ -72,13 +72,13 @@ func (c *Circle) GetCenter() *Point {
}

func (c *Circle) CollidesWith(other Shape) (bool, []*Point) {
switch other.(type) {
switch o := other.(type) {
case *Line:
return checkLineCircleCollision(other.(*Line), c)
return checkLineCircleCollision(o, c)
case *Circle:
return checkCircleCircleCollision(c, other.(*Circle))
return checkCircleCircleCollision(c, o)
case *Polygon:
return checkCirclePolygonCollision(c, other.(*Polygon))
return checkCirclePolygonCollision(c, o)
}
return false, nil
}
Expand Down Expand Up @@ -112,13 +112,13 @@ func (p *Polygon) GetLines() []*Line {
}

func (p *Polygon) CollidesWith(other Shape) (bool, []*Point) {
switch other.(type) {
switch o := other.(type) {
case *Line:
return checkLinePolygonCollision(other.(*Line), p)
return checkLinePolygonCollision(o, p)
case *Circle:
return checkCirclePolygonCollision(other.(*Circle), p)
return checkCirclePolygonCollision(o, p)
case *Polygon:
return checkPolygonPolygonCollision(p, other.(*Polygon))
return checkPolygonPolygonCollision(p, o)
}
return false, nil
}
Expand Down
2 changes: 1 addition & 1 deletion backend/pkg/server/geo/shape_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ func TestPolygonPolygonCollision(t *testing.T) {
},
},
want: true,
numPoints: 2, // Two points: one at each end of the overlapping edge (2,1) and (2,2)
numPoints: 2, // Two points: one at each end of the overlapping edge (2,1) and (2,2)
},
}

Expand Down
Loading
Loading