iT邦幫忙

2021 iThome 鐵人賽

DAY 10
0
Software Development

徵坦補! 新手可! Open-Match 配對框架系列 第 10

Day10 Overlapping Example

昨天已經看過我們在實務上可能會遇到的需求,利用多個可能重複範圍的配對池,當作匹配搜尋條件,今天讓我們用實作範例來觀察其效果。

角色等級 Overlapping 範例

本範例同樣基於官方 demo,藉由更改範例內容重新 build images 後完成。

Images

  • weiweiwesley/open-match:overlapping
  • weiweiwesley/mmf:overlapping

Yaml

Install

~ kubectl create namespace open-match-demo

~ kubectl apply -n open-match-demo -f ~/open-match/overlapping.yml

Result

https://i.imgur.com/fe4toQh.gif

Client

隨機產生不同等級條件 tickets,這段跟之前相同不贅述,請參考 client.go#95

Director

這是產生 overlapping MatchProfile 的部分,不同的 MatchProfile,以不同的等級上下限表示不同的配對池。以下這些 MatchProfile 會被 Director 向核心詢問,並且呼叫 MMF 執行配對判斷,你可以把它想成像是不同的階級如:

  • 新手場

    req := &pb.FetchMatchesRequest{
    			Config: &pb.FunctionConfig{
    				Host: "om-function.open-match-demo.svc.cluster.local",
    				Port: 50502,
    				Type: pb.FunctionConfig_GRPC,
    			},
    			Profile: &pb.MatchProfile{
    				Name: "MatchProfile1",
    				Pools: []*pb.Pool{
    					{
    						Name: "low level",
    						DoubleRangeFilters: []*pb.DoubleRangeFilter{
    							{
    								DoubleArg: "level",
    								Max:       10,
    								Min:       0,
    							},
    						},
    					},
    				},
    			},
    		}
    
  • 中級場

    req := &pb.FetchMatchesRequest{
    			Config: &pb.FunctionConfig{
    				Host: "om-function.open-match-demo.svc.cluster.local",
    				Port: 50502,
    				Type: pb.FunctionConfig_GRPC,
    			},
    			Profile: &pb.MatchProfile{
    				Name: "MatchProfile2",
    				Pools: []*pb.Pool{
    					{
    						Name: "mid level",
    						DoubleRangeFilters: []*pb.DoubleRangeFilter{
    							{
    								DoubleArg: "level",
    								Max:       28,
    								Min:       10,
    							},
    						},
    					},
    				},
    			},
    		}
    
  • 高手場

    req := &pb.FetchMatchesRequest{
    			Config: &pb.FunctionConfig{
    				Host: "om-function.open-match-demo.svc.cluster.local",
    				Port: 50502,
    				Type: pb.FunctionConfig_GRPC,
    			},
    			Profile: &pb.MatchProfile{
    				Name: "MatchProfile3",
    				Pools: []*pb.Pool{
    					{
    						Name: "high level",
    						DoubleRangeFilters: []*pb.DoubleRangeFilter{
    							{
    								DoubleArg: "level",
    								Max:       30,
    								Min:       25,
    							},
    						},
    					},
    				},
    			},
    		}
    

MMF

特此告知 makeMatches() 是直接使用 Skill Based Match Function,我只修改 SearchFields “level” 的部分。說明一下程式內容,與官方 demo 不同的地方在於,建立了 computeQuality() 計算出每場配對的等差,並將這項資訊回填於 Match.Extensions["evaluation_input"],這個資訊會被我們建立的 default Evaluator 所採用,pb.DefaultEvaluationCriteria 內記載的 Score,將用來比較最適合的配對組合。

From: https://gist.github.com/syntxerror/c634dccb96c686b50fee67f801d6e671

func makeMatches(p *pb.MatchProfile, poolTickets map[string][]*pb.Ticket) ([]*pb.Match, error) {
	// Create a colletion to hold match proposals
	var matches []*pb.Match
	count := 0
	for {
		insufficientTickets := false
		// Create a collection to hold tickets selected for a match
		matchTickets := []*pb.Ticket{}
		for pool, tickets := range poolTickets {
			// Set flag if there are not enough tickets to create a match
			if len(tickets) < ticketsPerPoolPerMatch {
				insufficientTickets = true
				break
			}
			// Sort the tickets based on skill
			sort.Slice(tickets, func(i, j int) bool {
				return tickets[i].SearchFields.DoubleArgs["mmr"] < tickets[j].SearchFields.DoubleArgs["mmr"]
			})

			// Remove the Tickets from this pool and add to the match proposal.
			matchTickets = append(matchTickets, tickets[0:ticketsPerPoolPerMatch]...)
			poolTickets[pool] = tickets[ticketsPerPoolPerMatch:]
		}

		if insufficientTickets {
			break
		}
		// Compute the match quality/score
		matchQuality := computeQuality(matchTickets)
		evaluationInput, err := ptypes.MarshalAny(&pb.DefaultEvaluationCriteria{
			Score: matchQuality,
		})

		if err != nil {
			log.Printf("Failed to marshal DefaultEvaluationCriteria, got %s.", err.Error())
			return nil, fmt.Errorf("Failed to marshal DefaultEvaluationCriteria, got %w", err)
		}
		// Output the match quality for our sanity
		log.Printf("Quality for the generated match is %v", matchQuality)
		// Create a match proposal
		matches = append(matches, &pb.Match{
			MatchId:       fmt.Sprintf("profile-%v-time-%v-%v", p.GetName(), time.Now().Format("2006-01-02T15:04:05.00"), count),
			MatchProfile:  p.GetName(),
			MatchFunction: matchName,
			Tickets:       matchTickets,
			Extensions: map[string]*any.Any{
				"evaluation_input": evaluationInput,
			},
		})

		count++
	}

	return matches, nil
}

// Compute the quality as a difference in the highest and lowest player skill levels. This can be used to determine if the match is outside a given skill differential
func computeQuality(tickets []*pb.Ticket) float64 {
	quality := 0.0
	high := 0.0
	low := tickets[0].SearchFields.DoubleArgs["mmr"]
	for _, ticket := range tickets {
		if high < ticket.SearchFields.DoubleArgs["mmr"] {
			high = ticket.SearchFields.DoubleArgs["mmr"]
		}
		if low > ticket.SearchFields.DoubleArgs["mmr"] {
			low = ticket.SearchFields.DoubleArgs["mmr"]
		}
	}
	quality = high - low

	return quality
}

Reference


上一篇
Day9 重疊條件配對池 Overlapping MatchProfiles
下一篇
Day11 配對品質評估 Evaluator
系列文
徵坦補! 新手可! Open-Match 配對框架30

尚未有邦友留言

立即登入留言