[go游戏开发实践]关于匹配机制的测试

关于游戏匹配机制的测试

上篇博文写过关于匹配的实现算法:[go游戏开发实践]游戏匹配机制实现
文中并没有对该算法进行优化并测试,没有验证该算法的可用性。
在测试的过程中发现了一些问题,并做出了优化的方案。

一、先对原来的算法进行测试,看有什么问题

这边还是贴一下整理后的关于匹配需要的结构代码
主要包括3个结构:匹配参数、匹配玩家信息、匹配池

package main

import (
	"time"
)

//============================匹配参数==========================
type MatchParamModel struct {
	SavePoolCheckTime uint32  //蓄池检测时间
	SavePoolTime      uint32  //蓄池时间
	SavePoolNum       uint32  //蓄池目标数量
	MinCount          uint32  //队伍最少人数
	Count             uint32  //队伍人数
	MaxWaitTime       uint32  //最长等待时间
	minRangeParam1    uint32  //参数1的最小值
	maxRangeParam1    uint32  //参数1的最大值
	minRangeParam2    uint32  //参数2的最小值
	maxRangeParam2    uint32  //参数2的最大值
}

//========================匹配玩家信息=========================
type MatchPlayerInfo struct {
	playerId  uint64  //玩家id
	param1    uint32  //玩家参数1
	param2    uint32  //玩家参数2
	startTime int64   //玩家开始匹配时间
}

func newMatchPlayerInfo(pId uint64, p1 uint32, p2 uint32) *MatchPlayerInfo {
	newMatchPlayer := &MatchPlayerInfo{}
	newMatchPlayer.param1 = p1
	newMatchPlayer.param2 = p2
	newMatchPlayer.playerId = pId
	newMatchPlayer.startTime = time.Now().Unix()
	return newMatchPlayer
}

func (this *MatchPlayerInfo) waitTime() uint64 {
	var res uint64
	if time.Now().Unix() >= this.startTime {
		res = uint64(time.Now().Unix() - this.startTime)
	}
	return res
}

//============================匹配池================================
type MatchPool struct {
	playerArr     map[uint64]*MatchPlayerInfo  //玩家map
	size          uint32					   //匹配池玩家数量
	maxWaitPlayer *MatchPlayerInfo       	   //最长等待玩家
}

func newMatchPool() *MatchPool {
	newPool := &MatchPool{}
	newPool.playerArr = make(map[uint64]*MatchPlayerInfo)
	newPool.size = 0
	return newPool
}

func (this *MatchPool) putPlayerIntoPool(pId uint64, p1 uint32, p2 uint32) bool {
	newPlayer := newMatchPlayerInfo(pId, p1, p2)
	_, ok := this.playerArr[pId]
	if ok {
		return false
	}
	this.playerArr[pId] = newPlayer
	this.size++
	return true
}

func (this *MatchPool) putPlayerArrIntoPool(pArr map[uint64]*MatchPlayerInfo) bool {
	res := true
	for _, p := range pArr {
		res = res && this.putPlayerIntoPool(p.playerId, p.param1, p.param2)
	}
	return res
}

func (this *MatchPool) removePlayerOutPool(pId uint64) bool {
	_, ok := this.playerArr[pId]
	if ok && this.size > 0 {
		delete(this.playerArr, pId)
		this.size--
		return true
	} else {
		return false
	}
}

func (this *MatchPool) removePlayerArrOutPool(pArr map[uint64]*MatchPlayerInfo) bool {
	res := true
	for _, p := range pArr {
		res = res && this.removePlayerOutPool(p.playerId)
	}
	return res
}

func (this *MatchPool) selectPlayerByCount(count uint32, res map[uint64]*MatchPlayerInfo) {
	for _, p := range this.playerArr {
		res[p.playerId] = p
		this.removePlayerOutPool(p.playerId)
		count--
		if count <= 0 {
			break
		}
	}
}

func (this *MatchPool) selectPlayerByParam(p1 uint32, p2 uint32, count uint32, res map[uint64]*MatchPlayerInfo) {
	times := 1
	for times <= 3 && count > 0 {
		for _, p := range this.playerArr {
			flag := true
			if times == 1 {
				flag = p1 == p.param1 && p2 == p.param2
			} else if times == 2 {
				flag = p1 == p.param1
			}
			if flag {
				res[p.playerId] = p
				this.removePlayerOutPool(p.playerId)
				count--
			}
			if count <= 0 {
				break
			}
		}
		times++
	}
}

func (this *MatchPool) selectPlayerByWaitTime(waittime uint64, res map[uint64]*MatchPlayerInfo) {
	for _, p := range this.playerArr {
		if p.waitTime() >= waittime {
			res[p.playerId] = p
			this.removePlayerOutPool(p.playerId)
		}
	}
}

func (this *MatchPool) refreshMaxWait() {
	var maxwait uint64
	for _, p := range this.playerArr {
		if maxwait <= p.waitTime() {
			maxwait = p.waitTime()
			this.maxWaitPlayer = p
		}
	}
}

下面使用协程测试一下匹配逻辑,看一下会有什么问题:

package main

import (
	"fmt"
	"math/rand"
	"time"
)
var pool *MatchPool
var count uint32
var groupVariance []int
var model *MatchParamModel

func main() {
	setModel(2, 4, 50, 19) 
	pool = newMatchPool()
	test1()  //~协程测试
	
	time.Sleep(20 * time.Second)
	fmt.Println("匹配结束!")
	var s int
	for _, v := range groupVariance {
		s += v
	}
	fmt.Printf("匹配平均方差为%v", float64(s)/float64(count))
}

//~设置参数
func setModel(spCheckTime uint32, spTime uint32, spNum uint32, mWaitTime uint32) {
	model = &MatchParamModel{
		SavePoolCheckTime: spCheckTime,
		SavePoolTime:      spTime,
		SavePoolNum:       spNum,
		MinCount:          8,
		Count:             12,
		MaxWaitTime:       mWaitTime,
		minRangeParam1:    1,
		maxRangeParam1:    10,
		minRangeParam2:    10,
		maxRangeParam2:    20,
	}
}

func test1() {
	var t uint64
	for t = 0; t < 5; t++ {
		//~随机产生玩家信息 -- 每隔2秒产生100个玩家
		var i uint64
		fmt.Println("===================开始匹配=============")
		m := make(map[uint64]*MatchPlayerInfo)
		for i = 0; i < 100; i++ {
			p1 := rand.Uint32()%10 + 1
			p2 := rand.Uint32()%10 + 11
			p := newMatchPlayerInfo(t*1000+i, p1, p2)
			if pool.size == 0 { //池子没有玩家,起协程
				pool.putPlayerIntoPool(p.playerId, p.param1, p.param2)
				go matchProcess(model) 
			} else {
				pool.putPlayerIntoPool(p.playerId, p.param1, p.param2)
			}
		}
		time.Sleep(2 * time.Second)
	}
}

