go.charczuk.com@v0.0.0-20240327042549-bc490516bd1a/experiments/matchmaker/pkg/sim/simulation_test.go (about) 1 /* 2 3 Copyright (c) 2024 - Present. Will Charczuk. All rights reserved. 4 Use of this source code is governed by a MIT license that can be found in the LICENSE file at the root of the repository. 5 6 */ 7 8 package sim 9 10 import ( 11 "math/rand" 12 "testing" 13 "time" 14 15 "go.charczuk.com/sdk/assert" 16 "go.charczuk.com/sdk/uuid" 17 ) 18 19 func Test_Simulation_tickPlayerArrivals(t *testing.T) { 20 s := new(Simulation) 21 s.Config = SimulationConfig{ 22 PlayerActiveDuration: time.Hour, 23 } 24 s.RandSource = rand.NewSource(1234) 25 s.r = rand.New(s.RandSource) 26 s.players = s.generatePlayers() 27 s.playersAvailable = s.players.Copy() 28 s.playersIdle = make(PlayerLookup) 29 30 currentTimestamp := time.Date(2023, 12, 13, 11, 12, 13, 14, time.UTC) 31 elapsedSinceLastTick := 500 * time.Millisecond 32 s.tickPlayerArrivals(currentTimestamp, elapsedSinceLastTick) 33 assert.ItsNotEmpty(t, s.playersIdle) 34 } 35 36 func Test_Simulation_randomNewPlayerCount(t *testing.T) { 37 s := new(Simulation) 38 s.RandSource = rand.NewSource(1234) 39 s.r = rand.New(s.RandSource) 40 41 currentTimestampOff := time.Date(2023, 12, 13, 4, 12, 13, 14, time.UTC) 42 currentTimestampPeak := time.Date(2023, 12, 13, 19, 12, 13, 14, time.UTC) 43 elapsedSinceLastTick := 500 * time.Millisecond 44 45 offNewPlayerCount := s.randomNewPlayerCount(currentTimestampOff, elapsedSinceLastTick) 46 assert.ItsEqual(t, 0, offNewPlayerCount) 47 48 _ = rand.New(s.RandSource).Float64() 49 _ = rand.New(s.RandSource).Float64() 50 _ = rand.New(s.RandSource).Float64() 51 _ = rand.New(s.RandSource).Float64() 52 53 peakNewPlayerCount := s.randomNewPlayerCount(currentTimestampPeak, elapsedSinceLastTick) 54 assert.ItsEqual(t, 4, peakNewPlayerCount) 55 } 56 57 func Test_Simulation_randomNormal(t *testing.T) { 58 s := new(Simulation) 59 s.RandSource = rand.NewSource(1234) 60 s.Init() 61 62 rv := s.randomNormal(2, 2) 63 assert.ItsEqual(t, 2, int(rv)) 64 } 65 66 func Test_Simulation_updatePlayerStatsAndHistory_win(t *testing.T) { 67 s := new(Simulation) 68 69 mpa0 := mockPlayerNoHistory() 70 mpa1 := mockPlayerNoHistory() 71 mpa2 := mockPlayerNoHistory() 72 73 mpb0 := mockPlayerNoHistory() 74 mpb1 := mockPlayerNoHistory() 75 mpb2 := mockPlayerNoHistory() 76 77 mpc0 := mockPlayerNoHistory() 78 mpc1 := mockPlayerNoHistory() 79 mpc2 := mockPlayerNoHistory() 80 81 s.players = NewLookup([]*Player{ 82 mpa0, mpa1, mpa2, 83 mpb0, mpb1, mpb2, 84 mpc0, mpc1, mpc2, 85 }) 86 87 s.updatePlayerStatsAndHistory(mpa0.ID, &Game{ 88 TeamA: NewLookup([]*Player{mpa0, mpa1, mpa2}), 89 TeamB: NewLookup([]*Player{mpb0, mpb1, mpb2}), 90 Outcome: TeamA, 91 }) 92 assert.ItsEqual(t, 1, mpa0.Wins) 93 assert.ItsEqual(t, 1, mpa0.Games) 94 assert.ItsEqual(t, 1, mpa0.Recent.Len()) 95 } 96 97 func Test_Simulation_updatePlayerStatsAndHistory_loss(t *testing.T) { 98 s := new(Simulation) 99 100 mpa0 := mockPlayerNoHistory() 101 mpa1 := mockPlayerNoHistory() 102 mpa2 := mockPlayerNoHistory() 103 104 mpb0 := mockPlayerNoHistory() 105 mpb1 := mockPlayerNoHistory() 106 mpb2 := mockPlayerNoHistory() 107 108 mpc0 := mockPlayerNoHistory() 109 mpc1 := mockPlayerNoHistory() 110 mpc2 := mockPlayerNoHistory() 111 112 s.players = NewLookup([]*Player{ 113 mpa0, mpa1, mpa2, 114 mpb0, mpb1, mpb2, 115 mpc0, mpc1, mpc2, 116 }) 117 118 s.updatePlayerStatsAndHistory(mpb0.ID, &Game{ 119 TeamA: NewLookup([]*Player{mpa0, mpa1, mpa2}), 120 TeamB: NewLookup([]*Player{mpb0, mpb1, mpb2}), 121 Outcome: TeamA, 122 }) 123 124 assert.ItsEqual(t, 0, mpb0.Wins) 125 assert.ItsEqual(t, 1, mpb0.Games) 126 assert.ItsEqual(t, 1, mpb0.Recent.Len()) 127 } 128 129 func Test_Simulation_updatePlayerStatsAndHistory_maintainsMaxQueueLength(t *testing.T) { 130 s := new(Simulation) 131 s.Config.PlayerRatingHistoryLen = 5 132 133 mpa0 := mockPlayerNoHistory() 134 for x := 0; x < s.Config.PlayerRatingHistoryLen; x++ { 135 mpa0.Recent.Push(&Game{ID: uuid.V4(), TeamA: team(mpa0), RatingTeamA: 1000, RatingTeamB: 1100, Outcome: TeamA}) 136 } 137 138 mpa1 := mockPlayerNoHistory() 139 mpa2 := mockPlayerNoHistory() 140 141 mpb0 := mockPlayerNoHistory() 142 mpb1 := mockPlayerNoHistory() 143 mpb2 := mockPlayerNoHistory() 144 145 mpc0 := mockPlayerNoHistory() 146 mpc1 := mockPlayerNoHistory() 147 mpc2 := mockPlayerNoHistory() 148 149 s.players = NewLookup([]*Player{ 150 mpa0, mpa1, mpa2, 151 mpb0, mpb1, mpb2, 152 mpc0, mpc1, mpc2, 153 }) 154 155 s.updatePlayerStatsAndHistory(mpa0.ID, &Game{ 156 TeamA: NewLookup([]*Player{mpa0, mpa1, mpa2}), 157 TeamB: NewLookup([]*Player{mpb0, mpb1, mpb2}), 158 Outcome: TeamA, 159 }) 160 assert.ItsEqual(t, s.Config.PlayerRatingHistoryLen, mpa0.Recent.Len()) 161 } 162 163 func Test_Simulation_moveGamePlayersToIdle(t *testing.T) { 164 s := new(Simulation) 165 166 mpa0 := mockPlayer0() 167 mpa1 := mockPlayer0() 168 mpa2 := mockPlayer0() 169 170 mpb0 := mockPlayer1() 171 mpb1 := mockPlayer1() 172 mpb2 := mockPlayer1() 173 174 mpc0 := mockPlayer1() 175 mpc1 := mockPlayer1() 176 mpc2 := mockPlayer1() 177 178 s.players = NewLookup([]*Player{ 179 mpa0, mpa1, mpa2, 180 mpb0, mpb1, mpb2, 181 mpc0, mpc1, mpc2, 182 }) 183 s.playersInGame = s.players.Copy() 184 s.playersIdle = make(PlayerLookup) 185 186 assert.ItsLen(t, s.players, 9) 187 assert.ItsLen(t, s.playersInGame, 9) 188 assert.ItsLen(t, s.playersIdle, 0) 189 190 s.moveGamePlayersToIdle(&Game{ 191 TeamA: NewLookup([]*Player{mpa0, mpa1, mpa2}), 192 TeamB: NewLookup([]*Player{mpb0, mpb1, mpb2}), 193 }) 194 195 assert.ItsLen(t, s.players, 9) 196 assert.ItsLen(t, s.playersInGame, 3) 197 assert.ItsLen(t, s.playersIdle, 6) 198 199 assert.ItsTrue(t, s.playersInGame.HasKey(mpc0.ID)) 200 assert.ItsTrue(t, s.playersInGame.HasKey(mpc1.ID)) 201 assert.ItsTrue(t, s.playersInGame.HasKey(mpc2.ID)) 202 203 assert.ItsTrue(t, s.playersIdle.HasKey(mpa0.ID)) 204 assert.ItsTrue(t, s.playersIdle.HasKey(mpa1.ID)) 205 assert.ItsTrue(t, s.playersIdle.HasKey(mpa2.ID)) 206 assert.ItsTrue(t, s.playersIdle.HasKey(mpb0.ID)) 207 assert.ItsTrue(t, s.playersIdle.HasKey(mpb1.ID)) 208 assert.ItsTrue(t, s.playersIdle.HasKey(mpb2.ID)) 209 } 210 211 func Test_Simulation_movePlayerToIdle(t *testing.T) { 212 s := new(Simulation) 213 214 s.players = make(PlayerLookup) 215 s.playersInGame = make(PlayerLookup) 216 s.playersIdle = make(PlayerLookup) 217 218 mp0 := mockPlayer0() 219 mp1 := mockPlayer0() 220 s.players.Add(mp0) 221 s.players.Add(mp1) 222 223 s.playersInGame.Add(mp0) 224 s.playersInGame.Add(mp1) 225 226 assert.ItsLen(t, s.players, 2) 227 assert.ItsLen(t, s.playersInGame, 2) 228 assert.ItsLen(t, s.playersIdle, 0) 229 230 s.movePlayerToIdle(mp0.ID) 231 232 assert.ItsLen(t, s.players, 2) 233 assert.ItsLen(t, s.playersInGame, 1) 234 assert.ItsLen(t, s.playersIdle, 1) 235 236 assert.ItsEqual(t, false, s.playersInGame.HasKey(mp0.ID)) 237 assert.ItsEqual(t, true, s.playersInGame.HasKey(mp1.ID)) 238 239 assert.ItsEqual(t, true, s.playersIdle.HasKey(mp0.ID)) 240 assert.ItsEqual(t, false, s.playersIdle.HasKey(mp1.ID)) 241 } 242 243 func Test_Simulation_moveGamePlayersToInGame(t *testing.T) { 244 s := new(Simulation) 245 246 mpa0 := mockPlayer0() 247 mpa1 := mockPlayer0() 248 mpa2 := mockPlayer0() 249 250 mpb0 := mockPlayer1() 251 mpb1 := mockPlayer1() 252 mpb2 := mockPlayer1() 253 254 mpc0 := mockPlayer1() 255 mpc1 := mockPlayer1() 256 mpc2 := mockPlayer1() 257 258 s.players = NewLookup([]*Player{ 259 mpa0, mpa1, mpa2, 260 mpb0, mpb1, mpb2, 261 mpc0, mpc1, mpc2, 262 }) 263 s.playersInQueue = s.players.Copy() 264 s.playersInGame = make(PlayerLookup) 265 266 assert.ItsLen(t, s.players, 9) 267 assert.ItsLen(t, s.playersInQueue, 9) 268 assert.ItsLen(t, s.playersInGame, 0) 269 270 s.moveGamePlayersToInGame(&Game{ 271 TeamA: NewLookup([]*Player{mpa0, mpa1, mpa2}), 272 TeamB: NewLookup([]*Player{mpb0, mpb1, mpb2}), 273 }) 274 275 assert.ItsLen(t, s.players, 9) 276 assert.ItsLen(t, s.playersInQueue, 3) 277 assert.ItsLen(t, s.playersInGame, 6) 278 279 assert.ItsTrue(t, s.playersInGame.HasKey(mpa0.ID)) 280 assert.ItsTrue(t, s.playersInGame.HasKey(mpa1.ID)) 281 assert.ItsTrue(t, s.playersInGame.HasKey(mpa2.ID)) 282 assert.ItsTrue(t, s.playersInGame.HasKey(mpb0.ID)) 283 assert.ItsTrue(t, s.playersInGame.HasKey(mpb1.ID)) 284 assert.ItsTrue(t, s.playersInGame.HasKey(mpb2.ID)) 285 286 assert.ItsFalse(t, s.playersInGame.HasKey(mpc0.ID)) 287 assert.ItsFalse(t, s.playersInGame.HasKey(mpc1.ID)) 288 assert.ItsFalse(t, s.playersInGame.HasKey(mpc2.ID)) 289 } 290 291 func Test_Simulation_movePlayerToInGame(t *testing.T) { 292 s := new(Simulation) 293 294 s.players = make(PlayerLookup) 295 s.playersInQueue = make(PlayerLookup) 296 s.playersInGame = make(PlayerLookup) 297 298 mp0 := mockPlayer0() 299 mp1 := mockPlayer0() 300 s.players.Add(mp0) 301 s.players.Add(mp1) 302 303 s.playersInQueue.Add(mp0) 304 s.playersInQueue.Add(mp1) 305 306 assert.ItsLen(t, s.players, 2) 307 assert.ItsLen(t, s.playersInQueue, 2) 308 assert.ItsLen(t, s.playersInGame, 0) 309 310 s.movePlayerToInGame(mp0.ID) 311 312 assert.ItsLen(t, s.players, 2) 313 assert.ItsLen(t, s.playersInQueue, 1) 314 assert.ItsLen(t, s.playersInGame, 1) 315 316 assert.ItsEqual(t, false, s.playersInQueue.HasKey(mp0.ID)) 317 assert.ItsEqual(t, true, s.playersInQueue.HasKey(mp1.ID)) 318 319 assert.ItsEqual(t, true, s.playersInGame.HasKey(mp0.ID)) 320 assert.ItsEqual(t, false, s.playersInGame.HasKey(mp1.ID)) 321 } 322 323 func Test_Simulation_generatePlayers(t *testing.T) { 324 s := new(Simulation) 325 s.RandSource = rand.NewSource(1234) 326 s.Config.PlayerCount = 64 327 s.Config.ServerCount = 32 328 s.Init() 329 330 players := s.generatePlayers() 331 assert.ItsLen(t, players, 64) 332 for id, p := range players { 333 assert.ItsFalse(t, id.IsZero()) 334 assert.ItsFalse(t, p.ID.IsZero()) 335 assert.ItsTrue(t, id.Equal(p.ID)) 336 assert.ItsTrue(t, p.NaturalSkill > 0 && p.NaturalSkill < 2000) 337 assert.ItsNotNil(t, p.Recent) 338 } 339 } 340 341 func Test_Simulation_generateServers(t *testing.T) { 342 s := new(Simulation) 343 s.Config.ServerCount = 32 344 345 servers := s.generateServers() 346 assert.ItsLen(t, servers, 32) 347 for id, svr := range servers { 348 assert.ItsFalse(t, id.IsZero()) 349 assert.ItsFalse(t, svr.ID.IsZero()) 350 assert.ItsTrue(t, id.Equal(svr.ID)) 351 } 352 } 353 354 func Test_Simulation_server(t *testing.T) { 355 s := new(Simulation) 356 svr := s.createServer() 357 assert.ItsEqual(t, false, svr.ID.IsZero()) 358 } 359 360 func Test_Simulation_createPlayer(t *testing.T) { 361 s := new(Simulation) 362 s.RandSource = rand.NewSource(1234) 363 s.Init() 364 s.Config.PlayerNaturalRatingMedian = 1000 365 s.Config.PlayerNaturalRatingStdDev = 100 366 367 p := s.createPlayer() 368 369 assert.ItsEqual(t, false, p.ID.IsZero()) 370 assert.ItsEqual(t, 1021, p.NaturalSkill) 371 assert.ItsEqual(t, ratingBase, p.Rating) 372 assert.ItsNotNil(t, p.Recent) 373 } 374 375 func Test_Simulation_createGame(t *testing.T) { 376 s := new(Simulation) 377 378 currentTimestamp := time.Date(2023, 12, 11, 10, 9, 8, 7, time.UTC) 379 teamA := []*Player{ 380 mockPlayer0(), 381 mockPlayer0(), 382 mockPlayer1(), 383 mockPlayer1(), 384 mockPlayer0(), 385 mockPlayer0(), 386 } 387 teamB := []*Player{ 388 mockPlayer1(), 389 mockPlayer1(), 390 mockPlayer0(), 391 mockPlayer0(), 392 mockPlayer1(), 393 mockPlayer1(), 394 } 395 396 g := s.createGame(currentTimestamp, teamA, teamB) 397 assert.ItsEqual(t, false, g.ID.IsZero()) 398 assert.ItsEqual(t, currentTimestamp, g.StartedAt) 399 assert.ItsEqual(t, currentTimestamp.Add(s.Config.GameDurationOrDefault()), g.FinishedAt) 400 assert.ItsLen(t, g.TeamA, 6) 401 assert.ItsLen(t, g.TeamB, 6) 402 403 assert.ItsEqual(t, 1533, g.RatingTeamA) 404 assert.ItsEqual(t, 1666, g.RatingTeamB) 405 }