Skip to content

Commit

Permalink
Merge pull request #215 from ubclaunchpad/brian/arena-tests
Browse files Browse the repository at this point in the history
Brian/arena tests
  • Loading branch information
brian-nguyen committed May 31, 2018
2 parents 0e59751 + 40704f1 commit a0255e6
Show file tree
Hide file tree
Showing 6 changed files with 255 additions and 40 deletions.
2 changes: 2 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ before_install:
- dep ensure
- if [ ! -f "bin/gometalinter" ]; then curl -sfL https://install.goreleaser.com/github.com/alecthomas/gometalinter.sh | bash; fi

install: true

script:
- cd $TRAVIS_BUILD_DIR/client
- npm run lint
Expand Down
89 changes: 64 additions & 25 deletions server/arena/arena.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,8 @@ import (
"github.com/ubclaunchpad/bumper/server/models"
)

// Game related constants
// Arena related constants
const (
JunkCount = 30
HoleCount = 20
MinDistanceBetween = models.MaxHoleRadius
)

Expand All @@ -32,20 +30,22 @@ type Arena struct {
}

// CreateArena constructor for arena initializes holes and junk
func CreateArena(height float64, width float64) *Arena {
a := Arena{sync.RWMutex{}, height, width, nil, nil, nil}
a.Players = make(map[string]*models.Player)
func CreateArena(height float64, width float64, holeCount int, junkCount int) *Arena {
a := Arena{
sync.RWMutex{},
height,
width,
make([]*models.Hole, 0, holeCount),
make([]*models.Junk, 0, junkCount),
make(map[string]*models.Player),
}

for i := 0; i < HoleCount; i++ {
position := a.generateCoordinate(models.MinHoleRadius)
hole := models.CreateHole(position)
a.Holes = append(a.Holes, hole)
for i := 0; i < holeCount; i++ {
a.addHole()
}

for i := 0; i < JunkCount; i++ {
position := a.generateCoordinate(models.JunkRadius)
junk := models.CreateJunk(position)
a.Junk = append(a.Junk, &junk)
for i := 0; i < junkCount; i++ {
a.addJunk()
}

return &a
Expand All @@ -56,11 +56,8 @@ func (a *Arena) UpdatePositions() {
for i, hole := range a.Holes {
hole.Update()
if hole.IsDead() {
// remove that hole from the holes
a.Holes = append(a.Holes[:i], a.Holes[i+1:]...)
// generate a new hole
hole = models.CreateHole(a.generateCoordinate(models.MaxHoleRadius))
a.Holes = append(a.Holes, hole)
a.removeHole(i)
a.addHole()
}
}
for _, junk := range a.Junk {
Expand All @@ -78,6 +75,20 @@ func (a *Arena) CollisionDetection() {
a.junkCollisions()
}

// GetState assembles an UpdateMessage from the current state of the arena
func (a *Arena) GetState() *models.UpdateMessage {
players := make([]*models.Player, 0, len(a.Players))
for _, player := range a.Players {
players = append(players, player)
}

return &models.UpdateMessage{
Holes: a.Holes,
Junk: a.Junk,
Players: players,
}
}

// AddPlayer adds a new player to the arena
func (a *Arena) AddPlayer(id string, n string, ws *websocket.Conn) error {
color, err := a.generateRandomColor()
Expand Down Expand Up @@ -185,10 +196,8 @@ func (a *Arena) holeCollisions() {
playerScored.AddPoints(models.PointsPerJunk)
}

// remove that junk from the junk
a.Junk = append(a.Junk[:i], a.Junk[i+1:]...)
//create a new junk to hold the count steady
a.generateJunk()
a.removeJunk(i)
a.addJunk()
} else if areCirclesColliding(junk.Position, models.JunkRadius, hole.Position, hole.GravityRadius) {
junk.ApplyGravity(hole)
}
Expand Down Expand Up @@ -245,8 +254,38 @@ func (a *Arena) generateRandomColor() (string, error) {
}

// adds a junk in a random spot
func (a *Arena) generateJunk() {
func (a *Arena) addJunk() {
position := a.generateCoordinate(models.JunkRadius)
junk := models.CreateJunk(position)
a.Junk = append(a.Junk, &junk)
a.Junk = append(a.Junk, junk)
}

// remove junk without considering order
func (a *Arena) removeJunk(index int) bool {
if len(a.Junk) < index+1 {
return false
}

a.Junk[index] = a.Junk[len(a.Junk)-1]
a.Junk[len(a.Junk)-1] = nil
a.Junk = a.Junk[:len(a.Junk)-1]
return true
}

// adds a hole in a random spot
func (a *Arena) addHole() {
h := models.CreateHole(a.generateCoordinate(models.MinHoleRadius))
a.Holes = append(a.Holes, h)
}

// remove hole without considering order
func (a *Arena) removeHole(index int) bool {
if len(a.Holes) < index+1 {
return false
}

a.Holes[index] = a.Holes[len(a.Holes)-1]
a.Holes[len(a.Holes)-1] = nil
a.Holes = a.Holes[:len(a.Holes)-1]
return true
}
183 changes: 183 additions & 0 deletions server/arena/arena_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
package arena

import (
"fmt"
"testing"

"github.com/ubclaunchpad/bumper/server/models"
)

const (
testHeight = 2400
testWidth = 2800
testHoleCount = 20
testJunkCount = 30
)

var (
testVelocity = models.Velocity{Dx: 1, Dy: 1}
centerPosition = models.Position{X: testWidth / 2, Y: testHeight / 2}
quarterPosition = models.Position{X: testWidth / 4, Y: testHeight / 4}
)

func CreateArenaWithPlayer(p models.Position) *Arena {
a := CreateArena(testHeight, testWidth, 0, 0)
a.AddPlayer("test", "", nil)
testPlayer := a.Players["test"]
testPlayer.Position = p
testPlayer.Velocity = testVelocity
return a
}

func TestCreateArena(t *testing.T) {
a := CreateArena(testHeight, testWidth, testHoleCount, testJunkCount)

holeCount := len(a.Holes)
if holeCount != testHoleCount {
t.Errorf("Arena did not spawn enough holes. Got %d/%d Holes", holeCount, testHoleCount)
}

junkCount := len(a.Junk)
if junkCount != testJunkCount {
t.Errorf("Arena did not spawn enough junk. Got %d/%d Junk", junkCount, testJunkCount)
}
}

func TestAddPlayer(t *testing.T) {
a := CreateArena(testHeight, testWidth, testHoleCount, testJunkCount)

numPlayers := 3
for i := 0; i < numPlayers; i++ {
t.Run("TestAddPlayer", func(t *testing.T) {

name := fmt.Sprintf("player%d", i)
err := a.AddPlayer(name, name, nil)
if err != nil {
t.Errorf("Failed to add player: %v", err)
}

if len(a.Players) != i+1 {
t.Errorf("Player map error")
}
})
}
}

func TestAddRemoveObject(t *testing.T) {
a := CreateArena(testHeight, testWidth, 0, 0)

testCount := 10
testCases := []struct {
description string
remove func(int) bool
add func()
}{
{"Holes", a.removeHole, a.addHole},
{"Junk", a.removeJunk, a.addJunk},
}
for _, tc := range testCases {
t.Run(tc.description, func(t *testing.T) {
for i := 0; i < testCount; i++ {
tc.add()
}

for i := 0; i < testCount; i++ {
ok := tc.remove(0)
if !ok {
t.Errorf("%s removal error at count %d", tc.description, i)
}
}

ok := tc.remove(0)
if ok {
t.Errorf("Removal from empty slice of %s returned true", tc.description)
}
})
}
}

func TestPlayerToPlayerCollisions(t *testing.T) {
testCases := []struct {
otherPlayer string
testPosition models.Position
expectedPosition models.Position
}{
{"colliding", quarterPosition, models.Position{X: 700.375, Y: 600.375}},
{"non-colliding", centerPosition, quarterPosition},
}

for _, tc := range testCases {
t.Run("Player to Player collision", func(t *testing.T) {
a := CreateArenaWithPlayer(quarterPosition)

a.AddPlayer(tc.otherPlayer, "", nil)
a.Players[tc.otherPlayer].Position = tc.testPosition

a.playerCollisions()
if a.Players["test"].Position != tc.expectedPosition {
t.Errorf("%s detection failed. Got player at %v. Expected player at %v", tc.otherPlayer, a.Players["test"].Position, tc.expectedPosition)
}
})
}
}

func TestPlayerToJunkCollisions(t *testing.T) {
a := CreateArenaWithPlayer(quarterPosition)

testCases := []struct {
description string
testPosition models.Position
expectedPlayer *models.Player
}{
{"non-colliding", centerPosition, nil},
{"colliding", quarterPosition, a.Players["test"]},
}
for i, tc := range testCases {
t.Run(tc.description, func(t *testing.T) {
a.addJunk()
a.Junk[i].Position = tc.testPosition

a.playerCollisions()
if a.Junk[i].LastPlayerHit != tc.expectedPlayer {
t.Errorf("%s detection failed. Test Player at %v. Junk at %v. Junk Last Player Hit %v", tc.description, a.Players["test"].Position, a.Junk[i].Position, a.Junk[i].LastPlayerHit)
}
})
}
}

func TestJunkToJunkCollisions(t *testing.T) {
testCases := []struct {
description string
testPosition models.Position
expectedVelocity models.Velocity
}{
{"non-colliding", centerPosition, testVelocity},
{"colliding", quarterPosition, models.Velocity{Dx: -0.5, Dy: -0.5}},
}
for _, tc := range testCases {
t.Run(tc.description, func(t *testing.T) {
a := CreateArena(testHeight, testWidth, 0, 0)
a.addJunk()
a.Junk[0].Position = quarterPosition
a.Junk[0].Velocity = testVelocity

a.addJunk()
a.Junk[1].Position = tc.testPosition

a.junkCollisions()
if a.Junk[0].Velocity != tc.expectedVelocity {
t.Errorf("%s detection failed. Expected %v. Got %v", tc.description, tc.expectedVelocity, a.Junk[0].Velocity)
}
})
}
}

// TODO: Complete once Game package refactoring has happened
func TestHoleToPlayerCollisions(t *testing.T) {

}

// TODO: Complete once Game package refactoring has happened
func TestHoleToJunkCollisions(t *testing.T) {

}
13 changes: 2 additions & 11 deletions server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,18 +105,9 @@ func tick(g *Game) {
for {
time.Sleep(g.RefreshRate)

players := make([]*models.Player, 0, len(g.Arena.Players))
for _, player := range g.Arena.Players {
players = append(players, player)
}

msg := models.Message{
Type: "update",
Data: models.UpdateMessage{
Holes: g.Arena.Holes,
Junk: g.Arena.Junk,
Players: players,
},
Data: g.Arena.GetState(),
}

// update every client
Expand Down Expand Up @@ -184,7 +175,7 @@ func main() {

arena.MessageChannel = MessageChannel
game := Game{
Arena: arena.CreateArena(2400, 2800),
Arena: arena.CreateArena(2400, 2800, 20, 30),
RefreshRate: time.Millisecond * 17, // 60 Hz
}

Expand Down
4 changes: 2 additions & 2 deletions server/models/junk.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ type Junk struct {
}

// CreateJunk initializes and returns an instance of a Junk
func CreateJunk(position Position) Junk {
return Junk{
func CreateJunk(position Position) *Junk {
return &Junk{
Position: position,
Velocity: Velocity{0, 0},
Color: "white",
Expand Down
4 changes: 2 additions & 2 deletions server/models/junk_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ func TestJunkBumpJunk(t *testing.T) {
j2.Velocity = otherJunkVelocity

// Hit Junk with Other Junk
j1.HitJunk(&j2)
j1.HitJunk(j2)

// Both Junk's velocities should have been affected, not black boxed :(
if j1.Velocity.Dx != (initialJunkVelocity.Dx*-JunkVTransferFactor)+(otherJunkVelocity.Dx*JunkVTransferFactor) ||
Expand All @@ -221,7 +221,7 @@ func TestJunkBumpJunk(t *testing.T) {

// Second collision right away should have no effect because of the debounce period.
lastVelocity := j1.Velocity
j1.HitJunk(&j2)
j1.HitJunk(j2)
if j1.Velocity.Dx != lastVelocity.Dx || j1.Velocity.Dy != lastVelocity.Dy {
t.Error("Error: Junk/Junk collision debouncing failed")
}
Expand Down

0 comments on commit a0255e6

Please sign in to comment.