Commit e2d3e592 authored by Joshua Gutow's avatar Joshua Gutow Committed by GitHub

game solver: Add generalized index & tests to position (#6086)

* game solver: Add generalized index & tests to position

* Fix tests

* Fix traceIndex function

---------
Co-authored-by: default avatarmergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
parent 8ab4ffe5
...@@ -18,10 +18,10 @@ func main() { ...@@ -18,10 +18,10 @@ func main() {
p.Attack() p.Attack()
p.Print(3) p.Print(3)
// Trace Position is 0000 Trace Depth is: 0 Trace Index is: 8 // GIN: 1 Trace Position is 0 Trace Depth is: 0 Trace Index is: 8
// Trace Position is 0000 Trace Depth is: 1 Trace Index is: 4 // GIN: 10 Trace Position is 0 Trace Depth is: 1 Trace Index is: 4
// Trace Position is 0010 Trace Depth is: 2 Trace Index is: 6 // GIN: 110 Trace Position is 10 Trace Depth is: 2 Trace Index is: 6
// Trace Position is 0100 Trace Depth is: 3 Trace Index is: 5 // GIN: 1100 Trace Position is 100 Trace Depth is: 3 Trace Index is: 5
// Example 2 // Example 2
// abcdefgh // abcdefgh
......
...@@ -3,36 +3,42 @@ package fault ...@@ -3,36 +3,42 @@ package fault
import "fmt" import "fmt"
// Position is a golang wrapper around the dispute game Position type. // Position is a golang wrapper around the dispute game Position type.
// Depth refers to how many bisection steps have occurred.
// IndexAtDepth refers to the path that the bisection has taken
// where 1 = goes right & 0 = goes left.
type Position struct { type Position struct {
Depth int depth int
IndexAtDepth int indexAtDepth int
} }
// TraceIndex calculates the what the index of the claim value func NewPosition(depth, indexAtDepth int) Position {
// would be inside the trace. return Position{depth, indexAtDepth}
}
func NewPositionFromGIndex(x uint64) Position {
depth := MSBIndex(x)
indexAtDepth := ^(1 << depth) & x
return NewPosition(depth, int(indexAtDepth))
}
func (p *Position) Depth() int {
return p.depth
}
func (p *Position) IndexAtDepth() int {
return p.indexAtDepth
}
// TraceIndex calculates the what the index of the claim value would be inside the trace.
// It is equivalent to going right until the final depth has been reached.
func (p *Position) TraceIndex(maxDepth int) int { func (p *Position) TraceIndex(maxDepth int) int {
lo := 0 // When we go right, we do a shift left and set the bottom bit to be 1.
hi := 1 << maxDepth // To do this in a single step, do all the shifts at once & or in all 1s for the bottom bits.
mid := hi rd := maxDepth - p.depth
path := p.IndexAtDepth return p.indexAtDepth<<rd | ((1 << rd) - 1)
for i := p.Depth - 1; i >= 0; i-- {
mid = (lo + hi) / 2
mask := 1 << i
if path&mask == mask {
lo = mid
} else {
hi = mid
}
}
return mid
} }
// move goes to the left or right child.
func (p *Position) move(right bool) { func (p *Position) move(right bool) {
p.Depth++ p.depth++
p.IndexAtDepth = (p.IndexAtDepth << 1) | boolToInt(right) p.indexAtDepth = (p.indexAtDepth << 1) | boolToInt(right)
} }
func boolToInt(b bool) int { func boolToInt(b bool) int {
...@@ -43,15 +49,18 @@ func boolToInt(b bool) int { ...@@ -43,15 +49,18 @@ func boolToInt(b bool) int {
} }
} }
// parent moves up to the parent.
func (p *Position) parent() { func (p *Position) parent() {
p.Depth-- p.depth--
p.IndexAtDepth = p.IndexAtDepth >> 1 p.indexAtDepth = p.indexAtDepth >> 1
} }
// Attack moves this position to a position to the left which disagrees with this position.
func (p *Position) Attack() { func (p *Position) Attack() {
p.move(false) p.move(false)
} }
// Defend moves this position to the right which agrees with this position. Note:
func (p *Position) Defend() { func (p *Position) Defend() {
p.parent() p.parent()
p.move(true) p.move(true)
...@@ -59,5 +68,21 @@ func (p *Position) Defend() { ...@@ -59,5 +68,21 @@ func (p *Position) Defend() {
} }
func (p *Position) Print(maxDepth int) { func (p *Position) Print(maxDepth int) {
fmt.Printf("Trace Position is %04b\tTrace Depth is: %d\tTrace Index is: %d\n", p.IndexAtDepth, p.Depth, p.TraceIndex(maxDepth)) fmt.Printf("GIN: %4b\tTrace Position is %4b\tTrace Depth is: %d\tTrace Index is: %d\n", p.ToGIndex(), p.indexAtDepth, p.depth, p.TraceIndex(maxDepth))
}
func (p *Position) ToGIndex() uint64 {
return uint64(1<<p.depth | p.indexAtDepth)
}
// MSBIndex returns the index of the most significant bit
func MSBIndex(x uint64) int {
if x == 0 {
return 0
}
out := 0
for ; x != 0; out++ {
x = x >> 1
}
return out - 1
} }
package fault package fault
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestMSBIndex(t *testing.T) {
tests := []struct {
input uint64
expected int
}{
{0, 0},
{1, 0},
{2, 1},
{4, 2},
{8, 3},
{16, 4},
{255, 7},
{1024, 10},
{18446744073709551615, 63},
}
for _, test := range tests {
result := MSBIndex(test.input)
if result != test.expected {
t.Errorf("MSBIndex(%d) expected %d, but got %d", test.input, test.expected, result)
}
}
}
type testNodeInfo struct {
GIndex uint64
Depth int
IndexAtDepth int
TraceIndex int
}
var treeNodesMaxDepth4 = []testNodeInfo{
{GIndex: 1, Depth: 0, IndexAtDepth: 0, TraceIndex: 15},
{GIndex: 2, Depth: 1, IndexAtDepth: 0, TraceIndex: 7},
{GIndex: 3, Depth: 1, IndexAtDepth: 1, TraceIndex: 15},
{GIndex: 4, Depth: 2, IndexAtDepth: 0, TraceIndex: 3},
{GIndex: 5, Depth: 2, IndexAtDepth: 1, TraceIndex: 7},
{GIndex: 6, Depth: 2, IndexAtDepth: 2, TraceIndex: 11},
{GIndex: 7, Depth: 2, IndexAtDepth: 3, TraceIndex: 15},
{GIndex: 8, Depth: 3, IndexAtDepth: 0, TraceIndex: 1},
{GIndex: 9, Depth: 3, IndexAtDepth: 1, TraceIndex: 3},
{GIndex: 10, Depth: 3, IndexAtDepth: 2, TraceIndex: 5},
{GIndex: 11, Depth: 3, IndexAtDepth: 3, TraceIndex: 7},
{GIndex: 12, Depth: 3, IndexAtDepth: 4, TraceIndex: 9},
{GIndex: 13, Depth: 3, IndexAtDepth: 5, TraceIndex: 11},
{GIndex: 14, Depth: 3, IndexAtDepth: 6, TraceIndex: 13},
{GIndex: 15, Depth: 3, IndexAtDepth: 7, TraceIndex: 15},
{GIndex: 16, Depth: 4, IndexAtDepth: 0, TraceIndex: 0},
{GIndex: 17, Depth: 4, IndexAtDepth: 1, TraceIndex: 1},
{GIndex: 18, Depth: 4, IndexAtDepth: 2, TraceIndex: 2},
{GIndex: 19, Depth: 4, IndexAtDepth: 3, TraceIndex: 3},
{GIndex: 20, Depth: 4, IndexAtDepth: 4, TraceIndex: 4},
{GIndex: 21, Depth: 4, IndexAtDepth: 5, TraceIndex: 5},
{GIndex: 22, Depth: 4, IndexAtDepth: 6, TraceIndex: 6},
{GIndex: 23, Depth: 4, IndexAtDepth: 7, TraceIndex: 7},
{GIndex: 24, Depth: 4, IndexAtDepth: 8, TraceIndex: 8},
{GIndex: 25, Depth: 4, IndexAtDepth: 9, TraceIndex: 9},
{GIndex: 26, Depth: 4, IndexAtDepth: 10, TraceIndex: 10},
{GIndex: 27, Depth: 4, IndexAtDepth: 11, TraceIndex: 11},
{GIndex: 28, Depth: 4, IndexAtDepth: 12, TraceIndex: 12},
{GIndex: 29, Depth: 4, IndexAtDepth: 13, TraceIndex: 13},
{GIndex: 30, Depth: 4, IndexAtDepth: 14, TraceIndex: 14},
{GIndex: 31, Depth: 4, IndexAtDepth: 15, TraceIndex: 15},
}
// TestGINConversions does To & From the generalized index on the treeNodesMaxDepth4 data
func TestGINConversions(t *testing.T) {
for _, test := range treeNodesMaxDepth4 {
from := NewPositionFromGIndex(test.GIndex)
pos := NewPosition(test.Depth, test.IndexAtDepth)
require.Equal(t, pos, from)
to := pos.ToGIndex()
require.Equal(t, test.GIndex, to)
}
}
// TestTraceIndex creates the position & then tests the trace index function on the treeNodesMaxDepth4 data
func TestTraceIndex(t *testing.T) {
for _, test := range treeNodesMaxDepth4 {
pos := NewPosition(test.Depth, test.IndexAtDepth)
result := pos.TraceIndex(4)
require.Equal(t, test.TraceIndex, result)
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment