open-match.dev/open-match@v1.8.1/examples/scale/scenarios/battleroyal/battleroyal.go (about) 1 // Copyright 2019 Google LLC 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package battleroyal 16 17 import ( 18 "fmt" 19 "io" 20 "math/rand" 21 "time" 22 23 "open-match.dev/open-match/pkg/pb" 24 ) 25 26 const ( 27 poolName = "all" 28 regionArg = "region" 29 ) 30 31 func battleRoyalRegionName(i int) string { 32 return fmt.Sprintf("region_%d", i) 33 } 34 35 func Scenario() *BattleRoyalScenario { 36 return &BattleRoyalScenario{ 37 regions: 20, 38 } 39 } 40 41 type BattleRoyalScenario struct { 42 regions int 43 } 44 45 func (b *BattleRoyalScenario) Profiles() []*pb.MatchProfile { 46 p := []*pb.MatchProfile{} 47 48 for i := 0; i < b.regions; i++ { 49 p = append(p, &pb.MatchProfile{ 50 Name: battleRoyalRegionName(i), 51 Pools: []*pb.Pool{ 52 { 53 Name: poolName, 54 StringEqualsFilters: []*pb.StringEqualsFilter{ 55 { 56 StringArg: regionArg, 57 Value: battleRoyalRegionName(i), 58 }, 59 }, 60 }, 61 }, 62 }) 63 } 64 return p 65 } 66 67 func (b *BattleRoyalScenario) Ticket() *pb.Ticket { 68 // Simple way to give an uneven distribution of region population. 69 a := rand.Intn(b.regions) + 1 70 r := rand.Intn(a) 71 72 return &pb.Ticket{ 73 SearchFields: &pb.SearchFields{ 74 StringArgs: map[string]string{ 75 regionArg: battleRoyalRegionName(r), 76 }, 77 }, 78 } 79 } 80 81 func (b *BattleRoyalScenario) Backfill() *pb.Backfill { 82 return nil 83 } 84 85 func (b *BattleRoyalScenario) MatchFunction(p *pb.MatchProfile, poolBackfills map[string][]*pb.Backfill, poolTickets map[string][]*pb.Ticket) ([]*pb.Match, error) { 86 const playersInMatch = 100 87 88 tickets := poolTickets[poolName] 89 var matches []*pb.Match 90 91 for i := 0; i+playersInMatch <= len(tickets); i += playersInMatch { 92 matches = append(matches, &pb.Match{ 93 MatchId: fmt.Sprintf("profile-%v-time-%v-%v", p.GetName(), time.Now().Format("2006-01-02T15:04:05.00"), len(matches)), 94 Tickets: tickets[i : i+playersInMatch], 95 MatchProfile: p.GetName(), 96 MatchFunction: "battleRoyal", 97 }) 98 } 99 100 return matches, nil 101 } 102 103 // fifoEvaluate accepts all matches which don't contain the same ticket as in a 104 // previously accepted match. Essentially first to claim the ticket wins. 105 func (b *BattleRoyalScenario) Evaluate(stream pb.Evaluator_EvaluateServer) error { 106 used := map[string]struct{}{} 107 108 // TODO: once the evaluator client supports sending and receiving at the 109 // same time, don't buffer, just send results immediately. 110 matchIDs := []string{} 111 112 outer: 113 for { 114 req, err := stream.Recv() 115 if err == io.EOF { 116 break 117 } 118 if err != nil { 119 return fmt.Errorf("Error reading evaluator input stream: %w", err) 120 } 121 122 m := req.GetMatch() 123 124 for _, t := range m.Tickets { 125 if _, ok := used[t.Id]; ok { 126 continue outer 127 } 128 } 129 130 for _, t := range m.Tickets { 131 used[t.Id] = struct{}{} 132 } 133 134 matchIDs = append(matchIDs, m.GetMatchId()) 135 } 136 137 for _, mID := range matchIDs { 138 err := stream.Send(&pb.EvaluateResponse{MatchId: mID}) 139 if err != nil { 140 return fmt.Errorf("Error sending evaluator output stream: %w", err) 141 } 142 } 143 144 return nil 145 }