//~匹配流程
func matchProcess(model *MatchParamModel) {
	if model == nil {
		return
	}
	waitLongPool := make(map[uint64]*MatchPlayerInfo)
	//匹配池中有人就持续执行
	for pool.size > 0 {
		//删除超时玩家
		outTimePlayer := make(map[uint64]*MatchPlayerInfo)
		pool.selectPlayerByWaitTime(uint64(model.MaxWaitTime), outTimePlayer) //选择的时候已经将玩家从池中移除
		if len(outTimePlayer) > 0 {                                           //有超时玩家
			for _, p := range outTimePlayer {
				//超时通知处理
				fmt.Printf("玩家%v匹配超时\n", p.playerId)
			}
			continue
		}

		//刷新最长等待玩家 -- 用于最先匹配
		pool.refreshMaxWait()
		if pool.maxWaitPlayer == nil {
			return
		}

		//蓄池等待 -- 是否达到蓄池目标 玩家等待时间是否超出蓄池时间
		if pool.size < model.SavePoolNum && pool.maxWaitPlayer.waitTime() < uint64(model.SavePoolTime) {
			//~等待一轮检测时间
			time.Sleep(time.Duration(model.SavePoolCheckTime) * time.Second)
			continue
		}

		//进行匹配
		for pool.size >= model.MinCount {
			//挑选出超出蓄池时间的玩家
			pool.selectPlayerByWaitTime(uint64(model.SavePoolTime), waitLongPool)
			pool.refreshMaxWait()
			//优先匹配长等待玩家
			matchPlayer := make(map[uint64]*MatchPlayerInfo)
			if len(waitLongPool) > 0 {
				for _, p := range waitLongPool {
					matchPlayer[p.playerId] = p
					delete(waitLongPool, p.playerId)
					if uint32(len(matchPlayer)) == model.Count {
						break
					}
				}
			}
			//正常匹配
			if uint32(len(matchPlayer)) <= model.Count {
				p1 := pool.maxWaitPlayer.param1
				p2 := pool.maxWaitPlayer.param2
				needCount := model.Count - uint32(len(matchPlayer))
				pool.selectPlayerByParam(p1, p2, needCount, matchPlayer)
				if uint32(len(matchPlayer)) >= model.MinCount {
					//进行游戏处理
					fmt.Println("进行游戏")
					var arr []int
					for _, p := range matchPlayer {
						fmt.Printf("该组玩家为%v \n", p)
						arr = append(arr, int(p.param1))
					}
					fmt.Printf("该组玩家的方差为%v \n", cal(arr))
					groupVariance = append(groupVariance, cal(arr))
					count++
				} else {
					//玩家过少 继续放入池中等下一轮蓄池
					pool.putPlayerArrIntoPool(matchPlayer)
				}
			}
			pool.putPlayerArrIntoPool(waitLongPool)
		}
	}
	fmt.Printf("一共匹配%v组\n", count)
	fmt.Println("====================匹配完毕=======================")
}

//~简单计算方差
func cal(arr []int) int {
	var sum int
	for _, v := range arr {
		sum += v
	}
	avg := sum / len(arr)
	var res int
	for _, v := range arr {
		res += getPower(v, avg)
	}
	res = res / len(arr)
	return res
}
func getPower(v int, avg int) int {
	return (v - avg) * (v - avg)
}

测试会出现:
fatal error: concurrent map iteration and map write
go的map不支持同时的遍历跟读写,所以在协程运行时我对pool中的playerArr进行了遍历,但是在主线程,执行了putPlayerArrIntoPool操作,对map进行了写入

那么go的map为什么会不支持同时的遍历跟读写呢?

  • go的map采用了传统的hashMap结构,由数组跟链表构成
  • 当go在写入操作时,可能造成溢出,这个时候需要对hashMap进行重新分配
  • 虽然重新分配是逐步进行的,但是这个时候进行遍历或读操作会获取到不正确的信息

深度map分析可以参考:golang-map分析

二、利用锁解决map不支持并发写问题

基于map的不支持并发写,可以对读写操作进行加锁,对pool结构进行一下修改(仅贴出修改的方法跟结构):

type MatchPool struct {
	playerArr     map[uint64]*MatchPlayerInfo
	size          uint32
	maxWaitPlayer *MatchPlayerInfo
	rwMutex       sync.RWMutex  //加读写锁
}

func (this *MatchPool) putPlayerIntoPool(pId uint64, p1 uint32, p2 uint32) bool {
	this.rwMutex.Lock()
	defer this.rwMutex.Unlock()
	newPlayer := newMatchPlayerInfo(pId, p1, p2)
	_, ok := this.playerArr[pId]
	if ok {
		return false
	}
	this.playerArr[pId] = newPlayer
	this.size++
	return true
}

func (this *MatchPool) removePlayerArrOutPool(pArr map[uint64]*MatchPlayerInfo) bool {
	this.rwMutex.Lock()
	defer this.rwMutex.Unlock()
	res := true
	for _, p := range pArr {
		res = res && this.removePlayerOutPool(p.playerId)
	}
	return res
}

func (this *MatchPool) selectPlayerByCount(count uint32, res map[uint64]*MatchPlayerInfo) {
	this.rwMutex.Lock()
	defer this.rwMutex.Unlock()
	for _, p := range this.playerArr {
		res[p.playerId] = p
		this.removePlayerOutPool(p.playerId)
		count--
		if count <= 0 {
			break
		}
	}	
}

