Skip to content

Commit

Permalink
Implemented a way for the adjudicator to notify the client of limitat…
Browse files Browse the repository at this point in the history
…ions in the options structure. Used this to let Build and Disband orders notify the client about how many of them are allowed, so that the client an stop presenting options that aren't actually valid.
  • Loading branch information
zond committed May 15, 2020
1 parent 911111a commit f148d1a
Show file tree
Hide file tree
Showing 8 changed files with 336 additions and 29 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/zond/godip
go 1.14

require (
github.com/davecgh/go-spew v1.1.1
github.com/gorilla/mux v1.7.4
google.golang.org/appengine v1.6.5
)
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc=
Expand Down
60 changes: 57 additions & 3 deletions godip.go
Original file line number Diff line number Diff line change
Expand Up @@ -322,18 +322,72 @@ type SrcProvince Province

type OptionValue interface{}

type FilteredOptionValue struct {
Filter string
Value OptionValue
}

/*
Options defines a tree of valid orders for a given situation
*/
type Options map[OptionValue]Options

func (self Options) BubbleFilters() Options {
_, result := self.bubbleFiltersHelper(false)
return result
}

func (self Options) bubbleFiltersHelper(bubbleSelf bool) (string, Options) {
lastFilter := ""
filters := map[string]bool{}
bubbledChildren := Options{}
for k, v := range self {
if filtered, ok := k.(FilteredOptionValue); ok {
lastFilter = filtered.Filter
filters[lastFilter] = true
bubbledChildren[k] = v
} else {
childCommonFilter, newChild := v.bubbleFiltersHelper(true)
lastFilter = childCommonFilter
filters[lastFilter] = true
if childCommonFilter == "" {
bubbledChildren[k] = v
} else {
bubbledChildren[FilteredOptionValue{
Filter: childCommonFilter,
Value: k,
}] = newChild
}
}
}
if !bubbleSelf || lastFilter == "" || len(filters) > 1 {
return "", bubbledChildren
}
bubbledSelf := Options{}
for k, v := range bubbledChildren {
filtered := k.(FilteredOptionValue)
bubbledSelf[filtered.Value] = v
}
return lastFilter, bubbledSelf
}

