diff --git a/utils/arrangement/arrangement.go b/utils/arrangement/arrangement.go index 0f91d5fd..188e4cb0 100644 --- a/utils/arrangement/arrangement.go +++ b/utils/arrangement/arrangement.go @@ -8,9 +8,11 @@ import ( // NewArrangement 创建一个新的编排 func NewArrangement[ID comparable, AreaInfo any](options ...Option[ID, AreaInfo]) *Arrangement[ID, AreaInfo] { arrangement := &Arrangement[ID, AreaInfo]{ - items: map[ID]Item[ID]{}, - fixed: map[ID]ItemFixedAreaHandle[AreaInfo]{}, - priority: map[ID][]ItemPriorityHandle[ID, AreaInfo]{}, + items: map[ID]Item[ID]{}, + fixed: map[ID]ItemFixedAreaHandle[AreaInfo]{}, + priority: map[ID][]ItemPriorityHandle[ID, AreaInfo]{}, + itemNotAllow: map[ID][]ItemNotAllowVerifyHandle[ID, AreaInfo]{}, + threshold: 10, } for _, option := range options { option(arrangement) @@ -23,11 +25,12 @@ func NewArrangement[ID comparable, AreaInfo any](options ...Option[ID, AreaInfo] // - 目前我能想到的用途只有我的过往经历:排课 // - 如果是在游戏领域,或许适用于多人小队匹配编排等类似情况 type Arrangement[ID comparable, AreaInfo any] struct { - areas []*Area[ID, AreaInfo] // 所有的编排区域 - items map[ID]Item[ID] // 所有的成员 - fixed map[ID]ItemFixedAreaHandle[AreaInfo] // 固定编排区域的成员 - priority map[ID][]ItemPriorityHandle[ID, AreaInfo] // 成员的优先级函数 - threshold int // 重试次数阈值 + areas []*Area[ID, AreaInfo] // 所有的编排区域 + items map[ID]Item[ID] // 所有的成员 + fixed map[ID]ItemFixedAreaHandle[AreaInfo] // 固定编排区域的成员 + priority map[ID][]ItemPriorityHandle[ID, AreaInfo] // 成员的优先级函数 + itemNotAllow map[ID][]ItemNotAllowVerifyHandle[ID, AreaInfo] // 成员的不允的编排区域检测函数 + threshold int // 重试次数阈值 constraintHandles []ConstraintHandle[ID, AreaInfo] conflictHandles []ConflictHandle[ID, AreaInfo] @@ -116,7 +119,7 @@ func (slf *Arrangement[ID, AreaInfo]) Arrange() (areas []*Area[ID, AreaInfo], no } if area == nil { // 无法通过优先级找到合适的编排区域 - for i, a := range slf.areas { + for i, a := range editor.GetAreasWithScoreDesc(current) { if _, exist := itemAreaPriority[current.GetID()][i]; exist { continue } @@ -158,6 +161,17 @@ func (slf *Arrangement[ID, AreaInfo]) Arrange() (areas []*Area[ID, AreaInfo], no // try 尝试将 current 编排到 a 中 func (slf *Arrangement[ID, AreaInfo]) try(editor *Editor[ID, AreaInfo], a *Area[ID, AreaInfo], current Item[ID]) bool { + allow := true + for _, verify := range slf.itemNotAllow[current.GetID()] { + if verify(a.GetAreaInfo(), current) { + allow = false + break + } + } + if !allow { + return false + } + err, conflictItems, allow := a.IsAllow(current) if !allow { if err != nil { diff --git a/utils/arrangement/arrangement_test.go b/utils/arrangement/arrangement_test.go index 9cce3b7b..576c435a 100644 --- a/utils/arrangement/arrangement_test.go +++ b/utils/arrangement/arrangement_test.go @@ -48,7 +48,7 @@ func TestArrangement_Arrange(t *testing.T) { a.AddItem(&Player{ID: i + 1}) } - res, no := a.Arrange(50) + res, no := a.Arrange() for _, area := range res { var str = fmt.Sprintf("area %d: ", area.GetAreaInfo().ID) for id := range area.GetItems() { diff --git a/utils/arrangement/editor.go b/utils/arrangement/editor.go index 1a4e9f2b..3a80bd35 100644 --- a/utils/arrangement/editor.go +++ b/utils/arrangement/editor.go @@ -3,9 +3,10 @@ package arrangement import ( "github.com/kercylan98/minotaur/utils/hash" "github.com/kercylan98/minotaur/utils/slice" + "sort" ) -// Editor 编排器 +// Editor 提供了大量辅助函数的编辑器 type Editor[ID comparable, AreaInfo any] struct { a *Arrangement[ID, AreaInfo] pending []Item[ID] @@ -44,6 +45,24 @@ func (slf *Editor[ID, AreaInfo]) GetAreas() []*Area[ID, AreaInfo] { return slice.Copy(slf.a.areas) } +// GetAreasWithScoreAsc 获取所有的编排区域,并按照分数升序排序 +func (slf *Editor[ID, AreaInfo]) GetAreasWithScoreAsc(extra ...Item[ID]) []*Area[ID, AreaInfo] { + areas := slice.Copy(slf.a.areas) + sort.Slice(areas, func(i, j int) bool { + return areas[i].GetScore(extra...) < areas[j].GetScore(extra...) + }) + return areas +} + +// GetAreasWithScoreDesc 获取所有的编排区域,并按照分数降序排序 +func (slf *Editor[ID, AreaInfo]) GetAreasWithScoreDesc(extra ...Item[ID]) []*Area[ID, AreaInfo] { + areas := slice.Copy(slf.a.areas) + sort.Slice(areas, func(i, j int) bool { + return areas[i].GetScore(extra...) > areas[j].GetScore(extra...) + }) + return areas +} + // GetRetryCount 获取重试次数 func (slf *Editor[ID, AreaInfo]) GetRetryCount() int { return slf.retryCount @@ -53,3 +72,93 @@ func (slf *Editor[ID, AreaInfo]) GetRetryCount() int { func (slf *Editor[ID, AreaInfo]) GetThresholdProgressRate() float64 { return float64(slf.retryCount) / float64(slf.a.threshold) } + +// GetAllowAreas 获取允许的编排区域 +func (slf *Editor[ID, AreaInfo]) GetAllowAreas(item Item[ID]) []*Area[ID, AreaInfo] { + var areas []*Area[ID, AreaInfo] + for _, area := range slf.a.areas { + if _, _, allow := area.IsAllow(item); allow { + areas = append(areas, area) + } + } + return areas +} + +// GetNoAllowAreas 获取不允许的编排区域 +func (slf *Editor[ID, AreaInfo]) GetNoAllowAreas(item Item[ID]) []*Area[ID, AreaInfo] { + var areas []*Area[ID, AreaInfo] + for _, area := range slf.a.areas { + if _, _, allow := area.IsAllow(item); !allow { + areas = append(areas, area) + } + } + return areas +} + +// GetBestAllowArea 获取最佳的允许的编排区域,如果不存在,则返回 nil +func (slf *Editor[ID, AreaInfo]) GetBestAllowArea(item Item[ID]) *Area[ID, AreaInfo] { + var areas = slf.GetAllowAreas(item) + if len(areas) == 0 { + return nil + } + var bestArea = areas[0] + var score = bestArea.GetScore(item) + for _, area := range areas { + if area.GetScore(item) > score { + bestArea = area + score = area.GetScore(item) + } + } + return bestArea +} + +// GetBestNoAllowArea 获取最佳的不允许的编排区域,如果不存在,则返回 nil +func (slf *Editor[ID, AreaInfo]) GetBestNoAllowArea(item Item[ID]) *Area[ID, AreaInfo] { + var areas = slf.GetNoAllowAreas(item) + if len(areas) == 0 { + return nil + } + var bestArea = areas[0] + var score = bestArea.GetScore(item) + for _, area := range areas { + if area.GetScore(item) > score { + bestArea = area + score = area.GetScore(item) + } + } + return bestArea +} + +// GetWorstAllowArea 获取最差的允许的编排区域,如果不存在,则返回 nil +func (slf *Editor[ID, AreaInfo]) GetWorstAllowArea(item Item[ID]) *Area[ID, AreaInfo] { + var areas = slf.GetAllowAreas(item) + if len(areas) == 0 { + return nil + } + var worstArea = areas[0] + var score = worstArea.GetScore(item) + for _, area := range areas { + if area.GetScore(item) < score { + worstArea = area + score = area.GetScore(item) + } + } + return worstArea +} + +// GetWorstNoAllowArea 获取最差的不允许的编排区域,如果不存在,则返回 nil +func (slf *Editor[ID, AreaInfo]) GetWorstNoAllowArea(item Item[ID]) *Area[ID, AreaInfo] { + var areas = slf.GetNoAllowAreas(item) + if len(areas) == 0 { + return nil + } + var worstArea = areas[0] + var score = worstArea.GetScore(item) + for _, area := range areas { + if area.GetScore(item) < score { + worstArea = area + score = area.GetScore(item) + } + } + return worstArea +} diff --git a/utils/arrangement/item_options.go b/utils/arrangement/item_options.go index fba7f4b2..f0115e23 100644 --- a/utils/arrangement/item_options.go +++ b/utils/arrangement/item_options.go @@ -4,8 +4,9 @@ package arrangement type ItemOption[ID comparable, AreaInfo any] func(arrangement *Arrangement[ID, AreaInfo], item Item[ID]) type ( - ItemFixedAreaHandle[AreaInfo any] func(areaInfo AreaInfo) bool - ItemPriorityHandle[ID comparable, AreaInfo any] func(areaInfo AreaInfo, item Item[ID]) float64 + ItemFixedAreaHandle[AreaInfo any] func(areaInfo AreaInfo) bool + ItemPriorityHandle[ID comparable, AreaInfo any] func(areaInfo AreaInfo, item Item[ID]) float64 + ItemNotAllowVerifyHandle[ID comparable, AreaInfo any] func(areaInfo AreaInfo, item Item[ID]) bool ) // WithItemFixed 设置成员的固定编排区域 @@ -21,3 +22,10 @@ func WithItemPriority[ID comparable, AreaInfo any](priority ItemPriorityHandle[I arrangement.priority[item.GetID()] = append(arrangement.priority[item.GetID()], priority) } } + +// WithItemNotAllow 设置成员不允许的编排区域 +func WithItemNotAllow[ID comparable, AreaInfo any](verify ItemNotAllowVerifyHandle[ID, AreaInfo]) ItemOption[ID, AreaInfo] { + return func(arrangement *Arrangement[ID, AreaInfo], item Item[ID]) { + arrangement.itemNotAllow[item.GetID()] = append(arrangement.itemNotAllow[item.GetID()], verify) + } +}