Skip to content

Commit

Permalink
Add object interface
Browse files Browse the repository at this point in the history
Remove unused rw mutexes

Delete manual pointer dereferencing

Dummy object for gravitational field

Disable db functions

Restore mutex for player websocket

Add missing null checks

Utilize getID function of player

Disable db functions cont..

Add unit test command

Updated tests
  • Loading branch information
brian-nguyen committed Nov 11, 2018
1 parent 68e2d9b commit ade87ef
Show file tree
Hide file tree
Showing 13 changed files with 214 additions and 277 deletions.
7 changes: 6 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,9 @@ client:
# Starts the server (exposed on port 9090)
.PHONY: server
server:
(cd ./server ; DATABASE_URL=$(DATABASE_URL) PORT=8081 gin -p $(SERVER_PORT) -a 8081 -i run main.go)
(cd ./server ; DATABASE_URL=$(DATABASE_URL) PORT=8081 gin -p $(SERVER_PORT) -a 8081 -i run main.go)

# Runs unit tests
.PHONY: test
test:
(cd ./server ; go test -race ./...)
6 changes: 3 additions & 3 deletions client/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ export default class App extends React.Component {
async componentDidMount() {
this.canvas = document.getElementById('ctx');
await this.connectPlayer();
registerNewTesterEvent();
registerTesterUpdateEvent();
// registerNewTesterEvent();
// registerTesterUpdateEvent();
}

openGameOverModal() {
Expand Down Expand Up @@ -198,7 +198,7 @@ export default class App extends React.Component {

// Drawing the walls requires the players position
const player = this.state.players.find(p => p.id === this.state.player.id);
if (player.name !== '') {
if (player && player.name !== '') {
drawWalls(player, this.state.arena, this.canvas);
}
}
Expand Down
1 change: 1 addition & 0 deletions client/components/GameObjects.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const JUNK_SIZE = 15;

export function drawGame(data, canvas) {
const rawPlayer = data.players.find(p => p.id === data.player.id);
if (!rawPlayer) return;
// Need to copy the player and his position as it gets altered below.
// If you don't do this and another frame is draw before the next update message is
// recieved than the translated position is used to calcluate the next translations and
Expand Down
63 changes: 37 additions & 26 deletions server/arena/arena.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
"sync"

"github.com/gorilla/websocket"
"github.com/ubclaunchpad/bumper/server/database"
"github.com/ubclaunchpad/bumper/server/models"
)

Expand Down Expand Up @@ -125,16 +124,18 @@ func (a *Arena) GetState() *models.UpdateMessage {
// AddPlayer adds a new player to the arena
// player has no position or name until spawned
// TODO player has no color until spawned
func (a *Arena) AddPlayer(id string, ws *websocket.Conn) error {
func (a *Arena) AddPlayer(ws *websocket.Conn) (*models.Player, error) {
a.rwMutex.Lock()
defer a.rwMutex.Unlock()

color, err := a.generateRandomColor()
if err != nil {
return err
return nil, err
}
a.Players[id] = models.CreatePlayer(id, "", models.Position{}, color, ws)
return nil

p := models.CreatePlayer("", color, ws)
a.Players[p.GetID()] = p
return p, nil
}

// GetPlayer gets the specified player
Expand All @@ -145,11 +146,11 @@ func (a *Arena) GetPlayer(id string) *models.Player {
}

// RemovePlayer removes the specified player from the arena
func (a *Arena) RemovePlayer(id string) {
func (a *Arena) RemovePlayer(p *models.Player) {
a.rwMutex.Lock()
defer a.rwMutex.Unlock()

delete(a.Players, id)
delete(a.Players, p.GetID())
}

// SpawnPlayer spawns the player with a position on the map
Expand All @@ -171,31 +172,34 @@ func (a *Arena) SpawnPlayer(id string, name string, country string) error {
func (a *Arena) generateCoordinate(objectRadius float64) models.Position {
maxWidth := a.Width - objectRadius
maxHeight := a.Height - objectRadius

dummy := models.Hole{
Position: models.Position{},
Radius: MinDistanceBetween,
}
for {
x := math.Floor(rand.Float64()*(maxWidth)) + objectRadius
y := math.Floor(rand.Float64()*(maxHeight)) + objectRadius
position := models.Position{X: x, Y: y}
if a.isPositionValid(position) {
return position
dummy.Position = models.Position{X: x, Y: y}
if a.isPositionValid(&dummy) {
return dummy.Position
}

// TODO: Add a timeout here; return error here
}
}

func (a *Arena) isPositionValid(position models.Position) bool {
func (a *Arena) isPositionValid(obj models.Object) bool {
for _, hole := range a.Holes {
if areCirclesColliding(hole.Position, hole.GravityRadius, position, MinDistanceBetween) {
if areCirclesColliding(hole, obj) {
return false
}
}
for _, junk := range a.Junk {
if areCirclesColliding(junk.Position, models.JunkRadius, position, MinDistanceBetween) {
if areCirclesColliding(junk, obj) {
return false
}
}
for _, player := range a.Players {
if areCirclesColliding(player.Position, models.PlayerRadius, position, MinDistanceBetween) {
if areCirclesColliding(player, obj) {
return false
}
}
Expand All @@ -205,8 +209,10 @@ func (a *Arena) isPositionValid(position models.Position) bool {

// detect collision between objects
// (x2-x1)^2 + (y1-y2)^2 <= (r1+r2)^2
func areCirclesColliding(p models.Position, r1 float64, q models.Position, r2 float64) bool {
return math.Pow(p.X-q.X, 2)+math.Pow(p.Y-q.Y, 2) <= math.Pow(r1+r2, 2)
func areCirclesColliding(obj models.Object, other models.Object) bool {
p := obj.GetPosition()
q := other.GetPosition()
return math.Pow(p.X-q.X, 2)+math.Pow(p.Y-q.Y, 2) <= math.Pow(obj.GetRadius()+other.GetRadius(), 2)
}

/*
Expand All @@ -221,13 +227,13 @@ func (a *Arena) playerCollisions() {
if player == playerHit || memo[player] == playerHit {
continue
}
if areCirclesColliding(player.Position, models.PlayerRadius, playerHit.Position, models.PlayerRadius) {
if areCirclesColliding(player, playerHit) {
memo[playerHit] = player
player.HitPlayer(playerHit)
}
}
for _, junk := range a.Junk {
if areCirclesColliding(player.Position, models.PlayerRadius, junk.Position, models.JunkRadius) {
if areCirclesColliding(player, junk) {
junk.HitBy(player)
}
}
Expand All @@ -240,34 +246,39 @@ func (a *Arena) holeCollisions() {
continue
}

gravityField := models.Hole{
Position: hole.GetPosition(),
Radius: hole.GetGravityRadius(),
}

for name, player := range a.Players {
if areCirclesColliding(player.Position, models.PlayerRadius, hole.Position, hole.Radius) {
if areCirclesColliding(player, hole) {
playerScored := player.LastPlayerHit
if playerScored != nil {
playerScored.AddPoints(models.PointsPerPlayer)
go database.UpdatePlayerScore(playerScored)
// go database.UpdatePlayerScore(playerScored)
}

deathMsg := models.Message{
Type: "death",
Data: name,
}
MessageChannel <- deathMsg
} else if areCirclesColliding(player.Position, models.PlayerRadius, hole.Position, hole.GravityRadius) {
} else if areCirclesColliding(player, gravityField) {
player.ApplyGravity(hole)
}
}

for i, junk := range a.Junk {
if areCirclesColliding(junk.Position, models.JunkRadius, hole.Position, hole.Radius) {
if areCirclesColliding(junk, hole) {
playerScored := junk.LastPlayerHit
if playerScored != nil {
playerScored.AddPoints(models.PointsPerJunk)
}

a.removeJunk(i)
a.addJunk()
} else if areCirclesColliding(junk.Position, models.JunkRadius, hole.Position, hole.GravityRadius) {
} else if areCirclesColliding(junk, gravityField) {
junk.ApplyGravity(hole)
}
}
Expand All @@ -282,7 +293,7 @@ func (a *Arena) junkCollisions() {
if junk == junkHit || memo[junkHit] == junk {
continue
}
if areCirclesColliding(junk.Position, models.JunkRadius, junkHit.Position, models.JunkRadius) {
if areCirclesColliding(junk, junkHit) {
memo[junkHit] = junk
junk.HitJunk(junkHit)
}
Expand Down
32 changes: 15 additions & 17 deletions server/arena/arena_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,13 @@ var (
quarterPosition = models.Position{X: testWidth / 4, Y: testHeight / 4}
)

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

func TestCreateArena(t *testing.T) {
Expand All @@ -52,9 +51,9 @@ func TestAddPlayer(t *testing.T) {
t.Run("TestAddPlayer", func(t *testing.T) {

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

if len(a.Players) != i+1 {
Expand Down Expand Up @@ -103,37 +102,36 @@ func TestPlayerToPlayerCollisions(t *testing.T) {
testPosition models.Position
expectedPosition models.Position
}{
{"colliding", quarterPosition, models.Position{X: 699.2725, Y: 599.2725}},
{"non-colliding", centerPosition, models.Position{X: 700.97, Y: 600.97}},
{"non-colliding", centerPosition, models.Position{X: 700, Y: 600}},
}

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

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

a.playerCollisions()
a.UpdatePositions()

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)
if a.Players[p.GetID()].Position != tc.expectedPosition {
t.Errorf("%s detection failed. Got player at %v. Expected player at %v", tc.otherPlayer, a.Players[p.GetID()].Position, tc.expectedPosition)
}
})
}
}

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

testCases := []struct {
description string
testPosition models.Position
expectedPlayer *models.Player
}{
{"non-colliding", centerPosition, nil},
{"colliding", quarterPosition, a.Players["test"]},
{"colliding", quarterPosition, p},
}
for i, tc := range testCases {
t.Run(tc.description, func(t *testing.T) {
Expand All @@ -142,7 +140,7 @@ func TestPlayerToJunkCollisions(t *testing.T) {

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)
t.Errorf("%s detection failed. Test Player at %v. Junk at %v. Junk Last Player Hit %v", tc.description, p.Position, a.Junk[i].Position, a.Junk[i].LastPlayerHit)
}
})
}
Expand Down
33 changes: 15 additions & 18 deletions server/database/database_test.go
Original file line number Diff line number Diff line change
@@ -1,35 +1,32 @@
package database

import (
"log"
"testing"

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

func TestConnectDB(t *testing.T) {
// Connect to DB
ConnectDB("../service-account.json")
// ConnectDB("../service-account.json")

if DBC == nil {
log.Fatal("DBClient not initialized correctly")
}
// if DBC == nil {
// log.Fatal("DBClient not initialized correctly")
// }
}

func TestUpdateFetchPlayerScore(t *testing.T) {
// Connect to DB
ConnectDB("../service-account.json")
// ConnectDB("../service-account.json")

// Create a Player
p := new(models.Player)
p.ID = "tester"
p.Name = "tester"
p.AddPoints(100)
UpdatePlayerScore(p)
// // Create a Player
// p := new(models.Player)
// p.ID = "tester"
// p.Name = "tester"
// p.AddPoints(100)
// UpdatePlayerScore(p)

returnScore := FetchPlayerScore(p)
// returnScore := FetchPlayerScore(p)

if returnScore.Name != p.Name || returnScore.Score != p.Points {
log.Fatal("DBC did not store or retreve score correctly")
}
// if returnScore.Name != p.Name || returnScore.Score != p.Points {
// log.Fatal("DBC did not store or retreve score correctly")
// }
}

0 comments on commit ade87ef

Please sign in to comment.