func (this *MatchPool) selectPlayerByParam(p1 uint32, p2 uint32, count uint32, res map[uint64]*MatchPlayerInfo) {
	this.rwMutex.Lock()
	defer this.rwMutex.Unlock()
	times := 1
	for times <= 3 && count > 0 {
		for _, p := range this.playerArr {
			flag := true
			if times == 1 {
				flag = p1 == p.param1 && p2 == p.param2
			} else if times == 2 {
				flag = p1 == p.param1
			}
			if flag {
				res[p.playerId] = p
				this.removePlayerOutPool(p.playerId)
				count--
			}
			if count <= 0 {
				break
			}
		}
		times++
	}
}

func (this *MatchPool) selectPlayerByWaitTime(waittime uint64, res map[uint64]*MatchPlayerInfo) {
	this.rwMutex.Lock()
	defer this.rwMutex.Unlock()
	for _, p := range this.playerArr {
		if p.waitTime() >= waittime {
			res[p.playerId] = p
			this.removePlayerOutPool(p.playerId)
		}
	}
}

func (this *MatchPool) refreshMaxWait() {
	this.rwMutex.Lock()
	defer this.rwMutex.Unlock()
	var maxwait uint64
	
	for _, p := range this.playerArr {
		if maxwait <= p.waitTime() {
			maxwait = p.waitTime()
			this.maxWaitPlayer = p
		}
	}
}

加了锁之后,可以测试可以正确执行,这里就不贴测试结果了。

三、进一步优化

下面进行进一步的优化:

  • 考虑到显示的加锁,需要在每个有map读写操作的地方都去进行加锁
  • 从性能上考虑,这样频繁加锁并不佳
  • 实际匹配的过程中,会频繁的进行写入,匹配也就会频繁进行阻塞加锁
  • 可以利用chan和定时器进行锁优化
var p chan *MatchPlayerInfo
var start chan int
var quit chan int

func main() {
	setModel(2, 4, 200, 19)
	pool = newMatchPool()

	test2()
	
	time.Sleep(20 * time.Second)
	quit <- 1
	fmt.Println("===================匹配结束================")
	var s int
	for _, v := range groupVariance {
		s += v
	}
	fmt.Printf("匹配平均方差为%v", float64(s)/float64(count))
}

func test2() {
	p = make(chan *MatchPlayerInfo, 100) //玩家通道
	start = make(chan int, 1) //匹配信号
	quit = make(chan int, 1) //退出信号

	go doRun()
	go genPlayer()
	go clockStart()
}

func doRun() {
	for {
		select {
		case play := <-p:
			pool.putPlayerIntoPool(play.playerId, play.param1, play.param2)
		case <-start:
			matchProcess2(model)
		case <-quit:
			return
		}
	}
}

func matchProcess2(model *MatchParamModel) {
	if model == nil {
		return
	}
	fmt.Println("===================开始匹配=======================")
	waitLongPool := make(map[uint64]*MatchPlayerInfo)
	if pool.size > 0 {
		//删除超时玩家
		outTimePlayer := make(map[uint64]*MatchPlayerInfo)
		pool.selectPlayerByWaitTime(uint64(model.MaxWaitTime), outTimePlayer) //选择的时候已经将玩家从池中移除
		if len(outTimePlayer) > 0 {                                           //有超时玩家
			for _, p := range outTimePlayer {
				//超时通知处理
				fmt.Printf("玩家%v匹配超时\n", p.playerId)
			}
		}

		//刷新最长等待玩家 -- 用于最先匹配
		pool.refreshMaxWait()
		if pool.maxWaitPlayer == nil {
			return
		}

		//蓄池等待 -- 是否达到蓄池目标 玩家等待时间是否超出蓄池时间
		if pool.size < model.SavePoolNum && pool.maxWaitPlayer.waitTime() < uint64(model.SavePoolTime) {
			return
		}

		//进行匹配
		for pool.size >= model.MinCount {
			//挑选出超出蓄池时间的玩家
			pool.selectPlayerByWaitTime(uint64(model.SavePoolTime), waitLongPool)
			pool.refreshMaxWait()

			//匹配超时等待玩家
			//正常匹配
			matchPlayer := make(map[uint64]*MatchPlayerInfo)
			if len(waitLongPool) > 0 {
				for _, p := range waitLongPool {
					matchPlayer[p.playerId] = p
					delete(waitLongPool, p.playerId)
					if uint32(len(matchPlayer)) == model.Count {
						break
					}
				}
			}

			if uint32(len(matchPlayer)) <= model.Count {
				p1 := pool.maxWaitPlayer.param1
				p2 := pool.maxWaitPlayer.param2
				needCount := model.Count - uint32(len(matchPlayer))
				pool.selectPlayerByParam(p1, p2, needCount, matchPlayer)
				if uint32(len(matchPlayer)) >= model.MinCount && uint32(len(matchPlayer)) <= model.Count {
					//进行游戏处理
					fmt.Println("进行游戏")
					var arr []int
					for _, p := range matchPlayer {
						fmt.Printf("该组玩家为%v \n", p)
						arr = append(arr, int(p.param1))
					}
					fmt.Printf("该组玩家的方差为%v \n", cal(arr))
					groupVariance = append(groupVariance, cal(arr))
					count++
				} else {
					//玩家过少 继续放入池中等下一轮蓄池
					pool.putPlayerArrIntoPool(matchPlayer)
				}
			}
			pool.putPlayerArrIntoPool(waitLongPool)
		}
	}
	fmt.Printf("一共匹配%v组\n", count)
	fmt.Println("===================匹配完毕========================")
}

