From ade87efdbded732cdd806e937c3cc0fc79fa54fd Mon Sep 17 00:00:00 2001 From: brian-nguyen Date: Sat, 10 Nov 2018 16:46:55 -0800 Subject: [PATCH] Add object interface 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 --- Makefile | 7 +- client/App.js | 6 +- client/components/GameObjects.js | 1 + server/arena/arena.go | 63 ++++++++++------- server/arena/arena_test.go | 32 ++++----- server/database/database_test.go | 33 ++++----- server/game/game.go | 44 +++++------- server/main.go | 9 ++- server/models/hole.go | 61 ++++++---------- server/models/junk.go | 78 ++++++++------------- server/models/models.go | 11 +-- server/models/player.go | 116 ++++++++++--------------------- server/models/player_test.go | 30 +++++--- 13 files changed, 214 insertions(+), 277 deletions(-) diff --git a/Makefile b/Makefile index 377d836..72d1f3d 100644 --- a/Makefile +++ b/Makefile @@ -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) \ No newline at end of file + (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 ./...) \ No newline at end of file diff --git a/client/App.js b/client/App.js index 7f59002..0dbaba3 100644 --- a/client/App.js +++ b/client/App.js @@ -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() { @@ -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); } } diff --git a/client/components/GameObjects.js b/client/components/GameObjects.js index 5487ff2..1f4f55b 100644 --- a/client/components/GameObjects.js +++ b/client/components/GameObjects.js @@ -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 diff --git a/server/arena/arena.go b/server/arena/arena.go index e08bb49..10aefaf 100644 --- a/server/arena/arena.go +++ b/server/arena/arena.go @@ -8,7 +8,6 @@ import ( "sync" "github.com/gorilla/websocket" - "github.com/ubclaunchpad/bumper/server/database" "github.com/ubclaunchpad/bumper/server/models" ) @@ -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 @@ -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 @@ -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 } } @@ -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) } /* @@ -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) } } @@ -240,12 +246,17 @@ 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{ @@ -253,13 +264,13 @@ func (a *Arena) holeCollisions() { 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) @@ -267,7 +278,7 @@ func (a *Arena) holeCollisions() { a.removeJunk(i) a.addJunk() - } else if areCirclesColliding(junk.Position, models.JunkRadius, hole.Position, hole.GravityRadius) { + } else if areCirclesColliding(junk, gravityField) { junk.ApplyGravity(hole) } } @@ -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) } diff --git a/server/arena/arena_test.go b/server/arena/arena_test.go index a8bdf93..1e64784 100644 --- a/server/arena/arena_test.go +++ b/server/arena/arena_test.go @@ -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) { @@ -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 { @@ -103,29 +102,28 @@ 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 @@ -133,7 +131,7 @@ func TestPlayerToJunkCollisions(t *testing.T) { 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) { @@ -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) } }) } diff --git a/server/database/database_test.go b/server/database/database_test.go index bbc4200..170d3b4 100644 --- a/server/database/database_test.go +++ b/server/database/database_test.go @@ -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") + // } } diff --git a/server/game/game.go b/server/game/game.go index a0f63b3..523aabc 100644 --- a/server/game/game.go +++ b/server/game/game.go @@ -51,25 +51,23 @@ func (g *Game) ServeHTTP(w http.ResponseWriter, r *http.Request) { } defer ws.Close() - var initialMsg models.Message - id := models.GenUniqueID() - - err = g.Arena.AddPlayer(id, ws) + player, err := g.Arena.AddPlayer(ws) if err != nil { log.Printf("Error adding player:\n%v", err) - } else { - initialMsg = models.Message{ - Type: "connect", - Data: id, - } - arena.MessageChannel <- initialMsg + return + } + + arena.MessageChannel <- models.Message{ + Type: "connect", + Data: player.GetID(), } + for { var msg models.Message err := ws.ReadJSON(&msg) if err != nil { log.Printf("%v\n", err) - g.Arena.RemovePlayer(id) + g.Arena.RemovePlayer(player) break } switch msg.Type { @@ -80,19 +78,19 @@ func (g *Game) ServeHTTP(w http.ResponseWriter, r *http.Request) { log.Printf("%v\n", err) continue } - err := g.Arena.SpawnPlayer(id, spawn.Name, spawn.Country) + err := g.Arena.SpawnPlayer(player.GetID(), spawn.Name, spawn.Country) if err != nil { log.Printf("Error spawning player:\n%v", err) continue } case "reconnect": - err = g.Arena.AddPlayer(id, ws) + player, err = g.Arena.AddPlayer(ws) if err != nil { log.Printf("Error adding player:\n%v", err) } else { connectMsg := models.Message{ Type: "connect", - Data: id, + Data: player.GetID(), } arena.MessageChannel <- connectMsg } @@ -104,15 +102,11 @@ func (g *Game) ServeHTTP(w http.ResponseWriter, r *http.Request) { continue } - p := g.Arena.GetPlayer(id) - if p != nil { - if kh.IsPressed { - p.KeyDownHandler(kh.Key) - } else { - p.KeyUpHandler(kh.Key) - } + if kh.IsPressed { + player.KeyDownHandler(kh.Key) + } else { + player.KeyUpHandler(kh.Key) } - default: log.Println("Unknown message type received") } @@ -143,7 +137,7 @@ func (g *Game) tick() { if err != nil { log.Printf("error: %v", err) p.Close() - g.Arena.RemovePlayer(p.ID) + g.Arena.RemovePlayer(p) } } } @@ -171,7 +165,7 @@ func (g *Game) messageEmitter() { if err != nil { log.Printf("error: %v", err) p.Close() - g.Arena.RemovePlayer(id) + g.Arena.RemovePlayer(p) } case "death": @@ -187,7 +181,7 @@ func (g *Game) messageEmitter() { log.Printf("error: %v", err) p.Close() } - g.Arena.RemovePlayer(id) + g.Arena.RemovePlayer(p) default: log.Println("Unknown message type to emit") diff --git a/server/main.go b/server/main.go index 7e84cf0..e0b8a52 100644 --- a/server/main.go +++ b/server/main.go @@ -9,7 +9,6 @@ import ( "time" "github.com/ubclaunchpad/bumper/server/arena" - "github.com/ubclaunchpad/bumper/server/database" "github.com/ubclaunchpad/bumper/server/game" "github.com/ubclaunchpad/bumper/server/models" ) @@ -33,10 +32,10 @@ func main() { arena.MessageChannel = make(chan models.Message) game := game.CreateGame() - database.ConnectDB("service-account.json") - if database.DBC == nil { - log.Println("DBClient not initialized correctly") - } + // database.ConnectDB("service-account.json") + // if database.DBC == nil { + // log.Println("DBClient not initialized correctly") + // } http.HandleFunc("/start", getLobby) http.Handle("/connect", game) diff --git a/server/models/hole.go b/server/models/hole.go index 815dfd9..2c6fa2b 100644 --- a/server/models/hole.go +++ b/server/models/hole.go @@ -3,7 +3,6 @@ package models import ( "math" "math/rand" - "sync" ) // Hole related constants @@ -25,7 +24,6 @@ type Hole struct { IsAlive bool `json:"isAlive"` Life float64 `json:"-"` StartingLife float64 `json:"-"` - rwMutex sync.RWMutex } // CreateHole initializes and returns an instance of a Hole @@ -39,74 +37,61 @@ func CreateHole(position Position) *Hole { Life: life, IsAlive: false, StartingLife: life, - rwMutex: sync.RWMutex{}, } return &h } -// Getters -func (h *Hole) getPosition() Position { - h.rwMutex.RLock() - defer h.rwMutex.RUnlock() - - return h.Position +// GetID returns the hole's ID +func (h Hole) GetID() string { + return "" } -func (h *Hole) getLife() float64 { - h.rwMutex.RLock() - defer h.rwMutex.RUnlock() +// GetColor returns this hole's color +func (h Hole) GetColor() string { + return "" +} - return h.Life +// GetPosition returns this hole's position +func (h Hole) GetPosition() Position { + return h.Position } -func (h *Hole) getRadius() float64 { - h.rwMutex.RLock() - defer h.rwMutex.RUnlock() +// GetVelocity returns this hole's velocity +func (h Hole) GetVelocity() Velocity { + return Velocity{} +} +// GetRadius returns this hole's radius +func (h Hole) GetRadius() float64 { return h.Radius } -func (h *Hole) getGravityRadius() float64 { - h.rwMutex.RLock() - defer h.rwMutex.RUnlock() +func (h *Hole) getLife() float64 { + return h.Life +} +// GetGravityRadius returns this hole's gravitational radius +func (h *Hole) GetGravityRadius() float64 { return h.GravityRadius } func (h *Hole) getStartingLife() float64 { - h.rwMutex.RLock() - defer h.rwMutex.RUnlock() - return h.StartingLife } -// Setters - func (h *Hole) setIsAlive(isAlive bool) { - h.rwMutex.Lock() - defer h.rwMutex.Unlock() - h.IsAlive = isAlive } func (h *Hole) setLife(life float64) { - h.rwMutex.Lock() - defer h.rwMutex.Unlock() - h.Life = life } func (h *Hole) setRadius(radius float64) { - h.rwMutex.Lock() - defer h.rwMutex.Unlock() - h.Radius = radius } func (h *Hole) setGravityRadius(gravityRadius float64) { - h.rwMutex.Lock() - defer h.rwMutex.Unlock() - h.GravityRadius = gravityRadius } @@ -119,9 +104,9 @@ func (h *Hole) Update() { if hLife < h.getStartingLife()-HoleInfancy { h.setIsAlive(true) } - if hRadius := h.getRadius(); hRadius < MaxHoleRadius*1.2 { + if hRadius := h.GetRadius(); hRadius < MaxHoleRadius*1.2 { h.setRadius(hRadius + 0.02) - h.setGravityRadius(h.getGravityRadius() + 0.03) + h.setGravityRadius(h.GetGravityRadius() + 0.03) } } diff --git a/server/models/junk.go b/server/models/junk.go index 52b2cbb..afcb4b5 100644 --- a/server/models/junk.go +++ b/server/models/junk.go @@ -2,7 +2,6 @@ package models import ( "math" - "sync" ) // Junk related constants @@ -24,7 +23,6 @@ type Junk struct { LastPlayerHit *Player `json:"-"` Debounce int `json:"-"` jDebounce int - rwMutex sync.RWMutex } // CreateJunk initializes and returns an instance of a Junk @@ -35,88 +33,70 @@ func CreateJunk(position Position) *Junk { Color: "white", Debounce: 0, jDebounce: 0, - rwMutex: sync.RWMutex{}, } } -// Getters +// GetID returns the ID of this junk +func (j Junk) GetID() string { + return "" +} -func (j *Junk) getPosition() Position { - j.rwMutex.RLock() - defer j.rwMutex.RUnlock() +// GetColor returns the color of this junk +func (j Junk) GetColor() string { + return j.Color +} +// GetPosition returns the position of this jun +func (j Junk) GetPosition() Position { return j.Position } -func (j *Junk) getVelocity() Velocity { - j.rwMutex.RLock() - defer j.rwMutex.RUnlock() - +// GetVelocity returns the velocity of this junk +func (j Junk) GetVelocity() Velocity { return j.Velocity } -func (j *Junk) getDebounce() int { - j.rwMutex.RLock() - defer j.rwMutex.RUnlock() +// GetRadius returns the radius of this junk +func (j Junk) GetRadius() float64 { + return JunkRadius +} +func (j *Junk) getDebounce() int { return j.Debounce } func (j *Junk) getJDebounce() int { - j.rwMutex.RLock() - defer j.rwMutex.RUnlock() - return j.jDebounce } -// Setters - func (j *Junk) setPosition(pos Position) { - j.rwMutex.Lock() - defer j.rwMutex.Unlock() - j.Position = pos } func (j *Junk) setVelocity(v Velocity) { - j.rwMutex.Lock() - defer j.rwMutex.Unlock() - j.Velocity = v } func (j *Junk) setDebounce(debounce int) { - j.rwMutex.Lock() - defer j.rwMutex.Unlock() - j.Debounce = debounce } func (j *Junk) setJDebounce(jDebounce int) { - j.rwMutex.Lock() - defer j.rwMutex.Unlock() - j.jDebounce = jDebounce } func (j *Junk) setColor(color string) { - j.rwMutex.Lock() - defer j.rwMutex.Unlock() - j.Color = color } func (j *Junk) setLastPlayerHit(player *Player) { - j.rwMutex.Lock() - defer j.rwMutex.Unlock() - j.LastPlayerHit = player } // UpdatePosition Update Junk's position based on calculations of position/velocity func (j *Junk) UpdatePosition(height float64, width float64) { - positionVector := j.getPosition() - velocityVector := j.getVelocity() + positionVector := j.GetPosition() + velocityVector := j.GetVelocity() if positionVector.X+velocityVector.Dx > width-JunkRadius || positionVector.X+velocityVector.Dx < JunkRadius { velocityVector.Dx = -velocityVector.Dx } @@ -148,14 +128,14 @@ func (j *Junk) UpdatePosition(height float64, width float64) { // HitBy Update Junks's velocity based on calculations of being hit by a player func (j *Junk) HitBy(p *Player) { - pVelocity := p.getVelocity() - jVelocity := j.getVelocity() + pVelocity := p.GetVelocity() + jVelocity := j.GetVelocity() // We don't want this collision till the debounce is down. if j.getDebounce() != 0 { return } - j.setColor(p.getColor()) //Assign junk to last recently hit player color + j.setColor(p.GetColor()) //Assign junk to last recently hit player color j.setLastPlayerHit(p) if pVelocity.Dx < 0 { @@ -182,9 +162,9 @@ func (j *Junk) HitJunk(jh *Junk) { return } - jInitialVelocity := j.getVelocity() + jInitialVelocity := j.GetVelocity() jVelocity := jInitialVelocity - jhVelocity := jh.getVelocity() + jhVelocity := jh.GetVelocity() //Calculate this junks's new velocity jVelocity.Dx = (jVelocity.Dx * -JunkVTransferFactor) + (jhVelocity.Dx * JunkVTransferFactor) jVelocity.Dy = (jVelocity.Dy * -JunkVTransferFactor) + (jhVelocity.Dy * JunkVTransferFactor) @@ -201,9 +181,9 @@ func (j *Junk) HitJunk(jh *Junk) { // ApplyGravity applys a vector towards given position func (j *Junk) ApplyGravity(h *Hole) { - jVelocity := j.getVelocity() - jPosition := j.getPosition() - hPosition := h.getPosition() + jVelocity := j.GetVelocity() + jPosition := j.GetPosition() + hPosition := h.GetPosition() gravityVector := Velocity{0, 0} gravityVector.Dx = hPosition.X - jPosition.X @@ -212,7 +192,7 @@ func (j *Junk) ApplyGravity(h *Hole) { gravityVector.normalize() //Velocity is affected by how close you are, the size of the hole, and a damping factor. - jVelocity.Dx += gravityVector.Dx * inverseMagnitude * h.getRadius() * JunkGravityDamping - jVelocity.Dy += gravityVector.Dy * inverseMagnitude * h.getRadius() * JunkGravityDamping + jVelocity.Dx += gravityVector.Dx * inverseMagnitude * h.GetRadius() * JunkGravityDamping + jVelocity.Dy += gravityVector.Dy * inverseMagnitude * h.GetRadius() * JunkGravityDamping j.setVelocity(jVelocity) } diff --git a/server/models/models.go b/server/models/models.go index 5e01b66..1b542eb 100644 --- a/server/models/models.go +++ b/server/models/models.go @@ -4,10 +4,13 @@ import ( "math" ) -// Vector represents a point in 2D space -type Vector struct { - X float64 `json:"x"` - Y float64 `json:"y"` +// Object represents an interactable object on the Arena +type Object interface { + GetID() string + GetColor() string + GetPosition() Position + GetVelocity() Velocity + GetRadius() float64 } // Position x y position diff --git a/server/models/player.go b/server/models/player.go index 9de6d0d..ae14412 100644 --- a/server/models/player.go +++ b/server/models/player.go @@ -57,11 +57,11 @@ type Player struct { // CreatePlayer constructs an instance of player with // given position, color, and WebSocket connection -func CreatePlayer(id string, name string, pos Position, color string, ws *websocket.Conn) *Player { +func CreatePlayer(name string, color string, ws *websocket.Conn) *Player { return &Player{ Name: name, - ID: id, - Position: pos, + ID: xid.New().String(), + Position: Position{}, Velocity: Velocity{}, Color: color, Angle: math.Pi, @@ -73,132 +73,88 @@ func CreatePlayer(id string, name string, pos Position, color string, ws *websoc } } -// GetName returns name of player -func (p *Player) GetName() string { - p.rwMutex.RLock() - defer p.rwMutex.RUnlock() - - return p.Name +// GetID returns the ID of the player +func (p Player) GetID() string { + return p.ID } -func (p *Player) getColor() string { - p.rwMutex.RLock() - defer p.rwMutex.RUnlock() - +// GetColor returns the color of the player +func (p Player) GetColor() string { return p.Color } -func (p *Player) getVelocity() Velocity { - p.rwMutex.RLock() - defer p.rwMutex.RUnlock() +// GetPosition returns the position of the player +func (p Player) GetPosition() Position { + return p.Position +} +// GetVelocity returns the velocity of the player +func (p Player) GetVelocity() Velocity { return p.Velocity } -func (p *Player) getControls() KeysPressed { - p.rwMutex.RLock() - defer p.rwMutex.RUnlock() +// GetRadius returns the radius of the player +func (p Player) GetRadius() float64 { + return PlayerRadius +} + +// GetName returns name of player +func (p Player) GetName() string { + return p.Name +} +func (p *Player) getControls() KeysPressed { return p.Controls } func (p *Player) getAngle() float64 { - p.rwMutex.RLock() - defer p.rwMutex.RUnlock() - return p.Angle } -func (p *Player) getPosition() Position { - p.rwMutex.RLock() - defer p.rwMutex.RUnlock() - - return p.Position -} - func (p *Player) getPDebounce() int { - p.rwMutex.RLock() - defer p.rwMutex.RUnlock() - return p.pDebounce } func (p *Player) getPointsDebounce() int { - p.rwMutex.RLock() - defer p.rwMutex.RUnlock() - return p.pointsDebounce } func (p *Player) getLastPlayerHit() *Player { - p.rwMutex.RLock() - defer p.rwMutex.RUnlock() - return p.LastPlayerHit } func (p *Player) setVelocity(v Velocity) { - p.rwMutex.Lock() - defer p.rwMutex.Unlock() - p.Velocity = v } func (p *Player) setControls(k KeysPressed) { - p.rwMutex.Lock() - defer p.rwMutex.Unlock() - p.Controls = k } func (p *Player) setAngle(a float64) { - p.rwMutex.Lock() - defer p.rwMutex.Unlock() - p.Angle = a } func (p *Player) setPosition(pos Position) { - p.rwMutex.Lock() - defer p.rwMutex.Unlock() - p.Position = pos } func (p *Player) setPDebounce(pDebounce int) { - p.rwMutex.Lock() - defer p.rwMutex.Unlock() - p.pDebounce = pDebounce } func (p *Player) setPointsDebounce(pointsDebounce int) { - p.rwMutex.Lock() - defer p.rwMutex.Unlock() - p.pointsDebounce = pointsDebounce } func (p *Player) setPoints(points int) { - p.rwMutex.Lock() - defer p.rwMutex.Unlock() - p.Points = points } func (p *Player) setLastPlayerHit(playerHit *Player) { - p.rwMutex.Lock() - defer p.rwMutex.Unlock() - p.LastPlayerHit = playerHit } -// GenUniqueID generates unique id... -func GenUniqueID() string { - id := xid.New() - return id.String() -} - // AddPoints adds numPoints to player p func (p *Player) AddPoints(numPoints int) { p.setPoints(p.Points + numPoints) @@ -242,8 +198,8 @@ func (p *Player) UpdatePosition(height float64, width float64) { controlsVector.Dx *= PlayerAcceleration controlsVector.Dy *= PlayerAcceleration - positionVector := p.getPosition() - velocityVector := p.getVelocity() + positionVector := p.GetPosition() + velocityVector := p.GetVelocity() velocityVector.Dx = (velocityVector.Dx * PlayerFriction) + controlsVector.Dx velocityVector.Dy = (velocityVector.Dy * PlayerFriction) + controlsVector.Dy @@ -281,7 +237,7 @@ func (p *Player) UpdatePosition(height float64, width float64) { } func (p *Player) hitJunk() { - velocityVector := p.getVelocity() + velocityVector := p.GetVelocity() velocityVector.Dx *= JunkBounceFactor velocityVector.Dy *= JunkBounceFactor p.setVelocity(velocityVector) @@ -293,9 +249,9 @@ func (p *Player) HitPlayer(ph *Player) { return } - pInitialVelocity := p.getVelocity() + pInitialVelocity := p.GetVelocity() pVelocity := pInitialVelocity - phVelocity := ph.getVelocity() + phVelocity := ph.GetVelocity() //Calculate player's new velocity pVelocity.Dx = (pVelocity.Dx * -VelocityTransferFactor) + (phVelocity.Dx * VelocityTransferFactor) @@ -317,9 +273,9 @@ func (p *Player) HitPlayer(ph *Player) { // ApplyGravity applys a vector towards given position func (p *Player) ApplyGravity(h *Hole) { gravityVector := Velocity{0, 0} - pVelocity := p.getVelocity() - pPosition := p.getPosition() - hPosition := h.getPosition() + pVelocity := p.GetVelocity() + pPosition := p.GetPosition() + hPosition := h.GetPosition() gravityVector.Dx = hPosition.X - pPosition.X gravityVector.Dy = hPosition.Y - pPosition.Y @@ -328,16 +284,16 @@ func (p *Player) ApplyGravity(h *Hole) { gravityVector.normalize() //Velocity is affected by how close you are, the size of the hole, and a damping factor. - pVelocity.Dx += gravityVector.Dx * inverseMagnitude * h.getRadius() * gravityDamping - pVelocity.Dy += gravityVector.Dy * inverseMagnitude * h.getRadius() * gravityDamping + pVelocity.Dx += gravityVector.Dx * inverseMagnitude * h.GetRadius() * gravityDamping + pVelocity.Dy += gravityVector.Dy * inverseMagnitude * h.GetRadius() * gravityDamping p.setVelocity(pVelocity) } // checkWalls if the player is attempting to exit the walls, reverse their direction func (p *Player) checkWalls(height float64, width float64) { - positionVector := p.getPosition() - velocityVector := p.getVelocity() + positionVector := p.GetPosition() + velocityVector := p.GetVelocity() if positionVector.X+PlayerRadius > width { positionVector.X = width - PlayerRadius - 1 velocityVector.Dx *= WallBounceFactor diff --git a/server/models/player_test.go b/server/models/player_test.go index 885be15..0f272af 100644 --- a/server/models/player_test.go +++ b/server/models/player_test.go @@ -38,12 +38,13 @@ func TestAddPoints(t *testing.T) { } func TestCreatePlayer(t *testing.T) { - ws := new(*websocket.Conn) + ws := new(websocket.Conn) //Test initialization of player - p := CreatePlayer(testID, testNamePlayerTest, centerPosPlayerTest, testColorPlayerTest, *ws) + p := CreatePlayer(testNamePlayerTest, testColorPlayerTest, ws) + p.Position = centerPosPlayerTest - //Test name assignment of player + // Test name assignment of player if p.Name != testNamePlayerTest { t.Error("Error assigning name") } @@ -218,8 +219,8 @@ func TestPlayerBumpPlayer(t *testing.T) { } for _, tc := range testCases { t.Run(tc.description, func(t *testing.T) { - p1 := CreatePlayer("id1", "player1", Position{}, testColorPlayerTest, nil) - p2 := CreatePlayer("id2", "player2", Position{}, testColorPlayerTest, nil) + p1 := CreatePlayer("player1", testColorPlayerTest, nil) + p2 := CreatePlayer("player2", testColorPlayerTest, nil) p1.Velocity = tc.playerVelocity @@ -246,8 +247,12 @@ func TestPlayerKillPlayer(t *testing.T) { } for _, tc := range testCases { t.Run(tc.description, func(t *testing.T) { - p1 := CreatePlayer("id1", "player1", tc.bumperPosition, testColorPlayerTest, nil) - p2 := CreatePlayer("id2", "player2", tc.bumpeePosition, testColorPlayerTest, nil) + p1 := CreatePlayer("player1", testColorPlayerTest, nil) + p1.Position = tc.bumperPosition + + p2 := CreatePlayer("player2", testColorPlayerTest, nil) + p2.Position = tc.bumpeePosition + p1.Velocity = tc.bumperVelocity h := CreateHole(tc.holePosition) @@ -258,7 +263,7 @@ func TestPlayerKillPlayer(t *testing.T) { p1.UpdatePosition(testHeightPlayerTest, testWidthPlayerTest) p2.UpdatePosition(testHeightPlayerTest, testWidthPlayerTest) - if areCirclesColliding(p2.Position, PlayerRadius, h.Position, h.Radius) { + if areCirclesColliding(p2, h) { playerScored := p2.LastPlayerHit if playerScored != nil { playerScored.AddPoints(PointsPerPlayer) @@ -281,7 +286,10 @@ func TestPlayerKillPlayer(t *testing.T) { } } -// Helper function, detect collision between objects -func areCirclesColliding(p Position, r1 float64, q Position, r2 float64) bool { - return math.Pow(p.X-q.X, 2)+math.Pow(p.Y-q.Y, 2) <= math.Pow(r1+r2, 2) +// detect collision between objects +// (x2-x1)^2 + (y1-y2)^2 <= (r1+r2)^2 +func areCirclesColliding(obj Object, other Object) bool { + objPosition := obj.GetPosition() + otherPosition := other.GetPosition() + return math.Pow(objPosition.X-otherPosition.X, 2)+math.Pow(objPosition.Y-otherPosition.Y, 2) <= math.Pow(obj.GetRadius()+other.GetRadius(), 2) }