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  }