Skip to content

Commit

Permalink
Merge 6dfa527 into 20b01a6
Browse files Browse the repository at this point in the history
  • Loading branch information
Grafne23 committed Jul 27, 2018
2 parents 20b01a6 + 6dfa527 commit 36c66c8
Show file tree
Hide file tree
Showing 5 changed files with 181 additions and 65 deletions.
12 changes: 8 additions & 4 deletions server/arena/arena.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,12 +168,12 @@ func (a *Arena) playerCollisions() {
memo := make(map[*models.Player]*models.Player)
for _, player := range a.Players {
for _, playerHit := range a.Players {
if player == playerHit || memo[playerHit] == player {
if player == playerHit || memo[player] == playerHit {
continue
}
if areCirclesColliding(player.Position, models.PlayerRadius, playerHit.Position, models.PlayerRadius) {
memo[playerHit] = player
player.HitPlayer(playerHit, a.Height, a.Width)
player.HitPlayer(playerHit)
}
}
for _, junk := range a.Junk {
Expand All @@ -192,8 +192,12 @@ func (a *Arena) holeCollisions() {

for name, player := range a.Players {
if areCirclesColliding(player.Position, models.PlayerRadius, hole.Position, hole.Radius) {
// TODO: Should award some points to the bumper... Not as straight forward as the junk
go database.UpdatePlayerScore(player)
playerScored := player.LastPlayerHit
if playerScored != nil {
playerScored.AddPoints(models.PointsPerPlayer)
go database.UpdatePlayerScore(playerScored)
}

deathMsg := models.Message{
Type: "death",
Data: name,
Expand Down
7 changes: 5 additions & 2 deletions server/arena/arena_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ func CreateArenaWithPlayer(p models.Position) *Arena {
a := CreateArena(testHeight, testWidth, 0, 0)
a.AddPlayer("test", nil)
testPlayer := a.Players["test"]
testPlayer.Name = "testName"
testPlayer.Position = p
testPlayer.Velocity = testVelocity
return a
Expand Down Expand Up @@ -102,8 +103,8 @@ func TestPlayerToPlayerCollisions(t *testing.T) {
testPosition models.Position
expectedPosition models.Position
}{
{"colliding", quarterPosition, models.Position{X: 700.375, Y: 600.375}},
{"non-colliding", centerPosition, quarterPosition},
{"colliding", quarterPosition, models.Position{X: 699.2725, Y: 599.2725}},
{"non-colliding", centerPosition, models.Position{X: 700.97, Y: 600.97}},
}

for _, tc := range testCases {
Expand All @@ -114,6 +115,8 @@ func TestPlayerToPlayerCollisions(t *testing.T) {
a.Players[tc.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)
}
Expand Down
62 changes: 33 additions & 29 deletions server/models/junk.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,44 +67,48 @@ 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) {
// We don't want this collision till the debounce is down.
if j.Debounce == 0 {
j.Color = p.Color //Assign junk to last recently hit player color
j.LastPlayerHit = p

if p.Velocity.Dx < 0 {
j.Velocity.Dx = math.Min(p.Velocity.Dx*BumpFactor, -MinimumBump)
} else {
j.Velocity.Dx = math.Max(p.Velocity.Dx*BumpFactor, MinimumBump)
}

if p.Velocity.Dy < 0 {
j.Velocity.Dy = math.Min(p.Velocity.Dy*BumpFactor, -MinimumBump)
} else {
j.Velocity.Dy = math.Max(p.Velocity.Dy*BumpFactor, MinimumBump)
}

p.hitJunk()
j.Debounce = JunkDebounceTicks
if j.Debounce != 0 {
return
}

j.Color = p.Color //Assign junk to last recently hit player color
j.LastPlayerHit = p

if p.Velocity.Dx < 0 {
j.Velocity.Dx = math.Min(p.Velocity.Dx*BumpFactor, -MinimumBump)
} else {
j.Velocity.Dx = math.Max(p.Velocity.Dx*BumpFactor, MinimumBump)
}

if p.Velocity.Dy < 0 {
j.Velocity.Dy = math.Min(p.Velocity.Dy*BumpFactor, -MinimumBump)
} else {
j.Velocity.Dy = math.Max(p.Velocity.Dy*BumpFactor, MinimumBump)
}

p.hitJunk()
j.Debounce = JunkDebounceTicks
}

// HitJunk Update Junks's velocity based on calculations of being hit by another Junk
func (j *Junk) HitJunk(jh *Junk) {
// We don't want this collision till the debounce is down.
if j.jDebounce == 0 {
initalVelocity := j.Velocity
if j.jDebounce != 0 {
return
}

//Calculate this junks's new velocity
j.Velocity.Dx = (j.Velocity.Dx * -JunkVTransferFactor) + (jh.Velocity.Dx * JunkVTransferFactor)
j.Velocity.Dy = (j.Velocity.Dy * -JunkVTransferFactor) + (jh.Velocity.Dy * JunkVTransferFactor)
initalVelocity := j.Velocity

//Calculate other junk's new velocity
jh.Velocity.Dx = (jh.Velocity.Dx * -JunkVTransferFactor) + (initalVelocity.Dx * JunkVTransferFactor)
jh.Velocity.Dy = (jh.Velocity.Dy * -JunkVTransferFactor) + (initalVelocity.Dy * JunkVTransferFactor)
//Calculate this junks's new velocity
j.Velocity.Dx = (j.Velocity.Dx * -JunkVTransferFactor) + (jh.Velocity.Dx * JunkVTransferFactor)
j.Velocity.Dy = (j.Velocity.Dy * -JunkVTransferFactor) + (jh.Velocity.Dy * JunkVTransferFactor)

j.jDebounce = JunkDebounceTicks
jh.jDebounce = JunkDebounceTicks
}
//Calculate other junk's new velocity
jh.Velocity.Dx = (jh.Velocity.Dx * -JunkVTransferFactor) + (initalVelocity.Dx * JunkVTransferFactor)
jh.Velocity.Dy = (jh.Velocity.Dy * -JunkVTransferFactor) + (initalVelocity.Dy * JunkVTransferFactor)

j.jDebounce = JunkDebounceTicks
jh.jDebounce = JunkDebounceTicks
}

// ApplyGravity applys a vector towards given position
Expand Down
85 changes: 55 additions & 30 deletions server/models/player.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ const (
PlayerFriction = 0.97
MaxVelocity = 15
PointsPerJunk = 100
PointsPerPlayer = 500
gravityDamping = 0.075
PlayerDebounceTicks = 15
PointsDebounceTicks = 100
)

// KeysPressed contains a boolean about each key, true if it's down
Expand All @@ -36,35 +39,41 @@ type KeysPressed struct {

// Player contains data and state about a player's object
type Player struct {
Name string `json:"name"`
ID string `json:"id"`
Country string `json:"country"`
Position Position `json:"position"`
Velocity Velocity `json:"-"`
Color string `json:"color"`
Angle float64 `json:"angle"`
Controls KeysPressed `json:"-"`
Points int `json:"points"`
mutex sync.Mutex
ws *websocket.Conn
Name string `json:"name"`
ID string `json:"id"`
Country string `json:"country"`
Position Position `json:"position"`
Velocity Velocity `json:"-"`
Color string `json:"color"`
Angle float64 `json:"angle"`
Controls KeysPressed `json:"-"`
Points int `json:"points"`
LastPlayerHit *Player `json:"-"`
pointsDebounce int
pDebounce int
mutex sync.Mutex
ws *websocket.Conn
}

// CreatePlayer constructs an instance of player with
// given position, color, and WebSocket connection
func CreatePlayer(id string, n string, p Position, c string, ws *websocket.Conn) *Player {
func CreatePlayer(id string, name string, pos Position, color string, ws *websocket.Conn) *Player {
return &Player{
Name: n,
ID: id,
Position: p,
Velocity: Velocity{},
Color: c,
Angle: math.Pi,
Controls: KeysPressed{},
mutex: sync.Mutex{},
ws: ws,
Name: name,
ID: id,
Position: pos,
Velocity: Velocity{},
Color: color,
Angle: math.Pi,
Controls: KeysPressed{},
pDebounce: 0,
pointsDebounce: 0,
mutex: sync.Mutex{},
ws: ws,
}
}

// GenUniqueID generates unique id...
func GenUniqueID() string {
id := xid.New()
return id.String()
Expand Down Expand Up @@ -131,31 +140,47 @@ func (p *Player) UpdatePosition(height float64, width float64) {
p.Position.Y += p.Velocity.Dy

p.checkWalls(height, width)

if p.pDebounce > 0 {
p.pDebounce--
} else {
p.pDebounce = 0
}

if p.pointsDebounce > 0 {
p.pointsDebounce--
} else {
p.LastPlayerHit = nil
p.pointsDebounce = 0
}
}

func (p *Player) hitJunk() {
p.Velocity.Dx *= JunkBounceFactor
p.Velocity.Dy *= JunkBounceFactor
}

// HitPlayer calculates collision, update Player's position based on calculation of hitting another player
func (p *Player) HitPlayer(ph *Player, height float64, width float64) {
// HitPlayer calculates collision, update Player's velocity based on calculation of hitting another player
func (p *Player) HitPlayer(ph *Player) {
if p.pDebounce != 0 {
return
}

initalVelocity := p.Velocity

//Calculate player's new velocity
p.Velocity.Dx = (p.Velocity.Dx * -VelocityTransferFactor) + (ph.Velocity.Dx * VelocityTransferFactor)
p.Velocity.Dy = (p.Velocity.Dy * -VelocityTransferFactor) + (ph.Velocity.Dy * VelocityTransferFactor)
//We add one position update so that multiple collision events don't occur for a single bump
p.Position.X += p.Velocity.Dx
p.Position.Y += p.Velocity.Dy

//Calculate the player you hits new velocity (and again one position update)
//Calculate the player you hits new velocity
ph.Velocity.Dx = (ph.Velocity.Dx * -VelocityTransferFactor) + (initalVelocity.Dx * VelocityTransferFactor)
ph.Velocity.Dy = (ph.Velocity.Dy * -VelocityTransferFactor) + (initalVelocity.Dy * VelocityTransferFactor)
ph.Position.X += ph.Velocity.Dx
ph.Position.Y += ph.Velocity.Dy

p.checkWalls(height, width)
ph.LastPlayerHit = p
p.LastPlayerHit = ph
p.pointsDebounce = PointsDebounceTicks
ph.pointsDebounce = PointsDebounceTicks
p.pDebounce = PlayerDebounceTicks
}

// ApplyGravity applys a vector towards given position
Expand Down
80 changes: 80 additions & 0 deletions server/models/player_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,3 +205,83 @@ func TestKeyHandler(t *testing.T) {
})
}
}

func TestPlayerBumpPlayer(t *testing.T) {
testCases := []struct {
description string
playerVelocity Velocity
}{
{"Moving NW", Velocity{5, -5}},
{"Moving NE", Velocity{5, 5}},
{"Moving SW", Velocity{-5, -5}},
{"Moving SE", Velocity{-5, 5}},
}
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.Velocity = tc.playerVelocity

p1.HitPlayer(p2)

if !checkDirection(p2.Velocity, tc.playerVelocity) {
t.Error("Player wasn't bumped in the correct direction")
}
})
}
}

func TestPlayerKillPlayer(t *testing.T) {
testCases := []struct {
description string
bumperPosition Position
bumpeePosition Position
bumperVelocity Velocity
holePosition Position
shouldAwardPoints bool
}{
{"Award Points", Position{40, 50}, Position{45, 50}, Velocity{5, 0}, Position{50, 50}, true},
{"No Points", Position{5, 50}, Position{10, 50}, Velocity{5, 0}, Position{350, 50}, false},
}
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.Velocity = tc.bumperVelocity
h := CreateHole(tc.holePosition)

p1.HitPlayer(p2)

// Advance the game 200 time ticks
for i := 0; i < 200; i++ {
p1.UpdatePosition(testHeightPlayerTest, testWidthPlayerTest)
p2.UpdatePosition(testHeightPlayerTest, testWidthPlayerTest)

if areCirclesColliding(p2.Position, PlayerRadius, h.Position, h.Radius) {
playerScored := p2.LastPlayerHit
if playerScored != nil {
playerScored.AddPoints(PointsPerPlayer)
}
}
}

// If this flag is set the hole was close enough to eat the bumpee within the debounce time
// if not set the hole was too far away
if tc.shouldAwardPoints {
if p1.Points == 0 {
t.Error("Bumpee was not killed correctly")
}
} else {
if p1.Points != 0 {
t.Error("Bumpee was killed incorrectly")
}
}
})
}
}

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

0 comments on commit 36c66c8

Please sign in to comment.