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) }