func clockStart() {
	for i := 0; i < 6; i++ {
		start <- 1
		time.Sleep(2 * time.Second)
	}
}

func genPlayer() {
	var t uint64
	for t = 0; t < 10; t++ {
		//~随机产生玩家信息
		var i uint64
		for i = 0; i < 50; i++ {
			p1 := rand.Uint32()%10 + 1
			p2 := rand.Uint32()%10 + 11
			p <- newMatchPlayerInfo(t*1000+i, p1, p2)
			time.Sleep(time.Millisecond)
		}
		time.Sleep(2 * time.Second)
	}
}

select 语句使一个 Go 程可以等待多个通信操作。
select 会阻塞到某个分支可以继续执行为止,这时就会执行该分支。当多个分支都准备好时会随机选择一个执行。
利用了select将写入pool的操作匹配遍历操作隔离,使得在同一时间只能有一个操作在执行,同时修改了匹配流程,将匹配过程改为每隔一个clock时间匹配执行一次。

四、简单的测试结果

可以设置不同的匹配参数进行测试,下面是上述代码中设置的参数的测试结果:

进行游戏
该组玩家为&{1 5 19 1590560488}
该组玩家为&{16 5 14 1590560488}
该组玩家为&{24 10 17 1590560488}
该组玩家为&{12 5 15 1590560488}
该组玩家为&{37 5 19 1590560488}
该组玩家为&{48 5 17 1590560488}
该组玩家为&{38 5 17 1590560488}
该组玩家为&{8 3 12 1590560488}
该组玩家为&{41 5 18 1590560488}
该组玩家为&{9 5 14 1590560488}
该组玩家为&{25 5 13 1590560488}
该组玩家为&{18 5 14 1590560488}
该组玩家的方差为2
进行游戏
该组玩家为&{35 7 13 1590560488}
该组玩家为&{46 4 15 1590560488}
该组玩家为&{7 7 20 1590560488}
该组玩家为&{45 8 13 1590560488}
该组玩家为&{23 8 19 1590560488}
该组玩家为&{11 8 17 1590560488}
该组玩家为&{43 8 17 1590560488}
该组玩家为&{31 1 14 1590560488}
该组玩家为&{15 4 18 1590560488}
该组玩家为&{34 8 13 1590560488}
该组玩家为&{49 2 18 1590560488}
该组玩家为&{10 1 14 1590560488}
该组玩家的方差为8
进行游戏
该组玩家为&{3 1 11 1590560488}
该组玩家为&{29 7 11 1590560488}
该组玩家为&{27 2 11 1590560488}
该组玩家为&{47 2 18 1590560488}
该组玩家为&{21 10 18 1590560488}
该组玩家为&{28 7 18 1590560488}
该组玩家为&{33 3 16 1590560488}
该组玩家为&{5 10 14 1590560488}
该组玩家为&{4 3 11 1590560488}
该组玩家为&{42 3 20 1590560488}
该组玩家为&{20 7 12 1590560488}
该组玩家为&{30 3 12 1590560488}
该组玩家的方差为10
进行游戏
该组玩家为&{36 6 17 1590560488}
该组玩家为&{6 6 20 1590560488}
该组玩家为&{22 7 16 1590560488}
该组玩家为&{26 9 19 1590560488}
该组玩家为&{0 3 15 1590560488}
该组玩家为&{2 3 17 1590560488}
该组玩家为&{32 4 18 1590560488}
该组玩家为&{19 1 14 1590560488}
该组玩家为&{39 3 11 1590560488}
该组玩家为&{17 9 14 1590560488}
该组玩家为&{13 6 17 1590560488}
该组玩家为&{14 1 12 1590560488}
该组玩家的方差为7
一轮玩家全部匹配完毕!
一共匹配4组
开始匹配进行游戏
该组玩家为&{40 7 17 1590560488}
该组玩家为&{1008 5 15 1590560490}
该组玩家为&{1029 5 17 1590560490}
该组玩家为&{1030 5 13 1590560490}
该组玩家为&{1038 5 11 1590560490}
该组玩家为&{1045 2 15 1590560490}
该组玩家为&{44 4 16 1590560488}
该组玩家为&{1021 5 16 1590560490}
该组玩家为&{1032 5 17 1590560490}
该组玩家为&{1017 5 19 1590560490}
该组玩家为&{1002 5 11 1590560490}
该组玩家为&{1033 5 18 1590560490}
该组玩家的方差为1
进行游戏
该组玩家为&{1011 3 20 1590560490}
该组玩家为&{1005 7 18 1590560490}
该组玩家为&{1031 7 19 1590560490}
该组玩家为&{1004 1 15 1590560490}
该组玩家为&{1003 1 12 1590560490}
该组玩家为&{1028 3 11 1590560490}
该组玩家为&{1023 1 13 1590560490}
该组玩家为&{1024 3 14 1590560490}
该组玩家为&{1034 7 18 1590560490}
该组玩家为&{1018 7 17 1590560490}
该组玩家为&{1047 7 13 1590560490}
该组玩家为&{1037 9 15 1590560490}
该组玩家的方差为8
进行游戏
该组玩家为&{1027 2 18 1590560490}
该组玩家为&{1012 4 11 1590560490}
该组玩家为&{1013 8 14 1590560490}
该组玩家为&{1022 4 20 1590560490}
该组玩家为&{1039 3 19 1590560490}
该组玩家为&{1035 3 11 1590560490}
该组玩家为&{1000 2 14 1590560490}
该组玩家为&{1016 1 12 1590560490}
该组玩家为&{1019 10 17 1590560490}
该组玩家为&{1009 3 19 1590560490}
该组玩家为&{1006 3 15 1590560490}
该组玩家为&{1010 3 15 1590560490}
该组玩家的方差为6
进行游戏
该组玩家为&{1044 6 12 1590560490}
该组玩家为&{1036 1 12 1590560490}
该组玩家为&{1043 1 19 1590560490}
该组玩家为&{1048 1 14 1590560490}
该组玩家为&{1025 1 13 1590560490}
该组玩家为&{1007 9 16 1590560490}
该组玩家为&{1041 2 20 1590560490}
该组玩家为&{1026 9 20 1590560490}
该组玩家为&{1040 1 18 1590560490}
该组玩家为&{1042 2 18 1590560490}
该组玩家为&{1046 9 11 1590560490}
该组玩家为&{1014 9 13 1590560490}
该组玩家的方差为13
一轮玩家全部匹配完毕!
一共匹配8
开始匹配进行游戏
该组玩家为&{2045 9 14 1590560492}
该组玩家为&{2030 9 18 1590560492}
该组玩家为&{2026 9 12 1590560492}
该组玩家为&{2017 4 11 1590560492}
该组玩家为&{1015 6 12 1590560490}
该组玩家为&{1001 2 16 1590560490}
该组玩家为&{1049 2 13 1590560490}
该组玩家为&{2033 9 19 1590560492}
该组玩家为&{2027 9 19 1590560492}
该组玩家为&{1020 9 16 1590560490}
该组玩家为&{2037 9 16 1590560492}
该组玩家为&{2049 9 15 1590560492}
该组玩家的方差为7
进行游戏
该组玩家为&{2040 5 11 1590560492}
该组玩家为&{2041 6 13 1590560492}
该组玩家为&{2039 4 18 1590560492}
该组玩家为&{2002 3 12 1590560492}
该组玩家为&{2006 7 17 1590560492}
该组玩家为&{2021 4 15 1590560492}
该组玩家为&{2014 4 15 1590560492}
该组玩家为&{2011 7 12 1590560492}
该组玩家为&{2003 10 15 1590560492}
该组玩家为&{2046 4 12 1590560492}
该组玩家为&{2022 4 12 1590560492}
该组玩家为&{2032 4 12 1590560492}
该组玩家的方差为3
进行游戏
该组玩家为&{2015 2 11 1590560492}
该组玩家为&{2029 2 11 1590560492}
该组玩家为&{2005 8 13 1590560492}
该组玩家为&{2018 1 19 1590560492}
该组玩家为&{2035 7 15 1590560492}
该组玩家为&{2000 10 15 1590560492}
该组玩家为&{2034 2 13 1590560492}
该组玩家为&{2020 2 14 1590560492}
该组玩家为&{2031 2 17 1590560492}
该组玩家为&{2013 2 14 1590560492}
该组玩家为&{2047 3 18 1590560492}
该组玩家为&{2009 5 16 1590560492}
该组玩家的方差为8
进行游戏
该组玩家为&{2023 10 16 1590560492}
该组玩家为&{2044 10 16 1590560492}
该组玩家为&{2038 10 17 1590560492}
该组玩家为&{2019 10 19 1590560492}
该组玩家为&{2042 5 14 1590560492}
该组玩家为&{2008 10 16 1590560492}
该组玩家为&{2025 10 11 1590560492}
该组玩家为&{2036 10 11 1590560492}
该组玩家为&{2016 6 19 1590560492}
该组玩家为&{2043 1 15 1590560492}
该组玩家为&{2024 8 13 1590560492}
该组玩家为&{2012 5 18 1590560492}
该组玩家的方差为9
一轮玩家全部匹配完毕!
一共匹配12组
开始匹配进行游戏
该组玩家为&{2007 8 14 1590560492}
该组玩家为&{2010 1 14 1590560492}
该组玩家为&{2004 6 15 1590560492}
该组玩家为&{3049 3 11 1590560494}
该组玩家为&{3011 3 14 1590560494}
该组玩家为&{3040 3 19 1590560494}
该组玩家为&{2001 3 16 1590560492}
该组玩家为&{2028 1 16 1590560492}
该组玩家为&{2048 8 14 1590560492}
该组玩家为&{3005 3 16 1590560494}
该组玩家为&{3041 3 12 1590560494}
该组玩家为&{3042 3 15 1590560494}
该组玩家的方差为5
进行游戏
该组玩家为&{3037 7 18 1590560494}
该组玩家为&{3008 7 19 1590560494}
该组玩家为&{3027 4 11 1590560494}
该组玩家为&{3015 4 13 1590560494}
该组玩家为&{3013 4 14 1590560494}
该组玩家为&{3029 6 15 1590560494}
该组玩家为&{3033 1 18 1590560494}
该组玩家为&{3007 10 14 1590560494}
该组玩家为&{3035 8 16 1590560494}
该组玩家为&{3000 8 17 1590560494}
该组玩家为&{3009 5 15 1590560494}
该组玩家为&{3017 10 18 1590560494}
该组玩家的方差为6
进行游戏
该组玩家为&{3032 8 14 1590560494}
该组玩家为&{3019 9 17 1590560494}
该组玩家为&{3039 8 18 1590560494}
该组玩家为&{3014 6 16 1590560494}
该组玩家为&{3026 10 14 1590560494}
该组玩家为&{3034 10 16 1590560494}
该组玩家为&{3020 10 13 1590560494}
该组玩家为&{3006 10 18 1590560494}
该组玩家为&{3021 6 17 1590560494}
该组玩家为&{3012 7 18 1590560494}
该组玩家为&{3048 7 18 1590560494}
该组玩家为&{3003 2 19 1590560494}
该组玩家的方差为5
进行游戏
该组玩家为&{3004 3 16 1590560494}
该组玩家为&{3038 6 13 1590560494}
该组玩家为&{3022 1 13 1590560494}
该组玩家为&{3002 9 20 1590560494}
该组玩家为&{3044 9 17 1590560494}
该组玩家为&{3031 7 12 1590560494}
该组玩家为&{3001 8 18 1590560494}
该组玩家为&{3025 2 14 1590560494}
该组玩家为&{3028 1 19 1590560494}
该组玩家为&{3046 5 15 1590560494}
该组玩家为&{3016 1 15 1590560494}
该组玩家为&{3023 7 11 1590560494}
该组玩家的方差为10
进行游戏
该组玩家为&{3036 7 15 1590560494}
该组玩家为&{3043 5 16 1590560494}
该组玩家为&{3045 2 14 1590560494}
该组玩家为&{3018 6 11 1590560494}
该组玩家为&{3024 1 14 1590560494}
该组玩家为&{3030 7 11 1590560494}
该组玩家为&{3047 9 13 1590560494}
该组玩家为&{3010 9 19 1590560494}
该组玩家的方差为8
一轮玩家全部匹配完毕!
一共匹配17组
开始匹配进行游戏
该组玩家为&{4046 1 14 1590560496}
该组玩家为&{4018 1 11 1590560496}
该组玩家为&{4032 1 13 1590560496}
该组玩家为&{4033 6 13 1590560496}
该组玩家为&{4037 7 14 1590560496}
该组玩家为&{4007 2 11 1590560496}
该组玩家为&{4027 1 14 1590560496}
该组玩家为&{4035 1 20 1590560496}
该组玩家为&{4024 1 19 1590560496}
该组玩家为&{4013 1 15 1590560496}
该组玩家为&{4039 1 15 1590560496}
该组玩家为&{4009 1 19 1590560496}
该组玩家的方差为4
进行游戏
该组玩家为&{4028 8 17 1590560496}
该组玩家为&{4019 4 20 1590560496}
该组玩家为&{4014 5 11 1590560496}
该组玩家为&{4008 10 19 1590560496}
该组玩家为&{4021 10 16 1590560496}
该组玩家为&{4003 2 19 1590560496}
该组玩家为&{4005 9 16 1590560496}
该组玩家为&{4006 7 20 1590560496}
该组玩家为&{4040 8 20 1590560496}
该组玩家为&{4017 10 16 1590560496}
该组玩家为&{4000 5 12 1590560496}
该组玩家为&{4023 2 12 1590560496}
该组玩家的方差为8
进行游戏
该组玩家为&{4041 10 11 1590560496}
该组玩家为&{4004 10 15 1590560496}
该组玩家为&{4012 10 17 1590560496}
该组玩家为&{4045 8 18 1590560496}
该组玩家为&{4044 3 16 1590560496}
该组玩家为&{4029 10 20 1590560496}
该组玩家为&{4020 10 17 1590560496}
该组玩家为&{4001 10 14 1590560496}
该组玩家为&{4016 8 15 1590560496}
该组玩家为&{4011 4 12 1590560496}
该组玩家为&{4030 9 13 1590560496}
该组玩家为&{4034 9 17 1590560496}
该组玩家的方差为5
进行游戏
该组玩家为&{4002 9 19 1590560496}
该组玩家为&{4049 4 11 1590560496}
该组玩家为&{4025 8 11 1590560496}
该组玩家为&{4031 6 13 1590560496}
该组玩家为&{4010 4 14 1590560496}
该组玩家为&{4022 7 19 1590560496}
该组玩家为&{4048 9 12 1590560496}
该组玩家为&{4026 9 15 1590560496}
该组玩家为&{4038 3 12 1590560496}
该组玩家为&{4042 3 13 1590560496}
该组玩家为&{4015 6 16 1590560496}
该组玩家为&{4047 9 12 1590560496}
该组玩家的方差为5
一轮玩家全部匹配完毕!
一共匹配21组
匹配结束!
匹配平均方差为6.571428571428571

你可能感兴趣的:(go游戏开发实践)