func (self Options) MarshalJSON() ([]byte, error) {
repl := map[string]interface{}{}
for k, v := range self {
repl[fmt.Sprint(k)] = map[string]interface{}{
"Type": reflect.ValueOf(k).Type().Name(),
for k, v := range self.BubbleFilters() {
kVal := reflect.ValueOf(k)
filter := ""
if kVal.Type() == reflect.TypeOf(FilteredOptionValue{}) {
filter = kVal.FieldByName("Filter").String()
kVal = kVal.FieldByName("Value")
}
val := map[string]interface{}{
"Type": kVal.Type().Name(),
"Next": v,
}
if filter != "" {
val["Filter"] = filter
}
repl[fmt.Sprint(kVal.Interface())] = val
}
return json.Marshal(repl)
}
Expand Down
195 changes: 195 additions & 0 deletions godip_test.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,205 @@
package godip

import (
"reflect"
"sort"
"testing"
)

func TestOptionsBubbleFilters(t *testing.T) {
for _, tc := range []struct {
opts Options
bubbled Options
}{
{
opts: Options{
Province("spa"): Options{
OrderType("Build"): Options{
FilteredOptionValue{
Filter: "MAX:Build:1",
Value: UnitType("Army"),
}: Options{
SrcProvince("spa"): Options{},
},
},
},
},
bubbled: Options{
FilteredOptionValue{
Filter: "MAX:Build:1",
Value: Province("spa"),
}: Options{
OrderType("Build"): Options{
UnitType("Army"): Options{
SrcProvince("spa"): Options{},
},
},
},
},
},
{
opts: Options{
Province("lon"): Options{
OrderType("Build"): Options{
FilteredOptionValue{
Filter: "MAX:Build:1",
Value: UnitType("Army"),
}: Options{
SrcProvince("lon"): Options{},
},
},
},
Province("spa"): Options{
OrderType("Build"): Options{
FilteredOptionValue{
Filter: "MAX:Build:1",
Value: UnitType("Army"),
}: Options{
SrcProvince("spa"): Options{},
},
},
},
},
bubbled: Options{
FilteredOptionValue{
Filter: "MAX:Build:1",
Value: Province("spa"),
}: Options{
OrderType("Build"): Options{
UnitType("Army"): Options{
SrcProvince("spa"): Options{},
},
},
},
FilteredOptionValue{
Filter: "MAX:Build:1",
Value: Province("lon"),
}: Options{
OrderType("Build"): Options{
UnitType("Army"): Options{
SrcProvince("lon"): Options{},
},
},
},
},
},
{
opts: Options{
Province("spa"): Options{
OrderType("Build"): Options{
FilteredOptionValue{
Filter: "MAX:Build:1",
Value: UnitType("Army"),
}: Options{
SrcProvince("spa"): Options{},
},
},
OrderType("Disband"): Options{
FilteredOptionValue{
Filter: "MAX:Disband:1",
Value: UnitType("Army"),
}: Options{
SrcProvince("spa"): Options{},
},
},
},
},
bubbled: Options{
Province("spa"): Options{
OrderType("Build"): Options{
FilteredOptionValue{
Filter: "MAX:Build:1",
Value: UnitType("Army"),
}: Options{
SrcProvince("spa"): Options{},
},
},
OrderType("Disband"): Options{
FilteredOptionValue{
Filter: "MAX:Disband:1",
Value: UnitType("Army"),
}: Options{
SrcProvince("spa"): Options{},
},
},
},
},
},
{
opts: Options{
Province("spa"): Options{
OrderType("Build"): Options{
FilteredOptionValue{
Filter: "MAX:Build:1",
Value: UnitType("Army"),
}: Options{
SrcProvince("spa"): Options{},
},
FilteredOptionValue{
Filter: "MAX:Build:1",
Value: UnitType("Fleet"),
}: Options{
SrcProvince("spa"): Options{},
},
},
},
},
bubbled: Options{
FilteredOptionValue{
Filter: "MAX:Build:1",
Value: Province("spa"),
}: Options{
OrderType("Build"): Options{
UnitType("Army"): Options{
SrcProvince("spa"): Options{},
},
UnitType("Fleet"): Options{
SrcProvince("spa"): Options{},
},
},
},
},
},
{
opts: Options{
Province("spa"): Options{
OrderType("Build"): Options{
FilteredOptionValue{
Filter: "MAX:Build:1",
Value: UnitType("Army"),
}: Options{
SrcProvince("spa"): Options{},
},
UnitType("Fleet"): Options{
SrcProvince("spa"): Options{},
},
},
},
},
bubbled: Options{
Province("spa"): Options{
OrderType("Build"): Options{
FilteredOptionValue{
Filter: "MAX:Build:1",
Value: UnitType("Army"),
}: Options{
SrcProvince("spa"): Options{},
},
UnitType("Fleet"): Options{
SrcProvince("spa"): Options{},
},
},
},
},
},
} {
bubbled := tc.opts.BubbleFilters()
if !reflect.DeepEqual(bubbled, tc.bubbled) {
t.Errorf("Got %+v, wanted %+v", bubbled, tc.bubbled)
}
}
}

func TestNationSorting(t *testing.T) {
nations := Nations{
Austria,
Expand Down
20 changes: 14 additions & 6 deletions orders/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,26 +148,34 @@ func (self *build) Options(v godip.Validator, nation godip.Nation, src godip.Pro
if _, _, ok = v.Unit(src); ok {
return
}
var wrapperFunc func(godip.OptionValue) godip.FilteredOptionValue
if _, _, balance := AdjustmentStatus(v, me); balance < 1 {
return
} else {
wrapperFunc = func(val godip.OptionValue) godip.FilteredOptionValue {
return godip.FilteredOptionValue{
Filter: fmt.Sprintf("MAX:%v:%v", godip.Build, balance),
Value: val,
}
}
}
if v.Graph().Flags(src)[godip.Land] || v.Graph().Flags(src.Super())[godip.Land] {
if result == nil {
result = godip.Options{}
}
if result[godip.Army] == nil {
result[godip.Army] = godip.Options{}
if result[wrapperFunc(godip.Army)] == nil {
result[wrapperFunc(godip.Army)] = godip.Options{}
}
result[godip.Army][godip.SrcProvince(src.Super())] = nil
result[wrapperFunc(godip.Army)][godip.SrcProvince(src.Super())] = nil
}
if v.Graph().Flags(src)[godip.Sea] || v.Graph().Flags(src.Super())[godip.Sea] {
if result == nil {
result = godip.Options{}
}
if result[godip.Fleet] == nil {
result[godip.Fleet] = godip.Options{}
if result[wrapperFunc(godip.Fleet)] == nil {
result[wrapperFunc(godip.Fleet)] = godip.Options{}
}
result[godip.Fleet][godip.SrcProvince(src)] = nil
result[wrapperFunc(godip.Fleet)][godip.SrcProvince(src)] = nil
}
return
}
Expand Down
5 changes: 4 additions & 1 deletion orders/disband.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,10 @@ func (self *disband) Options(v godip.Validator, nation godip.Nation, src godip.P
if unit.Nation == nation {
if _, _, balance := AdjustmentStatus(v, unit.Nation); balance < 0 {
result = godip.Options{
godip.SrcProvince(actualSrc): nil,
godip.FilteredOptionValue{
Filter: fmt.Sprintf("MAX:%v:%v", godip.Disband, -balance),
Value: godip.SrcProvince(actualSrc),
}: nil,
}
}
}
Expand Down
38 changes: 34 additions & 4 deletions variants/classical/classical_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -400,15 +400,45 @@ func TestSTPBuildOptions(t *testing.T) {
judge.Next()
opts := judge.Phase().Options(judge, godip.Russia)
tst.AssertNoOpt(t, opts, []string{"stp"})
tst.AssertOpt(t, opts, []string{"stp/nc", "Build", "Fleet", "stp/nc"})
tst.AssertOpt(t, opts, []string{"stp/sc", "Build", "Fleet", "stp/sc"})
filter := "MAX:Build:1"
tst.AssertFilteredOpt(t, opts, filter, []string{"stp/nc", "Build", "Fleet", "stp/nc"})
tst.AssertFilteredOpt(t, opts, filter, []string{"stp/sc", "Build", "Fleet", "stp/sc"})
tst.AssertNoOpt(t, opts, []string{"stp/sc", "Build", "Fleet", "stp"})
tst.AssertOpt(t, opts, []string{"stp/nc", "Build", "Army", "stp"})
tst.AssertOpt(t, opts, []string{"stp/sc", "Build", "Army", "stp"})
tst.AssertFilteredOpt(t, opts, filter, []string{"stp/nc", "Build", "Army", "stp"})
tst.AssertFilteredOpt(t, opts, filter, []string{"stp/sc", "Build", "Army", "stp"})
tst.AssertNoOpt(t, opts, []string{"stp/sc", "Build", "Army", "stp/nc"})
tst.AssertNoOpt(t, opts, []string{"stp/sc", "Build", "Army", "stp/sc"})
}

func TestFilteredOptions(t *testing.T) {
judge := startState(t)
judge.SetOrder("stp", orders.Move("stp/sc", "fin"))
judge.SetOrder("sev", orders.Move("sev", "rum"))
judge.SetOrder("war", orders.Move("war", "sil"))
judge.SetOrder("vie", orders.Move("vie", "boh"))
judge.Next()
judge.Next()
judge.SetOrder("fin", orders.Move("fin", "swe"))
judge.SetOrder("boh", orders.Move("boh", "mun"))
judge.SetOrder("sil", orders.SupportMove("sil", "boh", "mun"))
judge.Next()
judge.SetOrder("mun", orders.Move("mun", "ruh"))
judge.Next()
opts := judge.Phase().Options(judge, godip.Russia)
filter := "MAX:Build:2"
tst.AssertFilteredOpt(t, opts, filter, []string{"stp/nc", "Build", "Fleet", "stp/nc"})
tst.AssertFilteredOpt(t, opts, filter, []string{"stp/sc", "Build", "Fleet", "stp/sc"})
tst.AssertFilteredOpt(t, opts, filter, []string{"stp/nc", "Build", "Army", "stp"})
tst.AssertFilteredOpt(t, opts, filter, []string{"stp/sc", "Build", "Army", "stp"})
tst.AssertFilteredOpt(t, opts, filter, []string{"sev", "Build", "Fleet", "sev"})
tst.AssertFilteredOpt(t, opts, filter, []string{"sev", "Build", "Army", "sev"})
opts = judge.Phase().Options(judge, godip.Germany)
filter = "MAX:Disband:1"
tst.AssertFilteredOpt(t, opts, filter, []string{"kie", "Disband", "kie"})
tst.AssertFilteredOpt(t, opts, filter, []string{"ber", "Disband", "ber"})
tst.AssertFilteredOpt(t, opts, filter, []string{"ruh", "Disband", "ruh"})
}

func TestSupportSTPOpts(t *testing.T) {
judge := startState(t)
opts := judge.Phase().Options(judge, godip.Russia)
Expand Down
Loading

0 comments on commit f148d1a

Please sign in to comment.