From c8c51b1a3cc728ae7caf992fc0738dab8cf3a79c Mon Sep 17 00:00:00 2001 From: ia Date: Sun, 17 Jun 2018 01:07:25 +0200 Subject: [PATCH] all: gofmt Run standard gofmt command on project root. - go version go1.10.3 darwin/amd64 Signed-off-by: ia --- src/ai/ai.go | 110 +- src/ai/mcts.go | 296 ++-- src/ai/minimax.go | 70 +- src/ai/pq.go | 54 +- src/ai/pq_test.go | 78 +- src/ai/tree_search.go | 17 +- src/cmd/benchmark/benchmark_analysis.go | 84 +- src/cmd/benchmark/benchmark_play.go | 245 ++-- src/cmd/benchmark/gen_benchmark_play.go | 101 +- src/cmd/dpickup/dpickup.go | 190 +-- src/cmd/main/main.go | 448 +++--- src/cmd/run/run.go | 87 +- src/deck/deck.go | 255 ++-- src/deck/gen.go | 26 +- src/deck/gen_test.go | 35 +- src/euchre/determinization_test.go | 324 +++-- src/euchre/euchre.go | 789 +++++----- src/euchre/euchre_mcts_test.go | 84 +- src/euchre/gen.go | 16 +- src/euchre/logic.go | 323 ++--- src/euchre/logic_test.go | 1736 +++++++++++------------ src/euchre/util.go | 47 +- src/player/player.go | 158 +-- src/player/player_test.go | 302 ++-- src/player/rand.go | 79 +- src/player/rule.go | 815 ++++++----- src/player/rule_player_test.go | 584 ++++---- src/player/smart.go | 445 +++--- 28 files changed, 3824 insertions(+), 3974 deletions(-) diff --git a/src/ai/ai.go b/src/ai/ai.go index 333791a..9ce4090 100644 --- a/src/ai/ai.go +++ b/src/ai/ai.go @@ -1,26 +1,23 @@ package ai import ( - "math/rand" - "time" + "math/rand" + "time" ) /* * Taken and inspired by appliedgo.net/perceptron */ - /* * The Input type to the perceptron breaks down any struct or type into a vector * of features which function as the inputs to the perceptron. The features are * either there or aren't, so they only take on a value of 1 or 0. */ type Input interface { - Features() []int + Features() []int } - - /* * A simple perceptron type that acts in the form w * x + b > 0. This means that * this Perceptron acts as a binary classifier with its given weight and bias @@ -29,11 +26,10 @@ type Input interface { * included together. */ type Perceptron struct { - weights []float32 - bias float32 + weights []float32 + bias float32 } - /* * Create a random perceptron that has n weights. The weight numbers are * randomly given to start between the given numbers. @@ -47,20 +43,19 @@ type Perceptron struct { * A pointer to a Perceptron with n random weights and a random bias. */ func CreatePerceptron(n int, low, high float32) *Perceptron { - r := rand.New(rand.NewSource(time.Now().UnixNano())) + r := rand.New(rand.NewSource(time.Now().UnixNano())) - weights := make([]float32, n, n) - for i := range weights { - weights[i] = r.Float32() * (high - low) + low - } + weights := make([]float32, n, n) + for i := range weights { + weights[i] = r.Float32()*(high-low) + low + } - return &Perceptron { - weights, - r.Float32() * (high - low) + low, - } + return &Perceptron{ + weights, + r.Float32()*(high-low) + low, + } } - /* * Process the given input and return a classification of either 1 or 0. * @@ -72,19 +67,18 @@ func CreatePerceptron(n int, low, high float32) *Perceptron { * otherwise. */ func (p *Perceptron) Process(input Input) int { - s := p.bias - for i, input := range input.Features() { - s += float32(input) * p.weights[i] - } + s := p.bias + for i, input := range input.Features() { + s += float32(input) * p.weights[i] + } - if s > 0 { - return 1 - } + if s > 0 { + return 1 + } - return 0 + return 0 } - /* * Based on an array of inputs and a parallel array of expected answers, train * the perceptron at the given rate. @@ -95,15 +89,14 @@ func (p *Perceptron) Process(input Input) int { * rate: The rate at which the Perceptron learns. */ func (p *Perceptron) Train(inputs []Input, expected []int, rate float32) { - for i, input := range inputs { - actual := p.Process(input) - del := expected[i] - actual + for i, input := range inputs { + actual := p.Process(input) + del := expected[i] - actual - p.adjust(input, del, rate) - } + p.adjust(input, del, rate) + } } - /* * Train the perceptron until a certain level of convergence. Basically this * method keeps training the perceptron on the same data until the percent of @@ -125,32 +118,31 @@ func (p *Perceptron) Train(inputs []Input, expected []int, rate float32) { * otherwise. */ func (p *Perceptron) Converge(inputs []Input, expected []int, - rate, percent float32, maxIter int) bool { - thres := int(percent * float32(len(inputs))) + rate, percent float32, maxIter int) bool { + thres := int(percent * float32(len(inputs))) - iter := 0 - wrong := thres + 1 - for wrong > thres && iter < maxIter { - wrong = 0 + iter := 0 + wrong := thres + 1 + for wrong > thres && iter < maxIter { + wrong = 0 - for i, input := range inputs { - actual := p.Process(input) - del := expected[i] - actual + for i, input := range inputs { + actual := p.Process(input) + del := expected[i] - actual - p.adjust(input, del, rate) + p.adjust(input, del, rate) - if del != 0 { - wrong++ - } - } + if del != 0 { + wrong++ + } + } - iter++ - } + iter++ + } - return wrong <= thres + return wrong <= thres } - /* * Get the weights for a Perceptron. * @@ -158,10 +150,9 @@ func (p *Perceptron) Converge(inputs []Input, expected []int, * The weights of a Perceptron. */ func (p *Perceptron) Weights() []float32 { - return p.weights + return p.weights } - /* * Get the bias of a Perceptron. * @@ -169,10 +160,9 @@ func (p *Perceptron) Weights() []float32 { * The bias field of a Perceptron. */ func (p *Perceptron) Bias() float32 { - return p.bias + return p.bias } - /* * Adjust a perceptron based on a given input, difference and learning rate. The * input is what is given to the perceptron, delta is either 1, 0, or -1. It @@ -189,9 +179,9 @@ func (p *Perceptron) Bias() float32 { * learningRate: The rate at which to adjust the perceptron weights. */ func (p *Perceptron) adjust(input Input, delta int, learningRate float32) { - for i, input := range input.Features() { - p.weights[i] += float32(input) * float32(delta) * learningRate - } + for i, input := range input.Features() { + p.weights[i] += float32(input) * float32(delta) * learningRate + } - p.bias += float32(delta) * learningRate + p.bias += float32(delta) * learningRate } diff --git a/src/ai/mcts.go b/src/ai/mcts.go index aa57bb8..cdc5750 100644 --- a/src/ai/mcts.go +++ b/src/ai/mcts.go @@ -1,20 +1,20 @@ package ai import ( - "container/heap" - "fmt" - "math" - "math/rand" - "time" + "container/heap" + "fmt" + "math" + "math/rand" + "time" ) var r = rand.New(rand.NewSource(time.Now().UnixNano())) type State interface { - Determinize() - // TODO: Is there a more efficient way than copying to get new - // determinizations? - Copy() State + Determinize() + // TODO: Is there a more efficient way than copying to get new + // determinizations? + Copy() State } // This is a Node that is used for the MCTS tree. It has the attributes necessary @@ -23,64 +23,64 @@ type State interface { // queue based on the node's UCB. Any data can be passed along with a node // through the Value methods, which accept a blank interface type. type Node struct { - value Move - priority float64 - index int + value Move + priority float64 + index int - children PriorityQueue - parent *Node + children PriorityQueue + parent *Node - eval float64 - simulations int + eval float64 + simulations int - memoize []Move - depth int + memoize []Move + depth int } // Return a new node that is properly initialized. Specifically, the priority // queue for the children is properly made. func NewNode() *Node { - var n Node + var n Node - n.children = make(PriorityQueue, 0) - heap.Init(&n.children) + n.children = make(PriorityQueue, 0) + heap.Init(&n.children) - return &n + return &n } func (node *Node) GetMove() Move { - return node.value + return node.value } func (node *Node) GetState() State { - return node.value.State.(State) + return node.value.State.(State) } func (node *Node) GetValue() interface{} { - return node.value + return node.value } func (node *Node) Value(v interface{}) { - node.value = v.(Move) + node.value = v.(Move) } // The priority of a node is based on how many times all of its siblings have // been sampled, how many times it has been sampled, and how many times it and // its siblings have been sampled. func (node *Node) GetPriority() float64 { - return node.priority + return node.priority } func (node *Node) Priority(priority float64) { - node.priority = priority + node.priority = priority } func (node *Node) GetIndex() int { - return node.index + return node.index } func (node *Node) Index(i int) { - node.index = i + node.index = i } // The UpperConfBound for a node's expected winnings are based on the node's @@ -92,17 +92,16 @@ func (node *Node) Index(i int) { // node - The node which we are calculating the UCB of. // Returns the UCB of the node using sqrt(2) as the bias parameter. func UpperConfBound(node *Node) float64 { - var ucb float64 - if node.simulations == 0 && node.parent != nil { - ucb = math.Inf(1) - } else if node.parent != nil { - ucb = float64(node.eval) / float64(node.simulations) + - math.Sqrt(2.0 * (math.Log(float64(node.parent.simulations)) + 1) / float64(node.simulations)) - } - return ucb + var ucb float64 + if node.simulations == 0 && node.parent != nil { + ucb = math.Inf(1) + } else if node.parent != nil { + ucb = float64(node.eval)/float64(node.simulations) + + math.Sqrt(2.0*(math.Log(float64(node.parent.simulations))+1)/float64(node.simulations)) + } + return ucb } - /* * Performs a Monte Carlo Tree search on the given the state and the game engine. * This MCTS is on a non-deterministic game so specify the amount of random @@ -120,48 +119,47 @@ func UpperConfBound(node *Node) float64 { * it. */ func MCTS(s State, engine TSEngine, runs int, deters int) (Move, float64) { - // TODO: Is there a better way than this dual map way. This probably isn't - // a bottleneck however. - weights := make(map[interface{}]float64) - conv := make(map[interface{}]Move) - counts := make(map[interface{}]int) - - for i := 0; i < deters; i++ { - copyState := s.Copy() - copyState.Determinize() - - n := NewNode() - m := Move { - nil, - copyState, - } - n.Value(m) - - for j := 0; j < runs; j++ { - RunPlayout(n, engine) - - topNode := n.children.Poll().(*Node) - topMove := topNode.GetMove() - - conv[topMove.Action] = topMove - weights[topMove.Action] += topNode.GetPriority() - counts[topMove.Action] += 1 - } - } - - var maxMove Move - maxWeight := math.Inf(-1) - for hash, weight := range weights { - if weight > maxWeight { - maxMove = conv[hash] - maxWeight = weight - } - } - - return maxMove, maxWeight / float64(counts[maxMove.Action]) + // TODO: Is there a better way than this dual map way. This probably isn't + // a bottleneck however. + weights := make(map[interface{}]float64) + conv := make(map[interface{}]Move) + counts := make(map[interface{}]int) + + for i := 0; i < deters; i++ { + copyState := s.Copy() + copyState.Determinize() + + n := NewNode() + m := Move{ + nil, + copyState, + } + n.Value(m) + + for j := 0; j < runs; j++ { + RunPlayout(n, engine) + + topNode := n.children.Poll().(*Node) + topMove := topNode.GetMove() + + conv[topMove.Action] = topMove + weights[topMove.Action] += topNode.GetPriority() + counts[topMove.Action] += 1 + } + } + + var maxMove Move + maxWeight := math.Inf(-1) + for hash, weight := range weights { + if weight > maxWeight { + maxMove = conv[hash] + maxWeight = weight + } + } + + return maxMove, maxWeight / float64(counts[maxMove.Action]) } - /* * This method is for internal testing of an MCTS playout. It prints out debug * info so that the MCTS process can be verified. @@ -175,10 +173,9 @@ func MCTS(s State, engine TSEngine, runs int, deters int) (Move, float64) { * engine's computation. */ func RunPlayoutDebug(node *Node, engine TSEngine) float64 { - return runPlayout(node, engine, true) + return runPlayout(node, engine, true) } - /* * The normal MCTS playout method. This method does not provide any logging. * @@ -191,10 +188,9 @@ func RunPlayoutDebug(node *Node, engine TSEngine) float64 { * engine's computation. */ func RunPlayout(node *Node, engine TSEngine) float64 { - return runPlayout(node, engine, false) + return runPlayout(node, engine, false) } - /* * The internal logic for the MCTS tree logic. Provides a logging flag for * debugging purposes. @@ -209,71 +205,71 @@ func RunPlayout(node *Node, engine TSEngine) float64 { * engine's computation. */ func runPlayout(node *Node, engine TSEngine, log bool) float64 { - if log { - fmt.Println(node.GetState()) - } - - node.simulations++ - - var eval float64 - // We have been given a node state that is the last in the playout. Time to - // return and backpropagate the results. - if engine.IsTerminal(node.GetState()) { - eval = engine.Evaluation(node.GetState()) - } else { - var nextMoves []Move - if node.depth <= 2 { - if node.memoize == nil { - node.memoize = engine.Successors(node.GetState()) - } - - nextMoves = node.memoize - } else { - nextMoves = engine.Successors(node.GetState()) - } - - var next *Node - - // If we don't have data on all the posssible next states, select one at - // random. Otherwise, choose the one with the highest UCB. - if len(nextMoves) > node.children.Len() { - takenMoves := make(map[interface{}]int) - - for i := 0; i < node.children.Len(); i++ { - takenMoves[node.children[i].(*Node).GetMove().Action] = i - } - - nextMove := nextMoves[r.Intn(len(nextMoves))] - - if _, ok := takenMoves[nextMove.Action]; ok { - next = node.children[takenMoves[nextMove.Action]].(*Node) - } else { - next = NewNode() - next.Value(nextMove) - next.parent = node - next.depth = node.depth + 1 - heap.Push(&node.children, next) - } - } else { - next = node.children.Poll().(*Node) - } - eval = runPlayout(next, engine, log) - - adjEval := eval - if adjEval < 0 { - adjEval *= -1 - } - - fav := engine.Favorable(node.GetState()) - if (fav && eval > 0) || (!fav && eval < 0) { - next.eval += adjEval - } else { - next.eval -= adjEval - } - - next.Priority(UpperConfBound(next)) - node.children.Update(next, next.GetValue(), next.GetPriority()) - } - - return eval + if log { + fmt.Println(node.GetState()) + } + + node.simulations++ + + var eval float64 + // We have been given a node state that is the last in the playout. Time to + // return and backpropagate the results. + if engine.IsTerminal(node.GetState()) { + eval = engine.Evaluation(node.GetState()) + } else { + var nextMoves []Move + if node.depth <= 2 { + if node.memoize == nil { + node.memoize = engine.Successors(node.GetState()) + } + + nextMoves = node.memoize + } else { + nextMoves = engine.Successors(node.GetState()) + } + + var next *Node + + // If we don't have data on all the posssible next states, select one at + // random. Otherwise, choose the one with the highest UCB. + if len(nextMoves) > node.children.Len() { + takenMoves := make(map[interface{}]int) + + for i := 0; i < node.children.Len(); i++ { + takenMoves[node.children[i].(*Node).GetMove().Action] = i + } + + nextMove := nextMoves[r.Intn(len(nextMoves))] + + if _, ok := takenMoves[nextMove.Action]; ok { + next = node.children[takenMoves[nextMove.Action]].(*Node) + } else { + next = NewNode() + next.Value(nextMove) + next.parent = node + next.depth = node.depth + 1 + heap.Push(&node.children, next) + } + } else { + next = node.children.Poll().(*Node) + } + eval = runPlayout(next, engine, log) + + adjEval := eval + if adjEval < 0 { + adjEval *= -1 + } + + fav := engine.Favorable(node.GetState()) + if (fav && eval > 0) || (!fav && eval < 0) { + next.eval += adjEval + } else { + next.eval -= adjEval + } + + next.Priority(UpperConfBound(next)) + node.children.Update(next, next.GetValue(), next.GetPriority()) + } + + return eval } diff --git a/src/ai/minimax.go b/src/ai/minimax.go index 6a286bb..027cc95 100644 --- a/src/ai/minimax.go +++ b/src/ai/minimax.go @@ -2,7 +2,6 @@ package ai import "math" - /* * Uses minimax adversarial tree search to find the optimal move in a game. * @@ -16,10 +15,9 @@ import "math" * and the state it will send you to. */ func Minimax(state TSState, engine TSEngine) (float64, Move) { - return minimaxHelper(state, engine, math.Inf(-1), math.Inf(1)) + return minimaxHelper(state, engine, math.Inf(-1), math.Inf(1)) } - /* * Finds the best move and its evaluation using minimax adversarial search and * alpha-beta pruning. This is a helper method used privately by Minimax. @@ -36,45 +34,45 @@ func Minimax(state TSState, engine TSEngine) (float64, Move) { * and the state it will send you to. */ func minimaxHelper(state TSState, engine TSEngine, alpha float64, - beta float64) (float64, Move) { - if engine.IsTerminal(state) { - return engine.Evaluation(state), Move { nil, state } - } + beta float64) (float64, Move) { + if engine.IsTerminal(state) { + return engine.Evaluation(state), Move{nil, state} + } - fav := engine.Favorable(state) + fav := engine.Favorable(state) - var extremeMove Move - var extremeValue float64 - if fav { - extremeValue = math.Inf(-1) - } else { - extremeValue = math.Inf(1) - } + var extremeMove Move + var extremeValue float64 + if fav { + extremeValue = math.Inf(-1) + } else { + extremeValue = math.Inf(1) + } - for _, nextMove := range engine.Successors(state) { - nextState := nextMove.State - nextEval, _ := minimaxHelper(nextState, engine, alpha, beta) + for _, nextMove := range engine.Successors(state) { + nextState := nextMove.State + nextEval, _ := minimaxHelper(nextState, engine, alpha, beta) - if fav { - if nextEval > extremeValue { - extremeValue = nextEval - extremeMove = nextMove - } + if fav { + if nextEval > extremeValue { + extremeValue = nextEval + extremeMove = nextMove + } - alpha = math.Max(alpha, nextEval) - } else { - if nextEval < extremeValue { - extremeValue = nextEval - extremeMove = nextMove - } + alpha = math.Max(alpha, nextEval) + } else { + if nextEval < extremeValue { + extremeValue = nextEval + extremeMove = nextMove + } - beta = math.Min(beta, nextEval) - } + beta = math.Min(beta, nextEval) + } - if beta < alpha { - break - } - } + if beta < alpha { + break + } + } - return extremeValue, extremeMove + return extremeValue, extremeMove } diff --git a/src/ai/pq.go b/src/ai/pq.go index 05a0e24..abdb5e4 100644 --- a/src/ai/pq.go +++ b/src/ai/pq.go @@ -7,58 +7,58 @@ import "container/heap" // TODO: Include example of initialization and usage code. type PQItem interface { - GetValue() interface{} - Value(v interface{}) + GetValue() interface{} + Value(v interface{}) - GetPriority() float64 - Priority(priority float64) + GetPriority() float64 + Priority(priority float64) - GetIndex() int - Index(i int) + GetIndex() int + Index(i int) } type PriorityQueue []PQItem func (pq PriorityQueue) Len() int { - return len(pq) + return len(pq) } func (pq PriorityQueue) Less(i, j int) bool { - // Since this is a MaxHeap, and container/heap implements a MinHeap, we need - // to use greater than. - return pq[i].GetPriority() > pq[j].GetPriority() + // Since this is a MaxHeap, and container/heap implements a MinHeap, we need + // to use greater than. + return pq[i].GetPriority() > pq[j].GetPriority() } func (pq PriorityQueue) Swap(i, j int) { - pq[i], pq[j] = pq[j], pq[i] - pq[i].Index(i) - pq[j].Index(j) + pq[i], pq[j] = pq[j], pq[i] + pq[i].Index(i) + pq[j].Index(j) } func (pq *PriorityQueue) Push(item interface{}) { - n := len(*pq) - x := item.(PQItem) - x.Index(n) - *pq = append(*pq, x) + n := len(*pq) + x := item.(PQItem) + x.Index(n) + *pq = append(*pq, x) } func (pq *PriorityQueue) Poll() PQItem { - return (*pq)[0] + return (*pq)[0] } func (pq *PriorityQueue) Pop() interface{} { - old := *pq - n := len(old) + old := *pq + n := len(old) - item := old[n - 1] - item.Index(-1) - *pq = old[:n - 1] + item := old[n-1] + item.Index(-1) + *pq = old[:n-1] - return item + return item } func (pq *PriorityQueue) Update(item PQItem, value interface{}, priority float64) { - item.Value(value) - item.Priority(priority) - heap.Fix(pq, item.GetIndex()) + item.Value(value) + item.Priority(priority) + heap.Fix(pq, item.GetIndex()) } diff --git a/src/ai/pq_test.go b/src/ai/pq_test.go index 1f4cb4c..3511f7f 100644 --- a/src/ai/pq_test.go +++ b/src/ai/pq_test.go @@ -1,69 +1,69 @@ package ai import ( - "container/heap" - "testing" + "container/heap" + "testing" ) type TestItem struct { - value interface{} - priority float64 - index int + value interface{} + priority float64 + index int } -func (t* TestItem) GetValue() interface{} { - return t.value +func (t *TestItem) GetValue() interface{} { + return t.value } -func (t* TestItem) Value(value interface{}) { - t.value = value +func (t *TestItem) Value(value interface{}) { + t.value = value } func (t *TestItem) GetPriority() float64 { - return t.priority + return t.priority } func (t *TestItem) Priority(priority float64) { - t.priority = priority + t.priority = priority } func (t *TestItem) GetIndex() int { - return t.index + return t.index } func (t *TestItem) Index(index int) { - t.index = index + t.index = index } func TestUpdateMaxHeap(t *testing.T) { - items := map[string]float64 { - "banana": 3, "apple": 2, "pear": 4, - } + items := map[string]float64{ + "banana": 3, "apple": 2, "pear": 4, + } - pq := make(PriorityQueue, len(items)) - i := 0 - for value, priority := range items { - pq[i] = &TestItem { - value, - priority, - i, - } + pq := make(PriorityQueue, len(items)) + i := 0 + for value, priority := range items { + pq[i] = &TestItem{ + value, + priority, + i, + } - i++ - } - heap.Init(&pq) + i++ + } + heap.Init(&pq) - item := &TestItem { - "pineapple", - 5, - 0, - } - heap.Push(&pq, item) - pq.Update(item, item.GetValue(), item.GetPriority()) + item := &TestItem{ + "pineapple", + 5, + 0, + } + heap.Push(&pq, item) + pq.Update(item, item.GetValue(), item.GetPriority()) - res := pq.Poll() - if res != item { - // TODO: Improve error message. - t.Error("Expected pineapple not %s.\n", res) - } + res := pq.Poll() + if res != item { + // TODO: Improve error message. + t.Error("Expected pineapple not %s.\n", res) + } } diff --git a/src/ai/tree_search.go b/src/ai/tree_search.go index e6954d4..7b3e91c 100644 --- a/src/ai/tree_search.go +++ b/src/ai/tree_search.go @@ -1,8 +1,6 @@ package ai - -type TSState interface { } - +type TSState interface{} /* * A type to represent a move in a game. A move consists of an action, and a @@ -10,14 +8,13 @@ type TSState interface { } * represented via a nil action. */ type Move struct { - Action interface{} - State TSState + Action interface{} + State TSState } - type TSEngine interface { - Favorable(state TSState) bool - IsTerminal(state TSState) bool - Evaluation(state TSState) float64 - Successors(state TSState) []Move + Favorable(state TSState) bool + IsTerminal(state TSState) bool + Evaluation(state TSState) float64 + Successors(state TSState) []Move } diff --git a/src/cmd/benchmark/benchmark_analysis.go b/src/cmd/benchmark/benchmark_analysis.go index 87559dd..04807cb 100644 --- a/src/cmd/benchmark/benchmark_analysis.go +++ b/src/cmd/benchmark/benchmark_analysis.go @@ -1,16 +1,15 @@ package main import ( - "bufio" - "flag" - "fmt" - "log" - "os" - "strconv" - "strings" + "bufio" + "flag" + "fmt" + "log" + "os" + "strconv" + "strings" ) - /* * Analyze the results of the benchmarking. The benchmarking results file has * the initial setup of a Euchre hand and then the difference between the @@ -22,40 +21,39 @@ import ( * ./benchmark_analysis -dataLoc={dataLoc} */ - func main() { - var dataLoc string - flag.StringVar(&dataLoc, "dataLoc", "", "Location of the benchmark results.") - flag.Parse() - - dataFile, err := os.Open(dataLoc) - if err != nil { - log.Fatal(err) - } - defer dataFile.Close() - - count := 0 - sum := 0.0 - bins := make([]float64, 7) - scanner := bufio.NewScanner(dataFile) - for scanner.Scan() { - line := scanner.Text() - tabIndex := strings.IndexRune(line, '\t') - - diff, _ := strconv.ParseFloat(line[tabIndex + 1:], 64) - bin := int(diff) - - count++ - sum += diff - bins[bin]++ - } - - for i := 0; i < len(bins); i++ { - bins[i] = bins[i] / float64(count) - } - - avg := sum / float64(count) - fmt.Printf("%f, %f, %f, %f, %f, %f, %f, %f\n", avg, bins[0], bins[1], bins[2], - bins[3], bins[4], bins[5], - bins[6]) + var dataLoc string + flag.StringVar(&dataLoc, "dataLoc", "", "Location of the benchmark results.") + flag.Parse() + + dataFile, err := os.Open(dataLoc) + if err != nil { + log.Fatal(err) + } + defer dataFile.Close() + + count := 0 + sum := 0.0 + bins := make([]float64, 7) + scanner := bufio.NewScanner(dataFile) + for scanner.Scan() { + line := scanner.Text() + tabIndex := strings.IndexRune(line, '\t') + + diff, _ := strconv.ParseFloat(line[tabIndex+1:], 64) + bin := int(diff) + + count++ + sum += diff + bins[bin]++ + } + + for i := 0; i < len(bins); i++ { + bins[i] = bins[i] / float64(count) + } + + avg := sum / float64(count) + fmt.Printf("%f, %f, %f, %f, %f, %f, %f, %f\n", avg, bins[0], bins[1], bins[2], + bins[3], bins[4], bins[5], + bins[6]) } diff --git a/src/cmd/benchmark/benchmark_play.go b/src/cmd/benchmark/benchmark_play.go index 01c1450..85e43a3 100644 --- a/src/cmd/benchmark/benchmark_play.go +++ b/src/cmd/benchmark/benchmark_play.go @@ -1,22 +1,20 @@ package main - import ( - "ai" - "bufio" - "deck" - "euchre" - "flag" - "fmt" - "encoding/json" - "log" - "os" - "player" - "strconv" - "strings" + "ai" + "bufio" + "deck" + "encoding/json" + "euchre" + "flag" + "fmt" + "log" + "os" + "player" + "strconv" + "strings" ) - /* * Benchmark an implementation against optimal players. Provide the location to * data that has a given state and the eventual score assuming perfect @@ -36,119 +34,116 @@ import ( * 2: RANDOM */ - const ( - PICKUP_CONF = 0.6 - CALL_CONF = 0.6 - ALONE_CONF = 1.2 - PICKUP_RUNS = 5000 - PICKUP_DETERMINIZATIONS = 50 - CALL_RUNS = 5000 - CALL_DETERMINIZATIONS = 50 - PLAY_RUNS = 5000 - PLAY_DETERMINIZATIONS = 50 - ALONE_RUNS = 5000 - ALONE_DETERMINIZATIONS = 50 + PICKUP_CONF = 0.6 + CALL_CONF = 0.6 + ALONE_CONF = 1.2 + PICKUP_RUNS = 5000 + PICKUP_DETERMINIZATIONS = 50 + CALL_RUNS = 5000 + CALL_DETERMINIZATIONS = 50 + PLAY_RUNS = 5000 + PLAY_DETERMINIZATIONS = 50 + ALONE_RUNS = 5000 + ALONE_DETERMINIZATIONS = 50 ) - func main() { - var dataLoc string - var playerType int - var paired bool - flag.StringVar(&dataLoc, "dataLoc", "", "Location of minimax evaluated games.") - flag.IntVar(&playerType, "playerType", 0, "The type of player to evaluate.") - flag.BoolVar(&paired, "paired", false, "Set if you wish partner play to be evaluated.") - flag.Parse() - - // Create the mapping of playerType to player object and get the desired - // player to evaluate. - players := make(map[int]player.Player) - players[0] = player.NewSmart(PICKUP_CONF, CALL_CONF, ALONE_CONF, - PICKUP_RUNS, PICKUP_DETERMINIZATIONS, - CALL_RUNS, CALL_DETERMINIZATIONS, - PLAY_RUNS, PLAY_DETERMINIZATIONS, - ALONE_RUNS, ALONE_DETERMINIZATIONS) - players[1] = player.NewRule("data/train.dat") - players[2] = player.NewRand(0.5, 0.5, 0) - chosenPlayer := players[playerType] - - dataFile, err := os.Open(dataLoc) - if err != nil { - log.Fatal(err) - } - defer dataFile.Close() - - scanner := bufio.NewScanner(dataFile) - for scanner.Scan() { - // Parse each line to get the minimax evaluation, and the actual game - // state. - line := scanner.Text() - tabIndex := strings.IndexRune(line, '\t') - - var state euchre.State - stateStr := line[:tabIndex] - json.Unmarshal([]byte(stateStr), &state) - minimaxEval, _ := strconv.ParseFloat(line[tabIndex + 1:], 64) - - // Now that we have the game state, we can simulate the game. Using - // Minimax players for the opponents and the desired player strategy - // for user 0. - engine := euchre.Engine{ } - - for i := 0; i < 5; i++ { - - var last int - for j := 0; j < 4; j++ { - last = state.Player - // If it is the AI's turn, use the chosen player logic to choose - // what card to use next. Then keep the state updated, so that the - // Minimax agents know what is going on. - if state.Player == 0 || (paired && state.Player == 2) { - curHand, chosen := chosenPlayer.Play(state.Player, state.Setup, - state.Hands[state.Player], - state.Played, state.Prior) - - state.Played = append(state.Played, chosen) - state.Hands[state.Player] = curHand - state.Player = (state.Player + 1) % 4 - } else { - // If it is the Minimax agents' turn, use their logic. This agent - // provides the successor state as well so just use that. - _, chosenMove := ai.Minimax(state, engine) - state = chosenMove.State.(euchre.State) - } - } - - // If the last turn was the AI's then the last trick was not added - // automatically to the state. Further we know, who led, since we - // were last. Namely, player 1. - // TODO: This is internal logic that is being handled very closely - // by the outside program. This should be encapsulated. - if last == 0 || (paired && last == 2) { - led := euchre.LeaderInclusive(state.Played, last, - state.Setup.AlonePlayer) - trick := euchre.Trick { - state.Played, - led, - state.Setup.Trump, - state.Setup.AlonePlayer, - } - state.Player = euchre.Winner(state.Played, state.Setup.Trump, led, - state.Setup.AlonePlayer) - state.Played = make([]deck.Card, 0, 4) - state.Prior = append(state.Prior, trick) - } - } - - playerScore := engine.Evaluation(state) - diff := minimaxEval - playerScore - - fmt.Printf("%s\t%f\n", stateStr, diff) - } - - - if err := scanner.Err(); err != nil { - log.Fatal(err) - } + var dataLoc string + var playerType int + var paired bool + flag.StringVar(&dataLoc, "dataLoc", "", "Location of minimax evaluated games.") + flag.IntVar(&playerType, "playerType", 0, "The type of player to evaluate.") + flag.BoolVar(&paired, "paired", false, "Set if you wish partner play to be evaluated.") + flag.Parse() + + // Create the mapping of playerType to player object and get the desired + // player to evaluate. + players := make(map[int]player.Player) + players[0] = player.NewSmart(PICKUP_CONF, CALL_CONF, ALONE_CONF, + PICKUP_RUNS, PICKUP_DETERMINIZATIONS, + CALL_RUNS, CALL_DETERMINIZATIONS, + PLAY_RUNS, PLAY_DETERMINIZATIONS, + ALONE_RUNS, ALONE_DETERMINIZATIONS) + players[1] = player.NewRule("data/train.dat") + players[2] = player.NewRand(0.5, 0.5, 0) + chosenPlayer := players[playerType] + + dataFile, err := os.Open(dataLoc) + if err != nil { + log.Fatal(err) + } + defer dataFile.Close() + + scanner := bufio.NewScanner(dataFile) + for scanner.Scan() { + // Parse each line to get the minimax evaluation, and the actual game + // state. + line := scanner.Text() + tabIndex := strings.IndexRune(line, '\t') + + var state euchre.State + stateStr := line[:tabIndex] + json.Unmarshal([]byte(stateStr), &state) + minimaxEval, _ := strconv.ParseFloat(line[tabIndex+1:], 64) + + // Now that we have the game state, we can simulate the game. Using + // Minimax players for the opponents and the desired player strategy + // for user 0. + engine := euchre.Engine{} + + for i := 0; i < 5; i++ { + + var last int + for j := 0; j < 4; j++ { + last = state.Player + // If it is the AI's turn, use the chosen player logic to choose + // what card to use next. Then keep the state updated, so that the + // Minimax agents know what is going on. + if state.Player == 0 || (paired && state.Player == 2) { + curHand, chosen := chosenPlayer.Play(state.Player, state.Setup, + state.Hands[state.Player], + state.Played, state.Prior) + + state.Played = append(state.Played, chosen) + state.Hands[state.Player] = curHand + state.Player = (state.Player + 1) % 4 + } else { + // If it is the Minimax agents' turn, use their logic. This agent + // provides the successor state as well so just use that. + _, chosenMove := ai.Minimax(state, engine) + state = chosenMove.State.(euchre.State) + } + } + + // If the last turn was the AI's then the last trick was not added + // automatically to the state. Further we know, who led, since we + // were last. Namely, player 1. + // TODO: This is internal logic that is being handled very closely + // by the outside program. This should be encapsulated. + if last == 0 || (paired && last == 2) { + led := euchre.LeaderInclusive(state.Played, last, + state.Setup.AlonePlayer) + trick := euchre.Trick{ + state.Played, + led, + state.Setup.Trump, + state.Setup.AlonePlayer, + } + state.Player = euchre.Winner(state.Played, state.Setup.Trump, led, + state.Setup.AlonePlayer) + state.Played = make([]deck.Card, 0, 4) + state.Prior = append(state.Prior, trick) + } + } + + playerScore := engine.Evaluation(state) + diff := minimaxEval - playerScore + + fmt.Printf("%s\t%f\n", stateStr, diff) + } + + if err := scanner.Err(); err != nil { + log.Fatal(err) + } } diff --git a/src/cmd/benchmark/gen_benchmark_play.go b/src/cmd/benchmark/gen_benchmark_play.go index 6c75bca..0c68876 100644 --- a/src/cmd/benchmark/gen_benchmark_play.go +++ b/src/cmd/benchmark/gen_benchmark_play.go @@ -1,18 +1,16 @@ package main - import ( - "ai" - "deck" - "encoding/json" - "euchre" - "flag" - "fmt" - "math/rand" - "time" + "ai" + "deck" + "encoding/json" + "euchre" + "flag" + "fmt" + "math/rand" + "time" ) - /* * This script randomly generates a data set to evaluate different strategies * on. This script will randomly generate a given number of samples along with @@ -26,10 +24,8 @@ import ( * samples are the number of situations you wish to compare. */ - var r = rand.New(rand.NewSource(time.Now().UnixNano())) - /* * Create a random setup given the current situation. The random setup randomly * chooses who dealt and what trump is. Nobody is ever going alone though, and @@ -43,48 +39,47 @@ var r = rand.New(rand.NewSource(time.Now().UnixNano())) * The randomized euchre setup. */ func randomNoAloneNoPickupSetup(splits [][]deck.Card) euchre.Setup { - dealer := r.Intn(4) - caller := r.Intn(4) - pickedUp := false - top := splits[4][3] - trump := deck.SUITS[r.Intn(len(deck.SUITS))] - var discard deck.Card - - return euchre.Setup { - dealer, - caller, - pickedUp, - top, - trump, - discard, - -1, - } + dealer := r.Intn(4) + caller := r.Intn(4) + pickedUp := false + top := splits[4][3] + trump := deck.SUITS[r.Intn(len(deck.SUITS))] + var discard deck.Card + + return euchre.Setup{ + dealer, + caller, + pickedUp, + top, + trump, + discard, + -1, + } } - func main() { - var samples int - flag.IntVar(&samples, "samples", 0, "Number of sample games to simluate") - flag.Parse() - - engine := euchre.Engine{ } - for i := 0; i < samples; i++ { - splits := euchre.GenSituation() - setup := randomNoAloneNoPickupSetup(splits) - - // Delete the kitty so that it is not included in the state. - copy(splits[4:], splits[5:]) - splits[len(splits) - 1] = nil - splits = splits[:len(splits) - 1] - - played := make([]deck.Card, 0, 4) - prior := make([]euchre.Trick, 0, 5) - state := euchre.NewDeterminizedState(setup, (setup.Dealer + 1) % 4, - splits, played, prior) - - score, _ := ai.Minimax(state, engine) - - stateStr, _ := json.Marshal(state) - fmt.Printf("%s\t%f\n", stateStr, score) - } + var samples int + flag.IntVar(&samples, "samples", 0, "Number of sample games to simluate") + flag.Parse() + + engine := euchre.Engine{} + for i := 0; i < samples; i++ { + splits := euchre.GenSituation() + setup := randomNoAloneNoPickupSetup(splits) + + // Delete the kitty so that it is not included in the state. + copy(splits[4:], splits[5:]) + splits[len(splits)-1] = nil + splits = splits[:len(splits)-1] + + played := make([]deck.Card, 0, 4) + prior := make([]euchre.Trick, 0, 5) + state := euchre.NewDeterminizedState(setup, (setup.Dealer+1)%4, + splits, played, prior) + + score, _ := ai.Minimax(state, engine) + + stateStr, _ := json.Marshal(state) + fmt.Printf("%s\t%f\n", stateStr, score) + } } diff --git a/src/cmd/dpickup/dpickup.go b/src/cmd/dpickup/dpickup.go index a60a9f3..03af328 100644 --- a/src/cmd/dpickup/dpickup.go +++ b/src/cmd/dpickup/dpickup.go @@ -1,111 +1,111 @@ package main import ( - "bufio" - "deck" - "fmt" - "math/rand" - "os" - "euchre/pickup" - "time" + "bufio" + "deck" + "euchre/pickup" + "fmt" + "math/rand" + "os" + "time" ) // TODO: Isolate randomness to it's own local package. var r *rand.Rand func GenPickupInput() pickup.Input { - r = rand.New(rand.NewSource(time.Now().UnixNano())) - - hand := deck.DrawN(5) - - var top deck.Card - inHand := true - for inHand { - top = deck.GenCard() - - for _, card := range hand { - inHand = false - if card == top { - inHand = true - break - } - } - } - - return pickup.Input { - top, - hand, - r.Intn(4), - } + r = rand.New(rand.NewSource(time.Now().UnixNano())) + + hand := deck.DrawN(5) + + var top deck.Card + inHand := true + for inHand { + top = deck.GenCard() + + for _, card := range hand { + inHand = false + if card == top { + inHand = true + break + } + } + } + + return pickup.Input{ + top, + hand, + r.Intn(4), + } } func check(err error) { - if err != nil { - panic(err) - } + if err != nil { + panic(err) + } } func main() { - filename := os.Args[1] - file, err := os.OpenFile(filename, os.O_RDWR, 0600) - check(err) - - // Read in the current existing samples into a map that easily tracks which - // problem instances have already been determined. - var samples map[pickup.Input]bool = make(map[pickup.Input]bool) - scanner := bufio.NewScanner(file) - for scanner.Scan() { - line := scanner.Text() - - var nextSample pickup.Input - var tmpTop string - var tmpHand [5]string - var tmpDealer int - fmt.Sscanf(line, "%s %s %s %s %s %s %d", &tmpTop, &tmpHand[0], - &tmpHand[1], &tmpHand[2], - &tmpHand[3], &tmpHand[4], - &tmpDealer) - - nextSample.Top = deck.CreateCard(tmpTop) - for i, tmpCard := range tmpHand { - nextSample.Hand[i] = deck.CreateCard(tmpCard) - } - nextSample.Dealer = tmpDealer - - samples[nextSample] = true - } - - // Move the file pointer to the end of the file. - file.Seek(0, 2) - - fmt.Print("Each line that is generated is a new test sample.\n") - fmt.Print("Enter 1 for it is picked up, 0 to pass, and -1 to quit.\n") - fmt.Printf("%-10s\t%-20s\t%-10s\n", "Top", "Hand", "Dealer") - for { - ps := GenPickupInput() - - // If this generated sample already exists generate a new one until it - // it is a new one. - for _, in := samples[ps]; in ; _, in = samples[ps] { - ps = GenPickupInput() - } - - // Output the new sample and get the user's decision. - var handStr string - for _, card := range ps.Hand { - handStr += card.String() + " " - } - handStr = handStr[:len(handStr) - 1] - fmt.Printf("%-10s\t%-20s\t%-10d ", ps.Top, handStr, ps.Dealer) - - var orderedUp int - fmt.Scanf("%d", &orderedUp) - if orderedUp < 0 { - break - } - - fmt.Fprintf(file, "%s %s %d %d\n", ps.Top, handStr, ps.Dealer, orderedUp) - } - - file.Close() + filename := os.Args[1] + file, err := os.OpenFile(filename, os.O_RDWR, 0600) + check(err) + + // Read in the current existing samples into a map that easily tracks which + // problem instances have already been determined. + var samples map[pickup.Input]bool = make(map[pickup.Input]bool) + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := scanner.Text() + + var nextSample pickup.Input + var tmpTop string + var tmpHand [5]string + var tmpDealer int + fmt.Sscanf(line, "%s %s %s %s %s %s %d", &tmpTop, &tmpHand[0], + &tmpHand[1], &tmpHand[2], + &tmpHand[3], &tmpHand[4], + &tmpDealer) + + nextSample.Top = deck.CreateCard(tmpTop) + for i, tmpCard := range tmpHand { + nextSample.Hand[i] = deck.CreateCard(tmpCard) + } + nextSample.Dealer = tmpDealer + + samples[nextSample] = true + } + + // Move the file pointer to the end of the file. + file.Seek(0, 2) + + fmt.Print("Each line that is generated is a new test sample.\n") + fmt.Print("Enter 1 for it is picked up, 0 to pass, and -1 to quit.\n") + fmt.Printf("%-10s\t%-20s\t%-10s\n", "Top", "Hand", "Dealer") + for { + ps := GenPickupInput() + + // If this generated sample already exists generate a new one until it + // it is a new one. + for _, in := samples[ps]; in; _, in = samples[ps] { + ps = GenPickupInput() + } + + // Output the new sample and get the user's decision. + var handStr string + for _, card := range ps.Hand { + handStr += card.String() + " " + } + handStr = handStr[:len(handStr)-1] + fmt.Printf("%-10s\t%-20s\t%-10d ", ps.Top, handStr, ps.Dealer) + + var orderedUp int + fmt.Scanf("%d", &orderedUp) + if orderedUp < 0 { + break + } + + fmt.Fprintf(file, "%s %s %d %d\n", ps.Top, handStr, ps.Dealer, orderedUp) + } + + file.Close() } diff --git a/src/cmd/main/main.go b/src/cmd/main/main.go index 62bbae9..bb90d43 100644 --- a/src/cmd/main/main.go +++ b/src/cmd/main/main.go @@ -1,242 +1,240 @@ package main import ( - "bufio" - "deck" - "flag" - "fmt" - "log" - "euchre" - "player" - "os" - "runtime/pprof" + "bufio" + "deck" + "euchre" + "flag" + "fmt" + "log" + "os" + "player" + "runtime/pprof" ) const ( - PICKUP_CONF = 0.6 - CALL_CONF = 0.6 - ALONE_CONF = 1.2 - PICKUP_RUNS = 5000 - PICKUP_DETERMINIZATIONS = 50 - CALL_RUNS = 5000 - CALL_DETERMINIZATIONS = 50 - PLAY_RUNS = 5000 - PLAY_DETERMINIZATIONS = 50 - ALONE_RUNS = 5000 - ALONE_DETERMINIZATIONS = 50 + PICKUP_CONF = 0.6 + CALL_CONF = 0.6 + ALONE_CONF = 1.2 + PICKUP_RUNS = 5000 + PICKUP_DETERMINIZATIONS = 50 + CALL_RUNS = 5000 + CALL_DETERMINIZATIONS = 50 + PLAY_RUNS = 5000 + PLAY_DETERMINIZATIONS = 50 + ALONE_RUNS = 5000 + ALONE_DETERMINIZATIONS = 50 ) var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file") func inputValidCard() deck.Card { - var cardStr string - fmt.Scanf("%s", &cardStr) - card, err := deck.CreateCard(cardStr) + var cardStr string + fmt.Scanf("%s", &cardStr) + card, err := deck.CreateCard(cardStr) - for err != nil { - fmt.Println("Invalid input.") - fmt.Scanf("%s", &cardStr) - card, err = deck.CreateCard(cardStr) - } + for err != nil { + fmt.Println("Invalid input.") + fmt.Scanf("%s", &cardStr) + card, err = deck.CreateCard(cardStr) + } - return card + return card } func main() { - var ( - dealer int - caller int - trump deck.Suit - d deck.Card - ) - alonePlayer := -1 - - - player := player.NewSmart(PICKUP_CONF, CALL_CONF, ALONE_CONF, - PICKUP_RUNS, PICKUP_DETERMINIZATIONS, - CALL_RUNS, CALL_DETERMINIZATIONS, - PLAY_RUNS, PLAY_DETERMINIZATIONS, - ALONE_RUNS, ALONE_DETERMINIZATIONS) - - fmt.Println("Welcome to the Euchre AI!.") - fmt.Println("Albert is basically the best euchre player ever.") - fmt.Println("This program plays a single hand (5 tricks) at a time and") - fmt.Println("includes picking the trump suit.") - fmt.Println() - - fmt.Println("Enter the 5 cards in your hand...") - hand := make([]deck.Card, 5) - for i := 0; i < 5; i++ { - hand[i] = inputValidCard() - } - fmt.Println() - - fmt.Println("Enter the top card...") - top := inputValidCard() - fmt.Println() - - fmt.Println("You (0), Left (1), Partner (2), Right (3)") - fmt.Println("Enter whose deal it was...") - - fmt.Scanf("%d", &dealer) - fmt.Println() - - flag.Parse() - if *cpuprofile != "" { - f, err := os.Create(*cpuprofile) - if err != nil { - log.Fatal(err) - } - pprof.StartCPUProfile(f) - defer pprof.StopCPUProfile() - } - - pickedUp := player.Pickup(hand, top, dealer) - if pickedUp { - fmt.Println("Order it up.") - caller = 0 - } else { - fmt.Println("Pass.") - fmt.Println() - var pickedUpIn int - fmt.Println("But did somebody else order it up?") - fmt.Scanf("%d", &pickedUpIn) - pickedUp = pickedUpIn == 1 - - if pickedUp { - var aloneIn int - fmt.Println("Who was it?") - fmt.Scanf("%d", &caller) - - fmt.Println("Is this person going alone? (1/0)") - fmt.Scanf("%d", &aloneIn) - if aloneIn == 1 { - alonePlayer = caller - } - } - - if !pickedUp { - if chosenSuit, call := player.Call(hand, top, dealer); call { - fmt.Printf("If possible call %s on second go around.\n", chosenSuit) - caller = 0 - } else { - fmt.Println("Pass if it makes its way back to you.") - } - - fmt.Println("Enter the eventual chosen trump suit...") - var trumpStr string - var err error - fmt.Scanf("%s", &trumpStr) - trump, err = deck.CreateSuit(trumpStr) - - for err != nil { - fmt.Println("Invalid input.") - fmt.Scanf("%s", &trumpStr) - trump, err = deck.CreateSuit(trumpStr) - } - - fmt.Println("Who called this suit?") - fmt.Scanf("%d", &caller) - } - } - - if pickedUp { - trump = top.Suit - - if dealer == 0 { - hand, d = player.Discard(hand, top) - fmt.Printf("Discard %s.\n", d) - } - } - - if caller == 0 { - alone := player.Alone(hand, top, dealer) - - if alone { - fmt.Println("Go alone!") - alonePlayer = 0 - } else { - fmt.Println("Do not go alone. Whatever you do, please....") - } - } - - setup := euchre.Setup { - dealer, - caller, - pickedUp, - top, - trump, - d, - alonePlayer, - } - - var expectedCards int - if alonePlayer >= 0 { - expectedCards = 3 - } else { - expectedCards = 4 - } - - - led := (dealer + 1) % 4 - var prior []euchre.Trick - var chosen deck.Card - curHand := hand[:] - scanner := bufio.NewScanner(os.Stdin) - for i := 0; i < 5; i++ { - fmt.Println() - fmt.Printf("Trick %d\n", i + 1) - fmt.Println("Cards already played (blank line when done)...") - - played := make([]deck.Card, 0, 3) - for i := 0; i < expectedCards - 1; i++ { - scanner.Scan() - line := scanner.Text() - if line == "" { - break - } - - card, err := deck.CreateCard(line) - for err != nil { - fmt.Println("Invalid input.") - scanner.Scan() - line := scanner.Text() - card, err = deck.CreateCard(line) - } - - played = append(played, card) - } - - curHand, chosen = player.Play(0, setup, curHand, played, prior) - played = append(played, chosen) - - fmt.Printf("Play %s.\n", chosen) - fmt.Println() - - fmt.Println("Enter the rest of the cards played.") - left := expectedCards - len(played) - for i := 0; i < left; i++ { - scanner.Scan() - line := scanner.Text() - - card, err := deck.CreateCard(line) - for err != nil { - fmt.Println("Invalid input.") - scanner.Scan() - line := scanner.Text() - card, err = deck.CreateCard(line) - } - - played = append(played, card) - } - - trick := euchre.Trick { - played, - led, - trump, - alonePlayer, - } - prior = append(prior, trick) - led = euchre.Winner(played, trump, led, alonePlayer) - } + var ( + dealer int + caller int + trump deck.Suit + d deck.Card + ) + alonePlayer := -1 + + player := player.NewSmart(PICKUP_CONF, CALL_CONF, ALONE_CONF, + PICKUP_RUNS, PICKUP_DETERMINIZATIONS, + CALL_RUNS, CALL_DETERMINIZATIONS, + PLAY_RUNS, PLAY_DETERMINIZATIONS, + ALONE_RUNS, ALONE_DETERMINIZATIONS) + + fmt.Println("Welcome to the Euchre AI!.") + fmt.Println("Albert is basically the best euchre player ever.") + fmt.Println("This program plays a single hand (5 tricks) at a time and") + fmt.Println("includes picking the trump suit.") + fmt.Println() + + fmt.Println("Enter the 5 cards in your hand...") + hand := make([]deck.Card, 5) + for i := 0; i < 5; i++ { + hand[i] = inputValidCard() + } + fmt.Println() + + fmt.Println("Enter the top card...") + top := inputValidCard() + fmt.Println() + + fmt.Println("You (0), Left (1), Partner (2), Right (3)") + fmt.Println("Enter whose deal it was...") + + fmt.Scanf("%d", &dealer) + fmt.Println() + + flag.Parse() + if *cpuprofile != "" { + f, err := os.Create(*cpuprofile) + if err != nil { + log.Fatal(err) + } + pprof.StartCPUProfile(f) + defer pprof.StopCPUProfile() + } + + pickedUp := player.Pickup(hand, top, dealer) + if pickedUp { + fmt.Println("Order it up.") + caller = 0 + } else { + fmt.Println("Pass.") + fmt.Println() + var pickedUpIn int + fmt.Println("But did somebody else order it up?") + fmt.Scanf("%d", &pickedUpIn) + pickedUp = pickedUpIn == 1 + + if pickedUp { + var aloneIn int + fmt.Println("Who was it?") + fmt.Scanf("%d", &caller) + + fmt.Println("Is this person going alone? (1/0)") + fmt.Scanf("%d", &aloneIn) + if aloneIn == 1 { + alonePlayer = caller + } + } + + if !pickedUp { + if chosenSuit, call := player.Call(hand, top, dealer); call { + fmt.Printf("If possible call %s on second go around.\n", chosenSuit) + caller = 0 + } else { + fmt.Println("Pass if it makes its way back to you.") + } + + fmt.Println("Enter the eventual chosen trump suit...") + var trumpStr string + var err error + fmt.Scanf("%s", &trumpStr) + trump, err = deck.CreateSuit(trumpStr) + + for err != nil { + fmt.Println("Invalid input.") + fmt.Scanf("%s", &trumpStr) + trump, err = deck.CreateSuit(trumpStr) + } + + fmt.Println("Who called this suit?") + fmt.Scanf("%d", &caller) + } + } + + if pickedUp { + trump = top.Suit + + if dealer == 0 { + hand, d = player.Discard(hand, top) + fmt.Printf("Discard %s.\n", d) + } + } + + if caller == 0 { + alone := player.Alone(hand, top, dealer) + + if alone { + fmt.Println("Go alone!") + alonePlayer = 0 + } else { + fmt.Println("Do not go alone. Whatever you do, please....") + } + } + + setup := euchre.Setup{ + dealer, + caller, + pickedUp, + top, + trump, + d, + alonePlayer, + } + + var expectedCards int + if alonePlayer >= 0 { + expectedCards = 3 + } else { + expectedCards = 4 + } + + led := (dealer + 1) % 4 + var prior []euchre.Trick + var chosen deck.Card + curHand := hand[:] + scanner := bufio.NewScanner(os.Stdin) + for i := 0; i < 5; i++ { + fmt.Println() + fmt.Printf("Trick %d\n", i+1) + fmt.Println("Cards already played (blank line when done)...") + + played := make([]deck.Card, 0, 3) + for i := 0; i < expectedCards-1; i++ { + scanner.Scan() + line := scanner.Text() + if line == "" { + break + } + + card, err := deck.CreateCard(line) + for err != nil { + fmt.Println("Invalid input.") + scanner.Scan() + line := scanner.Text() + card, err = deck.CreateCard(line) + } + + played = append(played, card) + } + + curHand, chosen = player.Play(0, setup, curHand, played, prior) + played = append(played, chosen) + + fmt.Printf("Play %s.\n", chosen) + fmt.Println() + + fmt.Println("Enter the rest of the cards played.") + left := expectedCards - len(played) + for i := 0; i < left; i++ { + scanner.Scan() + line := scanner.Text() + + card, err := deck.CreateCard(line) + for err != nil { + fmt.Println("Invalid input.") + scanner.Scan() + line := scanner.Text() + card, err = deck.CreateCard(line) + } + + played = append(played, card) + } + + trick := euchre.Trick{ + played, + led, + trump, + alonePlayer, + } + prior = append(prior, trick) + led = euchre.Winner(played, trump, led, alonePlayer) + } } diff --git a/src/cmd/run/run.go b/src/cmd/run/run.go index 995f7f8..92fe91a 100644 --- a/src/cmd/run/run.go +++ b/src/cmd/run/run.go @@ -1,16 +1,15 @@ package main import ( - "deck" - "fmt" - "math/rand" - "os" - "player" - "strconv" - "time" + "deck" + "fmt" + "math/rand" + "os" + "player" + "strconv" + "time" ) - /* * A runner that runs a provided type of player's pickup (inital pickup or pass) * on a random sampling of cards so as to better understand its behavior. By @@ -25,53 +24,51 @@ import ( * 3. samples corresponds to how many different hands will be ran. */ - // TODO: This should be moved into some configuration file. const ( - PICKUP_CONF = 0.6 - CALL_CONF = 0.6 - ALONE_CONF = 1.2 - PICKUP_RUNS = 50 - PICKUP_DETERMINIZATIONS = 50 - CALL_RUNS = 50 - CALL_DETERMINIZATIONS = 50 - PLAY_RUNS = 50 - PLAY_DETERMINIZATIONS = 50 - ALONE_RUNS = 50 - ALONE_DETERMINIZATIONS = 50 + PICKUP_CONF = 0.6 + CALL_CONF = 0.6 + ALONE_CONF = 1.2 + PICKUP_RUNS = 50 + PICKUP_DETERMINIZATIONS = 50 + CALL_RUNS = 50 + CALL_DETERMINIZATIONS = 50 + PLAY_RUNS = 50 + PLAY_DETERMINIZATIONS = 50 + ALONE_RUNS = 50 + ALONE_DETERMINIZATIONS = 50 ) - func main() { - r := rand.New(rand.NewSource(time.Now().UnixNano())) + r := rand.New(rand.NewSource(time.Now().UnixNano())) - players := make(map[int]player.Player) - players[0] = player.NewRand(0.5, 0.5, 0) - // TODO: Make non-hardcoded. - players[1] = player.NewRule("data/train.dat") - players[2] = player.NewSmart(PICKUP_CONF, CALL_CONF, ALONE_CONF, - PICKUP_RUNS, PICKUP_DETERMINIZATIONS, - CALL_RUNS, CALL_DETERMINIZATIONS, - PLAY_RUNS, PLAY_DETERMINIZATIONS, - ALONE_RUNS, ALONE_DETERMINIZATIONS) + players := make(map[int]player.Player) + players[0] = player.NewRand(0.5, 0.5, 0) + // TODO: Make non-hardcoded. + players[1] = player.NewRule("data/train.dat") + players[2] = player.NewSmart(PICKUP_CONF, CALL_CONF, ALONE_CONF, + PICKUP_RUNS, PICKUP_DETERMINIZATIONS, + CALL_RUNS, CALL_DETERMINIZATIONS, + PLAY_RUNS, PLAY_DETERMINIZATIONS, + ALONE_RUNS, ALONE_DETERMINIZATIONS) - // TODO: Use more robust library rather than command line arguments. - playerType, _ := strconv.Atoi(os.Args[1]) - samples, _ := strconv.Atoi(os.Args[2]) - player := players[playerType - 1] + // TODO: Use more robust library rather than command line arguments. + playerType, _ := strconv.Atoi(os.Args[1]) + samples, _ := strconv.Atoi(os.Args[2]) + player := players[playerType-1] - for i := 0; i < samples; i++ { - situation := deck.DrawN(6) + for i := 0; i < samples; i++ { + situation := deck.DrawN(6) - hand := situation[:5] - top := situation[5] + hand := situation[:5] + top := situation[5] - copyHand := make([]deck.Card, len(hand)) - copy(copyHand, hand) + copyHand := make([]deck.Card, len(hand)) + copy(copyHand, hand) - dealer := r.Intn(4) - pickup := player.Pickup(copyHand, top, dealer) + dealer := r.Intn(4) + pickup := player.Pickup(copyHand, top, dealer) - fmt.Printf("%v\t%s\t%d\t%t\n", hand, top, dealer, pickup) - } + fmt.Printf("%v\t%s\t%d\t%t\n", hand, top, dealer, pickup) + } } diff --git a/src/deck/deck.go b/src/deck/deck.go index 1108c44..ef65358 100644 --- a/src/deck/deck.go +++ b/src/deck/deck.go @@ -8,19 +8,18 @@ import "errors" * 52 card deck, H, D, S, C. */ type Suit string + const ( - H Suit = "H" - D Suit = "D" - S Suit = "S" - C Suit = "C" + H Suit = "H" + D Suit = "D" + S Suit = "S" + C Suit = "C" ) - /* * An array of all the suits. In order they are hearts, diamonds, spades, clubs. */ -var SUITS = [4]Suit { H, D, S, C, } - +var SUITS = [4]Suit{H, D, S, C} /* * Create a Suit from the input string. An error is provided if the input is not @@ -33,24 +32,23 @@ var SUITS = [4]Suit { H, D, S, C, } * Returns the converted Suit or an error if something went wrong. */ func CreateSuit(s string) (Suit, error) { - var res Suit - switch s { - case "H": - res = H - case "D": - res = D - case "S": - res = S - case "C": - res = C - default: - return H, errors.New("Input is not a valid suit.") - } - - return res, nil + var res Suit + switch s { + case "H": + res = H + case "D": + res = D + case "S": + res = S + case "C": + res = C + default: + return H, errors.New("Input is not a valid suit.") + } + + return res, nil } - /* * The left bower suit given the current suit. * @@ -58,21 +56,20 @@ func CreateSuit(s string) (Suit, error) { * The suit of the left bower if the right bower is this. */ func (s Suit) Left() Suit { - switch s { - case H: - return D - case D: - return H - case S: - return C - case C: - return S - } - - return H + switch s { + case H: + return D + case D: + return H + case S: + return C + case C: + return S + } + + return H } - /* * Convert the suit to a string representation. * @@ -80,31 +77,28 @@ func (s Suit) Left() Suit { * A string representation of the suit. */ func (s Suit) String() string { - return string(s) + return string(s) } - - /* * Define a Value type off the int type. Each Value corresponds to the different * cards used in euchre. A is high at value 14, and Nine is low at value 9. */ type Value int + const ( - Nine Value = iota + 9 - Ten - J - Q - K - A + Nine Value = iota + 9 + Ten + J + Q + K + A ) - /* * An array of all the values in ascending order of value. */ -var VALUES = [6]Value { Nine, Ten, J, Q, K, A } - +var VALUES = [6]Value{Nine, Ten, J, Q, K, A} /* * Compares two card values. The order of cards is: 9, 10, J, Q, K, A. If this @@ -120,10 +114,9 @@ var VALUES = [6]Value { Nine, Ten, J, Q, K, A } * than v2, respectively. */ func (v1 Value) Compare(v2 Value) int { - return int(v1) - int(v2) + return int(v1) - int(v2) } - /* * Returns a Value type from the input string. The mapping is evident from the * standard 52 card deck. @@ -136,28 +129,27 @@ func (v1 Value) Compare(v2 Value) int { * wrong. */ func CreateValue(s string) (Value, error) { - var res Value - switch s { - case "9": - res = Nine - case "10": - res = Ten - case "J": - res = J - case "Q": - res = Q - case "K": - res = K - case "A": - res = A - default: - return Nine, errors.New("Input does not represent a valid value.") - } - - return res, nil + var res Value + switch s { + case "9": + res = Nine + case "10": + res = Ten + case "J": + res = J + case "Q": + res = Q + case "K": + res = K + case "A": + res = A + default: + return Nine, errors.New("Input does not represent a valid value.") + } + + return res, nil } - /* * Converts a value type to a string. Nine goes to "9", Ten to "10", Q to "Q", * and so on. @@ -166,26 +158,24 @@ func CreateValue(s string) (Value, error) { * A string representation of the Value. Intuitive mapping. */ func (v Value) String() string { - switch v { - case Nine: - return "9" - case Ten: - return "10" - case J: - return "J" - case Q: - return "Q" - case K: - return "K" - case A: - return "A" - } - - return "" + switch v { + case Nine: + return "9" + case Ten: + return "10" + case J: + return "J" + case Q: + return "Q" + case K: + return "K" + case A: + return "A" + } + + return "" } - - /* * A Card represents a playing card from a standard 52 card deck. It consists of * a suit, such as Hearts (H), and a value such as J. The suit is represented by @@ -193,18 +183,16 @@ func (v Value) String() string { * [9, 14], where 14 is A, 13 is K, and so on. */ type Card struct { - Suit Suit - Value Value + Suit Suit + Value Value } - /* * Create an array of the all the cards in the euchre deck. */ var CARDS = createCards() var CARDS_SET = createCardsSet() - /* * Creates a card given the string in the format of {V}{S}, where V is the value * and S is the suit. @@ -218,25 +206,24 @@ var CARDS_SET = createCardsSet() * bubbled up. */ func CreateCard(s string) (Card, error) { - var card Card - var sErr, vErr error + var card Card + var sErr, vErr error - lastChar := len(s) - 1 - if lastChar < 0 { - lastChar = 0 - } + lastChar := len(s) - 1 + if lastChar < 0 { + lastChar = 0 + } - card.Suit, sErr = CreateSuit(s[lastChar:]) - card.Value, vErr = CreateValue(s[:lastChar]) + card.Suit, sErr = CreateSuit(s[lastChar:]) + card.Value, vErr = CreateValue(s[:lastChar]) - if sErr != nil || vErr != nil { - return card, errors.New("There was an error in the input.") - } + if sErr != nil || vErr != nil { + return card, errors.New("There was an error in the input.") + } - return card, nil + return card, nil } - /* * Converts a card to a string representation. * @@ -245,10 +232,9 @@ func CreateCard(s string) (Card, error) { * the value of the card and {S} is the Suit of the card. */ func (c Card) String() string { - return c.Value.String() + c.Suit.String() + return c.Value.String() + c.Suit.String() } - /* * Checks if a card is a trump card. This method accounts for the left bower * oddity in suits. @@ -262,10 +248,9 @@ func (c Card) String() string { * and the value of the card is J. */ func (c Card) IsTrump(t Suit) bool { - return c.AdjSuit(t) == t + return c.AdjSuit(t) == t } - /* * Adjusts suit of this card based on the trump suit. This is only really * valuable when it matters if the card can be the left bower. In this case, @@ -279,15 +264,14 @@ func (c Card) IsTrump(t Suit) bool { * The effective suit of the card accounting for the left bower oddity. */ func (c Card) AdjSuit(t Suit) Suit { - adjSuit := c.Suit - if c.Value == J && c.Suit == t.Left() { - adjSuit = t - } + adjSuit := c.Suit + if c.Value == J && c.Suit == t.Left() { + adjSuit = t + } - return adjSuit + return adjSuit } - /* * A helper to compare the values of two cards. This is a wrapper around the * Card compare function. @@ -301,10 +285,9 @@ func (c Card) AdjSuit(t Suit) Suit { * card2 respectively. */ func ValueCompare(card1 Card, card2 Card) int { - return card1.Value.Compare(card2.Value) + return card1.Value.Compare(card2.Value) } - /* * A convenient helper to check if two cards have the same suit. This helps with * checking for the edge case of bowers. @@ -319,10 +302,9 @@ func ValueCompare(card1 Card, card2 Card) int { * both cards. */ func SameSuit(card1 Card, card2 Card, trump Suit) bool { - return card1.AdjSuit(trump) == card2.AdjSuit(trump) + return card1.AdjSuit(trump) == card2.AdjSuit(trump) } - /* * Creates a set of the cards, initialized to true. This method does not give * back a new set each time, there is only one global set behind it all. This @@ -333,14 +315,13 @@ func SameSuit(card1 Card, card2 Card, trump Suit) bool { * a euchre game are in this set. */ func NewCardsSet() map[Card]bool { - for k, _ := range CARDS_SET { - CARDS_SET[k] = true - } + for k, _ := range CARDS_SET { + CARDS_SET[k] = true + } - return CARDS_SET + return CARDS_SET } - /* * Creates a new card set with each value initialized to true. * @@ -349,12 +330,12 @@ func NewCardsSet() map[Card]bool { * a euchre game are in this set. */ func createCardsSet() map[Card]bool { - set := make(map[Card]bool) - for i := 0; i < len(CARDS); i++ { - set[CARDS[i]] = true - } + set := make(map[Card]bool) + for i := 0; i < len(CARDS); i++ { + set[CARDS[i]] = true + } - return set + return set } /* @@ -365,12 +346,12 @@ func createCardsSet() map[Card]bool { * A new array with the 24 cards used in euchre. */ func createCards() [24]Card { - var cards [24]Card - for i, value := range VALUES { - for j, suit := range SUITS { - cards[i * len(SUITS) + j] = Card { suit, value } - } - } - - return cards + var cards [24]Card + for i, value := range VALUES { + for j, suit := range SUITS { + cards[i*len(SUITS)+j] = Card{suit, value} + } + } + + return cards } diff --git a/src/deck/gen.go b/src/deck/gen.go index 6751210..70246b9 100644 --- a/src/deck/gen.go +++ b/src/deck/gen.go @@ -1,8 +1,8 @@ package deck import ( - "math/rand" - "time" + "math/rand" + "time" ) var r = rand.New(rand.NewSource(time.Now().UnixNano())) @@ -12,7 +12,6 @@ var r = rand.New(rand.NewSource(time.Now().UnixNano())) * hands and so on using the random package in go. */ - /* * Generates a random card. The suit is generated randomly and so is the value. * @@ -20,14 +19,13 @@ var r = rand.New(rand.NewSource(time.Now().UnixNano())) * A random deck.Card. */ func Draw() Card { - var card Card - card.Suit = SUITS[r.Intn(4)] - card.Value = VALUES[r.Intn(6)] + var card Card + card.Suit = SUITS[r.Intn(4)] + card.Value = VALUES[r.Intn(6)] - return card + return card } - /* * Randomly generates n unique cards. * @@ -38,12 +36,12 @@ func Draw() Card { * A slice of n random, unique cards. */ func DrawN(n int) []Card { - hand := make([]Card, n) - perm := r.Perm(len(CARDS)) + hand := make([]Card, n) + perm := r.Perm(len(CARDS)) - for i := 0; i < n; i++ { - hand[i] = CARDS[perm[i]] - } + for i := 0; i < n; i++ { + hand[i] = CARDS[perm[i]] + } - return hand + return hand } diff --git a/src/deck/gen_test.go b/src/deck/gen_test.go index bac9069..082ed3a 100644 --- a/src/deck/gen_test.go +++ b/src/deck/gen_test.go @@ -2,40 +2,37 @@ package deck import "testing" - /* * Test the card drawing functionality. * TODO: Add test for more cards than in deck, and test for 0 cards. */ - /* * Test that all cards in the randomly drawn cards are unique. */ func TestDrawNUnique(t *testing.T) { - n := 5 - hand := DrawN(n) - present := make(map[Card]bool) - - for _, card := range hand { - if _, ok := present[card]; ok { - t.Errorf("%s has been seen twice.\n", card) - } - } + n := 5 + hand := DrawN(n) + present := make(map[Card]bool) + + for _, card := range hand { + if _, ok := present[card]; ok { + t.Errorf("%s has been seen twice.\n", card) + } + } } - /* * Test that the randomly drawn cards are of the expected length. */ func TestDrawNLength(t *testing.T) { - ns := []int { 1, 2, 3, 4, 5 } + ns := []int{1, 2, 3, 4, 5} - for _, n := range ns { - hand := DrawN(n) + for _, n := range ns { + hand := DrawN(n) - if len(hand) != n { - t.Errorf("Expected hand length to be %s but it is %s.\n", n, len(hand)) - } - } + if len(hand) != n { + t.Errorf("Expected hand length to be %s but it is %s.\n", n, len(hand)) + } + } } diff --git a/src/euchre/determinization_test.go b/src/euchre/determinization_test.go index 1bb087b..17fd8e5 100644 --- a/src/euchre/determinization_test.go +++ b/src/euchre/determinization_test.go @@ -1,190 +1,186 @@ package euchre import ( - "deck" - "fmt" - "testing" + "deck" + "fmt" + "testing" ) - /* * Test the determinization of the euchre State when there have been no cards * played in the current trick. */ func TestDeterminization(t *testing.T) { - setup := Setup { - 1, - 2, - false, - deck.Card { deck.C, deck.A }, - deck.S, - deck.Card { }, - -1, - } - - hand := []deck.Card { - deck.Card { deck.H, deck.A }, - deck.Card { deck.D, deck.Nine }, - deck.Card { deck.D, deck.A }, - deck.Card { deck.C, deck.Ten }, - } - - cards1 := []deck.Card { - deck.Card { deck.H, deck.K }, - deck.Card { deck.C, deck.Q }, - deck.Card { deck.H, deck.Q }, - deck.Card { deck.H, deck.Nine }, - } - - var played []deck.Card - - trick1 := Trick { - cards1, - 2, - deck.S, - -1, - } - - prior := []Trick { trick1 } - - state := NewUndeterminizedState(setup, 2, hand, played, prior) - state.Determinize() - - fmt.Printf("Sanity check: %v\n", state) + setup := Setup{ + 1, + 2, + false, + deck.Card{deck.C, deck.A}, + deck.S, + deck.Card{}, + -1, + } + + hand := []deck.Card{ + deck.Card{deck.H, deck.A}, + deck.Card{deck.D, deck.Nine}, + deck.Card{deck.D, deck.A}, + deck.Card{deck.C, deck.Ten}, + } + + cards1 := []deck.Card{ + deck.Card{deck.H, deck.K}, + deck.Card{deck.C, deck.Q}, + deck.Card{deck.H, deck.Q}, + deck.Card{deck.H, deck.Nine}, + } + + var played []deck.Card + + trick1 := Trick{ + cards1, + 2, + deck.S, + -1, + } + + prior := []Trick{trick1} + + state := NewUndeterminizedState(setup, 2, hand, played, prior) + state.Determinize() + + fmt.Printf("Sanity check: %v\n", state) } - /* * Test that a determinization in the middle of a trick will result in the * proper amount of cards in each hand. */ func TestDeterminizationMiddleOfTrick(t *testing.T) { - setup := Setup { - 1, - 2, - false, - deck.Card { deck.C, deck.A }, - deck.S, - deck.Card { }, - -1, - } - - hand := []deck.Card { - deck.Card { deck.H, deck.A }, - deck.Card { deck.D, deck.Nine }, - deck.Card { deck.D, deck.A }, - deck.Card { deck.C, deck.Ten }, - } - - cards1 := []deck.Card { - deck.Card { deck.H, deck.K }, - deck.Card { deck.C, deck.Q }, - deck.Card { deck.H, deck.Q }, - deck.Card { deck.H, deck.Nine }, - } - - played := []deck.Card { - deck.Card { deck.C, deck.A }, - deck.Card { deck.C, deck.Ten }, - } - - trick1 := Trick { - cards1, - 2, - deck.S, - -1, - } - - prior := []Trick { trick1 } - - state := NewUndeterminizedState(setup, 0, hand, played, prior) - state.Determinize() - - if len(state.Hands[0]) != 4 || len(state.Hands[1]) != 4 || - len(state.Hands[2]) != 3 || len(state.Hands[3]) != 3 { - t.Errorf("Incorrect number of cards in some players hand.\n") - } + setup := Setup{ + 1, + 2, + false, + deck.Card{deck.C, deck.A}, + deck.S, + deck.Card{}, + -1, + } + + hand := []deck.Card{ + deck.Card{deck.H, deck.A}, + deck.Card{deck.D, deck.Nine}, + deck.Card{deck.D, deck.A}, + deck.Card{deck.C, deck.Ten}, + } + + cards1 := []deck.Card{ + deck.Card{deck.H, deck.K}, + deck.Card{deck.C, deck.Q}, + deck.Card{deck.H, deck.Q}, + deck.Card{deck.H, deck.Nine}, + } + + played := []deck.Card{ + deck.Card{deck.C, deck.A}, + deck.Card{deck.C, deck.Ten}, + } + + trick1 := Trick{ + cards1, + 2, + deck.S, + -1, + } + + prior := []Trick{trick1} + + state := NewUndeterminizedState(setup, 0, hand, played, prior) + state.Determinize() + + if len(state.Hands[0]) != 4 || len(state.Hands[1]) != 4 || + len(state.Hands[2]) != 3 || len(state.Hands[3]) != 3 { + t.Errorf("Incorrect number of cards in some players hand.\n") + } } - /* * Test a problematic instance that is giving problems in generating next valid * states. */ func TestDeterminizationProblematic(t *testing.T) { - setup := Setup { - 1, - 0, - true, - deck.Card { deck.C, deck.K }, - deck.C, - deck.Card { }, - -1, - } - - cards1 := []deck.Card { - deck.Card { deck.C, deck.J }, - deck.Card { deck.C, deck.Ten }, - deck.Card { deck.C, deck.Q }, - deck.Card { deck.C, deck.K }, - } - - cards2 := []deck.Card { - deck.Card { deck.D, deck.A }, - deck.Card { deck.D, deck.K }, - deck.Card { deck.D, deck.Q }, - deck.Card { deck.D, deck.J }, - } - - cards3 := []deck.Card { - deck.Card { deck.D, deck.Ten }, - deck.Card { deck.C, deck.Nine }, - deck.Card { deck.C, deck.A }, - deck.Card { deck.S, deck.J }, - } - - played := []deck.Card { - deck.Card { deck.S, deck.Q }, - deck.Card { deck.S, deck.K }, - deck.Card { deck.H, deck.A }, - } - - hand := []deck.Card { - deck.Card { deck.S, deck.Nine }, - deck.Card { deck.S, deck.A }, - } - - - trick1 := Trick { - cards1, - 2, - deck.C, - -1, - } - - trick2 := Trick { - cards2, - 2, - deck.C, - -1, - } - - trick3 := Trick { - cards3, - 2, - deck.C, - -1, - } - - prior := []Trick { trick1, trick2, trick3 } - - state := NewUndeterminizedState(setup, 0, hand, played, prior) - state.Determinize() - - fmt.Printf("Sanity check: %v\n", state) - if len(state.Hands[0]) != 2 || len(state.Hands[1]) != 1 || - len(state.Hands[2]) != 1 || len(state.Hands[3]) != 1 { - t.Errorf("Incorrect number of cards in some players hand.\n") - } + setup := Setup{ + 1, + 0, + true, + deck.Card{deck.C, deck.K}, + deck.C, + deck.Card{}, + -1, + } + + cards1 := []deck.Card{ + deck.Card{deck.C, deck.J}, + deck.Card{deck.C, deck.Ten}, + deck.Card{deck.C, deck.Q}, + deck.Card{deck.C, deck.K}, + } + + cards2 := []deck.Card{ + deck.Card{deck.D, deck.A}, + deck.Card{deck.D, deck.K}, + deck.Card{deck.D, deck.Q}, + deck.Card{deck.D, deck.J}, + } + + cards3 := []deck.Card{ + deck.Card{deck.D, deck.Ten}, + deck.Card{deck.C, deck.Nine}, + deck.Card{deck.C, deck.A}, + deck.Card{deck.S, deck.J}, + } + + played := []deck.Card{ + deck.Card{deck.S, deck.Q}, + deck.Card{deck.S, deck.K}, + deck.Card{deck.H, deck.A}, + } + + hand := []deck.Card{ + deck.Card{deck.S, deck.Nine}, + deck.Card{deck.S, deck.A}, + } + + trick1 := Trick{ + cards1, + 2, + deck.C, + -1, + } + + trick2 := Trick{ + cards2, + 2, + deck.C, + -1, + } + + trick3 := Trick{ + cards3, + 2, + deck.C, + -1, + } + + prior := []Trick{trick1, trick2, trick3} + + state := NewUndeterminizedState(setup, 0, hand, played, prior) + state.Determinize() + + fmt.Printf("Sanity check: %v\n", state) + if len(state.Hands[0]) != 2 || len(state.Hands[1]) != 1 || + len(state.Hands[2]) != 1 || len(state.Hands[3]) != 1 { + t.Errorf("Incorrect number of cards in some players hand.\n") + } } diff --git a/src/euchre/euchre.go b/src/euchre/euchre.go index 6598f45..3cd972c 100644 --- a/src/euchre/euchre.go +++ b/src/euchre/euchre.go @@ -1,15 +1,14 @@ package euchre import ( - "ai" - "deck" - "math/rand" - "time" + "ai" + "deck" + "math/rand" + "time" ) var r = rand.New(rand.NewSource(time.Now().UnixNano())) - /* * Contains all the relevant information the setup portion of a euchre game. * This includes who was dealer, who called it up, what the top card was, if it @@ -20,283 +19,276 @@ var r = rand.New(rand.NewSource(time.Now().UnixNano())) * scenarios of interest. */ type Setup struct { - Dealer int - Caller int - PickedUp bool - Top deck.Card - Trump deck.Suit - Discard deck.Card - AlonePlayer int + Dealer int + Caller int + PickedUp bool + Top deck.Card + Trump deck.Suit + Discard deck.Card + AlonePlayer int } - - /* * A Trick in Euchre consists of the cards that were played and some context. * Namely, who led in the trick (using our familiar number designation), what * the trump suit was, and if anybody was going alone. */ type Trick struct { - Cards []deck.Card - Led int - Trump deck.Suit - Alone int + Cards []deck.Card + Led int + Trump deck.Suit + Alone int } - - /* * All informative state in the euchre state tree. A state contains all * information prior to this moment. */ type State struct { - Setup Setup - Player int - Hands [][]deck.Card - Played []deck.Card - Prior []Trick + Setup Setup + Player int + Hands [][]deck.Card + Played []deck.Card + Prior []Trick } - // TODO: Move state logic to another file. -var players2And3 = map[int]bool { - 2: true, - 3: true, +var players2And3 = map[int]bool{ + 2: true, + 3: true, } -var players1And3 = map[int]bool { - 1: true, - 3: true, +var players1And3 = map[int]bool{ + 1: true, + 3: true, } -var players1And2 = map[int]bool { - 1: true, - 2: true, +var players1And2 = map[int]bool{ + 1: true, + 2: true, } -var playerSubsets = map[int]map[int]bool { - 4: players1And2, - 5: players2And3, - 6: players1And3, +var playerSubsets = map[int]map[int]bool{ + 4: players1And2, + 5: players2And3, + 6: players1And3, } -var playerToSubsets = map[int][]int { - 1: []int { 1, 4, 6 }, - 2: []int { 2, 4, 5 }, - 3: []int { 3, 5, 6 }, +var playerToSubsets = map[int][]int{ + 1: []int{1, 4, 6}, + 2: []int{2, 4, 5}, + 3: []int{3, 5, 6}, } - /* * Create a determinized euchre state off of an incomplete (non-terminal) euchre * state. Information can range from the first move to the last move, and the * incomplete information will be filled in through a determinization process. */ func (s State) Determinize() { - cardsSet := deck.NewCardsSet() - left := len(cardsSet) - - // Remove all prior cards from contention. - for _, trick := range s.Prior { - for _, card := range trick.Cards { - cardsSet[card] = false - left-- - } - } - - // Remove all played cards from contention. - for _, card := range s.Played { - cardsSet[card] = false - left-- - } - - // Remove all known cards of a player's hand. - for _, card := range s.Hands[0] { - cardsSet[card] = false - left-- - } - - // Remove the top card from contention if it was flipped over, or remove - // the discarded card if you were the one who put it down. - if s.Setup.Dealer == 0 && s.Setup.PickedUp { - cardsSet[s.Setup.Discard] = false - left-- - } else if !s.Setup.PickedUp { - cardsSet[s.Setup.Top] = false - left-- - } - - // If the top card was picked up, and the top card has not already been - // excluded due to it already being played in the current or previous - // tricks, then remove it from contention. It can only be with the person - // who picked it up at this moment. - topPlayed := !cardsSet[s.Setup.Top] - // Add the card that was picked up, but not played yet to the dealer's hand. - if s.Setup.PickedUp && !topPlayed { - s.Hands[s.Setup.Dealer] = append(s.Hands[s.Setup.Dealer], s.Setup.Top) - cardsSet[s.Setup.Top] = false - left-- - } - - noSuits := noSuits(s.Prior, s.Setup.Trump) - availableCards := extractAvailableCards(cardsSet) - - // Assign each card to the list of players that it can be assigned to. - // Further keep track of how many options each player currently has, this - // way we do not run out of possible cards for each player. Also, we must - // keep track of each subset. The meaning of the keys is as follows: - // 1: player {1} - // 2: player {2} - // 3: player {3} - // 4: players {1, 2} - // 5: players {2, 3} - // 6: players {1, 3} - // At no point can the number of cards needed for a subset be less than the - // number of options for that subset. We do not need to include the whole - // set as a subset, because that must naturally have more options than - // needed cards if we are to have a valid game. - subsetOptions := make(map[int]int) - cardToPlayers := make(map[deck.Card]map[int]bool) - for _, card := range availableCards { - for i := 1; i < 4; i++ { - if _, ok := noSuits[i]; ok { - possible := true - for _, suit := range noSuits[i] { - if card.AdjSuit(s.Setup.Trump) == suit { - possible = false - break - } - - if !possible { - break - } - } - - if possible { - if cardToPlayers[card] == nil { - cardToPlayers[card] = make(map[int]bool) - } - cardToPlayers[card][i] = true - subsetOptions[i]++ - } - } else { - if cardToPlayers[card] == nil { - cardToPlayers[card] = make(map[int]bool) - } - cardToPlayers[card][i] = true - subsetOptions[i]++ - } - } - } - - // subsetOptions now has the singleton set counts. Now use cardToPlayers to - // compile the cardinality 2 subsets. - for idx, subset := range playerSubsets { - for _, players := range cardToPlayers { - union := intersectPlayerSets(players, subset) - if len(union) > 0 { - subsetOptions[idx]++ - } - } - } - - subsetHandSizes := make(map[int]int) - for i := 1; i < 4; i++ { - subsetHandSize := 5 - len(s.Prior) - len(s.Hands[i]) - - // If the current player has already played subtract one more from - // the current needed card count. - start := ((s.Player + 4) - len(s.Played)) % 4 - end := start + ((s.Player + 4) - start) % 4 - if (i >= start && i < end) || i + 4 < end { - subsetHandSize-- - } - subsetHandSizes[i] = subsetHandSize - } - subsetHandSizes[4] = subsetHandSizes[1] + subsetHandSizes[2] - subsetHandSizes[5] = subsetHandSizes[2] + subsetHandSizes[3] - subsetHandSizes[6] = subsetHandSizes[1] + subsetHandSizes[3] - - // The fact that a map is traversed randomly is needed, since otherwise the - // logic may be biased in what types of hands it produces. - notAtZero := map[int]bool { - 1: subsetHandSizes[1] != 0, - 2: subsetHandSizes[2] != 0, - 3: subsetHandSizes[3] != 0, - } - numAtZero := 0 - for _, v := range notAtZero { - if !v { - numAtZero++ - } - } - - for card, players := range cardToPlayers { - unFullPlayers := intersectPlayerSets(players, notAtZero) - - if len(unFullPlayers) > 0 { - shuffledUnFullPlayers := shufflePlayerSetKeys(unFullPlayers) - - brokenConstraints := false - chosen := -1 - for _, player := range shuffledUnFullPlayers { - brokenConstraints = false - - for _, other := range shuffledUnFullPlayers { - if other != player { - for _, subsetIdx := range playerToSubsets[other] { - offset := 0 - dualSubset, dualOk := playerSubsets[subsetIdx] - if dualOk { - if pres, ok := dualSubset[player]; pres && ok { - offset = 1 - } - } - brokenConstraints = brokenConstraints || subsetHandSizes[subsetIdx] - offset > subsetOptions[subsetIdx] - 1 - } - } - } - - if !brokenConstraints { - chosen = player - break - } - } - - // If this card can be chosen given the current constraints than - // assign it. - if chosen > 0 { - s.Hands[chosen] = append(s.Hands[chosen], card) - - for _, subsetIdx := range playerToSubsets[chosen] { - subsetHandSizes[subsetIdx]-- - } - - if subsetHandSizes[chosen] == 0 { - notAtZero[chosen] = false - numAtZero++ - } - - bitmap := 0 - for player, _ := range unFullPlayers { - for _, subsetIdx := range playerToSubsets[player] { - subtracted := (bitmap & (1 << uint(subsetIdx))) != 0 - if !subtracted { - subsetOptions[subsetIdx]-- - bitmap |= 1 << uint(subsetIdx) - } - } - } - } - - if numAtZero == 3 { - break - } - } - } + cardsSet := deck.NewCardsSet() + left := len(cardsSet) + + // Remove all prior cards from contention. + for _, trick := range s.Prior { + for _, card := range trick.Cards { + cardsSet[card] = false + left-- + } + } + + // Remove all played cards from contention. + for _, card := range s.Played { + cardsSet[card] = false + left-- + } + + // Remove all known cards of a player's hand. + for _, card := range s.Hands[0] { + cardsSet[card] = false + left-- + } + + // Remove the top card from contention if it was flipped over, or remove + // the discarded card if you were the one who put it down. + if s.Setup.Dealer == 0 && s.Setup.PickedUp { + cardsSet[s.Setup.Discard] = false + left-- + } else if !s.Setup.PickedUp { + cardsSet[s.Setup.Top] = false + left-- + } + + // If the top card was picked up, and the top card has not already been + // excluded due to it already being played in the current or previous + // tricks, then remove it from contention. It can only be with the person + // who picked it up at this moment. + topPlayed := !cardsSet[s.Setup.Top] + // Add the card that was picked up, but not played yet to the dealer's hand. + if s.Setup.PickedUp && !topPlayed { + s.Hands[s.Setup.Dealer] = append(s.Hands[s.Setup.Dealer], s.Setup.Top) + cardsSet[s.Setup.Top] = false + left-- + } + + noSuits := noSuits(s.Prior, s.Setup.Trump) + availableCards := extractAvailableCards(cardsSet) + + // Assign each card to the list of players that it can be assigned to. + // Further keep track of how many options each player currently has, this + // way we do not run out of possible cards for each player. Also, we must + // keep track of each subset. The meaning of the keys is as follows: + // 1: player {1} + // 2: player {2} + // 3: player {3} + // 4: players {1, 2} + // 5: players {2, 3} + // 6: players {1, 3} + // At no point can the number of cards needed for a subset be less than the + // number of options for that subset. We do not need to include the whole + // set as a subset, because that must naturally have more options than + // needed cards if we are to have a valid game. + subsetOptions := make(map[int]int) + cardToPlayers := make(map[deck.Card]map[int]bool) + for _, card := range availableCards { + for i := 1; i < 4; i++ { + if _, ok := noSuits[i]; ok { + possible := true + for _, suit := range noSuits[i] { + if card.AdjSuit(s.Setup.Trump) == suit { + possible = false + break + } + + if !possible { + break + } + } + + if possible { + if cardToPlayers[card] == nil { + cardToPlayers[card] = make(map[int]bool) + } + cardToPlayers[card][i] = true + subsetOptions[i]++ + } + } else { + if cardToPlayers[card] == nil { + cardToPlayers[card] = make(map[int]bool) + } + cardToPlayers[card][i] = true + subsetOptions[i]++ + } + } + } + + // subsetOptions now has the singleton set counts. Now use cardToPlayers to + // compile the cardinality 2 subsets. + for idx, subset := range playerSubsets { + for _, players := range cardToPlayers { + union := intersectPlayerSets(players, subset) + if len(union) > 0 { + subsetOptions[idx]++ + } + } + } + + subsetHandSizes := make(map[int]int) + for i := 1; i < 4; i++ { + subsetHandSize := 5 - len(s.Prior) - len(s.Hands[i]) + + // If the current player has already played subtract one more from + // the current needed card count. + start := ((s.Player + 4) - len(s.Played)) % 4 + end := start + ((s.Player+4)-start)%4 + if (i >= start && i < end) || i+4 < end { + subsetHandSize-- + } + subsetHandSizes[i] = subsetHandSize + } + subsetHandSizes[4] = subsetHandSizes[1] + subsetHandSizes[2] + subsetHandSizes[5] = subsetHandSizes[2] + subsetHandSizes[3] + subsetHandSizes[6] = subsetHandSizes[1] + subsetHandSizes[3] + + // The fact that a map is traversed randomly is needed, since otherwise the + // logic may be biased in what types of hands it produces. + notAtZero := map[int]bool{ + 1: subsetHandSizes[1] != 0, + 2: subsetHandSizes[2] != 0, + 3: subsetHandSizes[3] != 0, + } + numAtZero := 0 + for _, v := range notAtZero { + if !v { + numAtZero++ + } + } + + for card, players := range cardToPlayers { + unFullPlayers := intersectPlayerSets(players, notAtZero) + + if len(unFullPlayers) > 0 { + shuffledUnFullPlayers := shufflePlayerSetKeys(unFullPlayers) + + brokenConstraints := false + chosen := -1 + for _, player := range shuffledUnFullPlayers { + brokenConstraints = false + + for _, other := range shuffledUnFullPlayers { + if other != player { + for _, subsetIdx := range playerToSubsets[other] { + offset := 0 + dualSubset, dualOk := playerSubsets[subsetIdx] + if dualOk { + if pres, ok := dualSubset[player]; pres && ok { + offset = 1 + } + } + brokenConstraints = brokenConstraints || subsetHandSizes[subsetIdx]-offset > subsetOptions[subsetIdx]-1 + } + } + } + + if !brokenConstraints { + chosen = player + break + } + } + + // If this card can be chosen given the current constraints than + // assign it. + if chosen > 0 { + s.Hands[chosen] = append(s.Hands[chosen], card) + + for _, subsetIdx := range playerToSubsets[chosen] { + subsetHandSizes[subsetIdx]-- + } + + if subsetHandSizes[chosen] == 0 { + notAtZero[chosen] = false + numAtZero++ + } + + bitmap := 0 + for player, _ := range unFullPlayers { + for _, subsetIdx := range playerToSubsets[player] { + subtracted := (bitmap & (1 << uint(subsetIdx))) != 0 + if !subtracted { + subsetOptions[subsetIdx]-- + bitmap |= 1 << uint(subsetIdx) + } + } + } + } + + if numAtZero == 3 { + break + } + } + } } - /* * Creates a copy of this state. This copy is deep so the value returned is a * whole new state in memory with the same value as the caller. @@ -306,24 +298,23 @@ func (s State) Determinize() { * memory. In other words, a deep copy! */ func (s State) Copy() ai.State { - copyHands := copyAllHands(s) + copyHands := copyAllHands(s) - copyPlayed := make([]deck.Card, len(s.Played)) - copy(copyPlayed, s.Played) + copyPlayed := make([]deck.Card, len(s.Played)) + copy(copyPlayed, s.Played) - copyPrior := make([]Trick, len(s.Prior)) - copy(copyPrior, s.Prior) + copyPrior := make([]Trick, len(s.Prior)) + copy(copyPrior, s.Prior) - return State { - s.Setup, - s.Player, - copyHands, - copyPlayed, - copyPrior, - } + return State{ + s.Setup, + s.Player, + copyHands, + copyPlayed, + copyPrior, + } } - /* * Create a new state that only has the known information for any given instance * and is therefore undeterminized. @@ -342,24 +333,23 @@ func (s State) Copy() ai.State { * of the state have valid values. */ func NewUndeterminizedState(setup Setup, player int, hand, played []deck.Card, - prior []Trick) State { - // Create blank values for hands other than your own. - hands := make([][]deck.Card, 4) - hands[0] = hand - hands[1] = make([]deck.Card, 0) - hands[2] = make([]deck.Card, 0) - hands[3] = make([]deck.Card, 0) - - return State { - setup, - player, - hands, - played, - prior, - } + prior []Trick) State { + // Create blank values for hands other than your own. + hands := make([][]deck.Card, 4) + hands[0] = hand + hands[1] = make([]deck.Card, 0) + hands[2] = make([]deck.Card, 0) + hands[3] = make([]deck.Card, 0) + + return State{ + setup, + player, + hands, + played, + prior, + } } - /* * Create a new determinized state. This state may have missing information, * such as in the middle of euchre game. This is fine as long as the structure @@ -379,148 +369,143 @@ func NewUndeterminizedState(setup Setup, player int, hand, played []deck.Card, * information that is usually not known by an opponent is now assumed. */ func NewDeterminizedState(setup Setup, player int, hands [][]deck.Card, - played []deck.Card, prior []Trick) State { - return State { - setup, - player, - hands, - played, - prior, - } + played []deck.Card, prior []Trick) State { + return State{ + setup, + player, + hands, + played, + prior, + } } - /* * A TreeSearchEngine. This engine encapsulates all the game logic needed for * decision making in euchre in order to traverse the state tree. */ -type Engine struct { } - +type Engine struct{} func (engine Engine) Favorable(state ai.TSState) bool { - cState := state.(State) - return cState.Player % 2 == 0 + cState := state.(State) + return cState.Player%2 == 0 } - func (engine Engine) IsTerminal(state ai.TSState) bool { - cState := state.(State) - return len(cState.Played) == 0 && len(cState.Prior) == 5 + cState := state.(State) + return len(cState.Played) == 0 && len(cState.Prior) == 5 } - func (engine Engine) Successors(state ai.TSState) []ai.Move { - cState := state.(State) - nextMoves := make([]ai.Move, 0) - - curHand := cState.Hands[cState.Player] - possibleIdxs := Possible(curHand, cState.Played, cState.Setup.Trump) - - var nPlayed []deck.Card - var nPrior []Trick - var nPlayer int - nmPlayer := (cState.Player + 1) % 4 - - for _, idx := range possibleIdxs { - card := curHand[idx] - nHands := copyAllHands(cState) - - if len(cState.Played) < 3 { - // Copy the old played cards into memory and add the new card. - nPlayed = make([]deck.Card, len(cState.Played)) - copy(nPlayed, cState.Played) - nPlayed = append(nPlayed, card) - - // The prior tricks stay the same (no new tricks) and the next - // player is just the next modulo player. - nPrior = cState.Prior - nPlayer = nmPlayer - } else if len(cState.Played) == 3 { - // If this next card ends the trick then copy the old tricks over - // and make a new trick out of the current cards. - nPrior = make([]Trick, len(cState.Prior)) - copy(nPrior, cState.Prior) - - // TODO: Why does this work over this? - //cState.Played = append(cState.Played, card) - trickCards := make([]deck.Card, 3) - copy(trickCards, cState.Played) - trickCards = append(trickCards, card) - - nPlayed = make([]deck.Card, 0, 4) - // TODO: nmPlayer is not correct for AlonePlayers. - nPlayer = Winner(trickCards, cState.Setup.Trump, nmPlayer, - cState.Setup.AlonePlayer) - - nextPrior := Trick { - trickCards, - nmPlayer, - cState.Setup.Trump, - cState.Setup.AlonePlayer, - } - nPrior = append(nPrior, nextPrior) - } - - nHand := make([]deck.Card, len(curHand)) - copy(nHand, curHand) - nHand[idx] = nHand[len(nHand) - 1] - nHand = nHand[:len(nHand) - 1] - nHands[cState.Player] = nHand - - nextState := NewDeterminizedState(cState.Setup, nPlayer, nHands, - nPlayed, nPrior) - - nextMove := ai.Move { - card, - nextState, - } - nextMoves = append(nextMoves, nextMove) - } - - return nextMoves + cState := state.(State) + nextMoves := make([]ai.Move, 0) + + curHand := cState.Hands[cState.Player] + possibleIdxs := Possible(curHand, cState.Played, cState.Setup.Trump) + + var nPlayed []deck.Card + var nPrior []Trick + var nPlayer int + nmPlayer := (cState.Player + 1) % 4 + + for _, idx := range possibleIdxs { + card := curHand[idx] + nHands := copyAllHands(cState) + + if len(cState.Played) < 3 { + // Copy the old played cards into memory and add the new card. + nPlayed = make([]deck.Card, len(cState.Played)) + copy(nPlayed, cState.Played) + nPlayed = append(nPlayed, card) + + // The prior tricks stay the same (no new tricks) and the next + // player is just the next modulo player. + nPrior = cState.Prior + nPlayer = nmPlayer + } else if len(cState.Played) == 3 { + // If this next card ends the trick then copy the old tricks over + // and make a new trick out of the current cards. + nPrior = make([]Trick, len(cState.Prior)) + copy(nPrior, cState.Prior) + + // TODO: Why does this work over this? + //cState.Played = append(cState.Played, card) + trickCards := make([]deck.Card, 3) + copy(trickCards, cState.Played) + trickCards = append(trickCards, card) + + nPlayed = make([]deck.Card, 0, 4) + // TODO: nmPlayer is not correct for AlonePlayers. + nPlayer = Winner(trickCards, cState.Setup.Trump, nmPlayer, + cState.Setup.AlonePlayer) + + nextPrior := Trick{ + trickCards, + nmPlayer, + cState.Setup.Trump, + cState.Setup.AlonePlayer, + } + nPrior = append(nPrior, nextPrior) + } + + nHand := make([]deck.Card, len(curHand)) + copy(nHand, curHand) + nHand[idx] = nHand[len(nHand)-1] + nHand = nHand[:len(nHand)-1] + nHands[cState.Player] = nHand + + nextState := NewDeterminizedState(cState.Setup, nPlayer, nHands, + nPlayed, nPrior) + + nextMove := ai.Move{ + card, + nextState, + } + nextMoves = append(nextMoves, nextMove) + } + + return nextMoves } - func (engine Engine) Evaluation(state ai.TSState) float64 { - cState := state.(State) - - winCounts0 := 0 - winCounts1 := 0 - - for i := 0; i < len(cState.Prior); i++ { - trick := cState.Prior[i] - - w := Winner(trick.Cards, cState.Setup.Trump, trick.Led, - cState.Setup.AlonePlayer) - if w % 2 == 0 { - winCounts0++ - } else { - winCounts1++ - } - } - - // If a player calls going alone and wins all 5 tricks then they get 4 - // points. - if winCounts0 == 5 && cState.Setup.AlonePlayer % 2 == 0 { - return 4 - } else if winCounts1 == 5 && cState.Setup.AlonePlayer % 2 == 1 { - return 4 - } - - // If nobody who went alone won, but somebody won 5 hands or got euched then - // that's two points. - if winCounts0 == 5 || (winCounts0 >= 3 && cState.Setup.Caller % 2 == 1) { - return 2 - } else if winCounts0 == 0 || (winCounts0 < 3 && cState.Setup.Caller % 2 == 0) { - return -2 - } - - // For a normal win, that's worth one point. - if winCounts0 > winCounts1 { - return 1 - } else { - return -1 - } - - return 0 + cState := state.(State) + + winCounts0 := 0 + winCounts1 := 0 + + for i := 0; i < len(cState.Prior); i++ { + trick := cState.Prior[i] + + w := Winner(trick.Cards, cState.Setup.Trump, trick.Led, + cState.Setup.AlonePlayer) + if w%2 == 0 { + winCounts0++ + } else { + winCounts1++ + } + } + + // If a player calls going alone and wins all 5 tricks then they get 4 + // points. + if winCounts0 == 5 && cState.Setup.AlonePlayer%2 == 0 { + return 4 + } else if winCounts1 == 5 && cState.Setup.AlonePlayer%2 == 1 { + return 4 + } + + // If nobody who went alone won, but somebody won 5 hands or got euched then + // that's two points. + if winCounts0 == 5 || (winCounts0 >= 3 && cState.Setup.Caller%2 == 1) { + return 2 + } else if winCounts0 == 0 || (winCounts0 < 3 && cState.Setup.Caller%2 == 0) { + return -2 + } + + // For a normal win, that's worth one point. + if winCounts0 > winCounts1 { + return 1 + } else { + return -1 + } + + return 0 } diff --git a/src/euchre/euchre_mcts_test.go b/src/euchre/euchre_mcts_test.go index f5320ab..e9e39ab 100644 --- a/src/euchre/euchre_mcts_test.go +++ b/src/euchre/euchre_mcts_test.go @@ -1,59 +1,57 @@ package euchre import ( - "ai" - "deck" - "fmt" - "testing" + "ai" + "deck" + "fmt" + "testing" ) - /* * Tests the ai package in conjunction with the euchre package. Some of these * tests are not real tests, but are simple sanity checks whose output must be * manually checked. */ - /* * Tests the output for a run playout for the first card played by the computer. */ func TestRunPlayout(t *testing.T) { - setup := Setup { - 1, - 1, - true, - deck.Card { deck.D, deck.Nine }, - deck.D, - deck.Card{ }, - -1, - } - - hand := []deck.Card { - deck.Card { deck.H, deck.Nine }, - deck.Card { deck.H, deck.Ten }, - deck.Card { deck.S, deck.A }, - deck.Card { deck.D, deck.Q }, - deck.Card { deck.C, deck.Q }, - } - - played := []deck.Card { - deck.Card { deck.C, deck.J }, - deck.Card { deck.C, deck.A }, - } - - var prior []Trick - - s := NewUndeterminizedState(setup, 0, hand, played, prior) - s.Determinize() - n := ai.NewNode() - m := ai.Move { - nil, - s, - } - n.Value(m) - e := Engine{ } - - fmt.Println("Playout debug output") - ai.RunPlayoutDebug(n, e) + setup := Setup{ + 1, + 1, + true, + deck.Card{deck.D, deck.Nine}, + deck.D, + deck.Card{}, + -1, + } + + hand := []deck.Card{ + deck.Card{deck.H, deck.Nine}, + deck.Card{deck.H, deck.Ten}, + deck.Card{deck.S, deck.A}, + deck.Card{deck.D, deck.Q}, + deck.Card{deck.C, deck.Q}, + } + + played := []deck.Card{ + deck.Card{deck.C, deck.J}, + deck.Card{deck.C, deck.A}, + } + + var prior []Trick + + s := NewUndeterminizedState(setup, 0, hand, played, prior) + s.Determinize() + n := ai.NewNode() + m := ai.Move{ + nil, + s, + } + n.Value(m) + e := Engine{} + + fmt.Println("Playout debug output") + ai.RunPlayoutDebug(n, e) } diff --git a/src/euchre/gen.go b/src/euchre/gen.go index bc3f41d..ba27097 100644 --- a/src/euchre/gen.go +++ b/src/euchre/gen.go @@ -1,9 +1,7 @@ package euchre - import "deck" - /* * Create a random euchre situation. This means cards are randomly distributed * among the players, using our trusty player number assignment. @@ -13,14 +11,14 @@ import "deck" * in the last card slice. */ func GenSituation() [][]deck.Card { - cards := deck.DrawN(24) + cards := deck.DrawN(24) - hands := make([][]deck.Card, 5) - for i := 0; i < 4; i++ { - hands[i] = cards[i * 5: (i + 1) * 5] - } + hands := make([][]deck.Card, 5) + for i := 0; i < 4; i++ { + hands[i] = cards[i*5 : (i+1)*5] + } - hands[4] = cards[20:] + hands[4] = cards[20:] - return hands + return hands } diff --git a/src/euchre/logic.go b/src/euchre/logic.go index 123e6e4..b4f5142 100644 --- a/src/euchre/logic.go +++ b/src/euchre/logic.go @@ -1,10 +1,9 @@ package euchre import ( - "deck" + "deck" ) - /* * Returns whether a beats b given the current trump suit. a and b are assumed * to be different cards. Also it is assumed a leads before b, such that if a @@ -19,45 +18,44 @@ import ( * True if a beats b, if a is led and we are given the trump suit. */ func Beat(a deck.Card, b deck.Card, trump deck.Suit) bool { - var res bool - // If a is a trump card but b is not, then a wins. - if (a.AdjSuit(trump) == trump && b.AdjSuit(trump) != trump) || - (a.AdjSuit(trump) != trump && b.AdjSuit(trump) == trump) { - res = a.AdjSuit(trump) == trump - } else if a.AdjSuit(trump) == trump && b.AdjSuit(trump) == trump { - // If a is a trump and so is b, then we must compare their values knowing - // that right and left bower are a rule. - if a.Value == deck.J || b.Value == deck.J { - // If a is right bower, then it must win. - if a.Value == deck.J && a.Suit == trump { - res = true - } else if a.Value == deck.J && a.Suit == trump.Left() { - // If a is left bower, then it wins as long as b is not the right - // bower. - res = b.Value != deck.J - } else { - // Otherwise, a is not a J, so it is b so b must win. - res = false - } - } else { - // If neither are one of the bowers, then the values of the cards are - // compared as normal. - res = a.Value.Compare(b.Value) > 0 - } - } else if a.Suit == b.Suit { - // Otherwise, if they are both the same and they are not both trump, then - // whoever has the higher value will win. - res = a.Value.Compare(b.Value) > 0 - } else { - // And lastly if they have different suits, then a wins automatically since - // b did not lead. - res = true - } - - return res + var res bool + // If a is a trump card but b is not, then a wins. + if (a.AdjSuit(trump) == trump && b.AdjSuit(trump) != trump) || + (a.AdjSuit(trump) != trump && b.AdjSuit(trump) == trump) { + res = a.AdjSuit(trump) == trump + } else if a.AdjSuit(trump) == trump && b.AdjSuit(trump) == trump { + // If a is a trump and so is b, then we must compare their values knowing + // that right and left bower are a rule. + if a.Value == deck.J || b.Value == deck.J { + // If a is right bower, then it must win. + if a.Value == deck.J && a.Suit == trump { + res = true + } else if a.Value == deck.J && a.Suit == trump.Left() { + // If a is left bower, then it wins as long as b is not the right + // bower. + res = b.Value != deck.J + } else { + // Otherwise, a is not a J, so it is b so b must win. + res = false + } + } else { + // If neither are one of the bowers, then the values of the cards are + // compared as normal. + res = a.Value.Compare(b.Value) > 0 + } + } else if a.Suit == b.Suit { + // Otherwise, if they are both the same and they are not both trump, then + // whoever has the higher value will win. + res = a.Value.Compare(b.Value) > 0 + } else { + // And lastly if they have different suits, then a wins automatically since + // b did not lead. + res = true + } + + return res } - /* * Given a player's current hand and the cards that have been played, the * possible cards for a player to play are returned. In other words, all cards @@ -74,25 +72,24 @@ func Beat(a deck.Card, b deck.Card, trump deck.Suit) bool { * The index of cards in hand that can be played according to euchre rules. */ func Possible(hand, played []deck.Card, trump deck.Suit) []int { - possible := make([]int, 0, len(hand)) - if len(played) > 0 { - for i := range hand { - if hand[i].AdjSuit(trump) == played[0].AdjSuit(trump) { - possible = append(possible, i) - } - } - } - - if len(possible) == 0 { - for i := range hand { - possible = append(possible, i) - } - } - - return possible + possible := make([]int, 0, len(hand)) + if len(played) > 0 { + for i := range hand { + if hand[i].AdjSuit(trump) == played[0].AdjSuit(trump) { + possible = append(possible, i) + } + } + } + + if len(possible) == 0 { + for i := range hand { + possible = append(possible, i) + } + } + + return possible } - /* * A function that returns the winning player (using the same number designation * as before) based on the trump suit, the cards that have been played, what the @@ -109,36 +106,35 @@ func Possible(hand, played []deck.Card, trump deck.Suit) []int { * The number designation of the person who won the trick. */ func Winner(played []deck.Card, trump deck.Suit, led int, alone int) int { - highPlayer := led - - if len(played) >= 2 { - highest := played[0] - for i, card := range played[1:] { - if !Beat(highest, card, trump) { - highest = card - highPlayer = (led + i + 1) % 4 - } - } - } - - nextAfterHigh := (highPlayer + 1) % 4 - if alone >= 0 && alone < 4 { - for player := led; player != nextAfterHigh; player = (player + 1) % 4 { - // If somewhere between the leading player, and the highPlayer the - // player who is cucked by their partner calling going alone is - // found, then the highPlayer should be moved up one, since one - // player is not actually playing. - if player == (alone + 2) % 4 { - highPlayer = (highPlayer + 1) % 4 - break - } - } - } - - return highPlayer + highPlayer := led + + if len(played) >= 2 { + highest := played[0] + for i, card := range played[1:] { + if !Beat(highest, card, trump) { + highest = card + highPlayer = (led + i + 1) % 4 + } + } + } + + nextAfterHigh := (highPlayer + 1) % 4 + if alone >= 0 && alone < 4 { + for player := led; player != nextAfterHigh; player = (player + 1) % 4 { + // If somewhere between the leading player, and the highPlayer the + // player who is cucked by their partner calling going alone is + // found, then the highPlayer should be moved up one, since one + // player is not actually playing. + if player == (alone+2)%4 { + highPlayer = (highPlayer + 1) % 4 + break + } + } + } + + return highPlayer } - /* * Provides the index of the card that wins out of the provided played cards and * current trump. @@ -152,23 +148,22 @@ func Winner(played []deck.Card, trump deck.Suit, led int, alone int) int { * no cards were played, -1 is returned instead. */ func WinnerIdx(played []deck.Card, trump deck.Suit) int { - highIdx := -1 - - if len(played) > 0 { - highIdx = 0 - highest := played[0] - for i, card := range played[1:] { - if !Beat(highest, card, trump) { - highest = card - highIdx = i + 1 - } - } - } - - return highIdx + highIdx := -1 + + if len(played) > 0 { + highIdx = 0 + highest := played[0] + for i, card := range played[1:] { + if !Beat(highest, card, trump) { + highest = card + highIdx = i + 1 + } + } + } + + return highIdx } - /* * Given the current played cards, the current player's turn and who is going * alone, find what player led the hand. @@ -183,21 +178,20 @@ func WinnerIdx(played []deck.Card, trump deck.Suit) int { * current list of played cards. */ func Leader(played []deck.Card, player, alone int) int { - leader := (player + 4 - len(played)) % 4 - - if alone >= 0 { - empty := (alone + 2) % 4 - if (player > leader && empty >= leader && empty < player) || - (player < leader && ((player + 4 > empty && empty >= leader) || - (player > empty && empty + 4 >= leader))) { - leader-- - } - } - - return leader + leader := (player + 4 - len(played)) % 4 + + if alone >= 0 { + empty := (alone + 2) % 4 + if (player > leader && empty >= leader && empty < player) || + (player < leader && ((player+4 > empty && empty >= leader) || + (player > empty && empty+4 >= leader))) { + leader-- + } + } + + return leader } - /* * Given the current player cards and the last player's turn, and who is going * alone, find out what player led the hand. @@ -212,10 +206,9 @@ func Leader(played []deck.Card, player, alone int) int { * played. */ func LeaderInclusive(played []deck.Card, player, alone int) int { - return Leader(played[:len(played) - 1], player, alone) + return Leader(played[:len(played)-1], player, alone) } - /* * Creates a new copy of the hands in memory from the given state. * @@ -226,18 +219,17 @@ func LeaderInclusive(played []deck.Card, player, alone int) int { * A copy of the players hands. A [][]deck.Card copy. */ func copyAllHands(state State) [][]deck.Card { - copyHands := make([][]deck.Card, len(state.Hands)) - for i, hand := range state.Hands { - copyHand := make([]deck.Card, len(hand)) - copy(copyHand, hand) + copyHands := make([][]deck.Card, len(state.Hands)) + for i, hand := range state.Hands { + copyHand := make([]deck.Card, len(hand)) + copy(copyHand, hand) - copyHands[i] = copyHand - } + copyHands[i] = copyHand + } - return copyHands + return copyHands } - /* * Extracts all cards that exist in the given set and have a value of true, into * a list of cards. @@ -250,17 +242,16 @@ func copyAllHands(state State) [][]deck.Card { * A slice of the existing cards in the given set. */ func extractAvailableCards(cardsSet map[deck.Card]bool) []deck.Card { - cards := make([]deck.Card, 0, len(cardsSet)) - for card, exists := range cardsSet { - if exists { - cards = append(cards, card) - } - } - - return cards + cards := make([]deck.Card, 0, len(cardsSet)) + for card, exists := range cardsSet { + if exists { + cards = append(cards, card) + } + } + + return cards } - /* * A private helper method that returns what suits a given player cannot have. * @@ -273,39 +264,39 @@ func extractAvailableCards(cardsSet map[deck.Card]bool) []deck.Card { * in a map. */ func noSuits(prior []Trick, trump deck.Suit) map[int][]deck.Suit { - noSuits := make(map[int][]deck.Suit) - - for i := 0; i < len(prior); i++ { - // For each trick, find out if a user did not follow suit and therefore - // does not have this suit. - trick := prior[i] - alonePlayer := trick.Alone >= 0 && trick.Alone < 4 - first := trick.Cards[0] - - for player := 0; player < 4; player++ { - if alonePlayer && player == (trick.Alone + 2) % 4 { - continue - } - - // If between the leading player and the current player, there - // exists the player who is not playing because his partner is - // playing alone. - adjust := 0 - for j := trick.Led; j != player; j = (j + 1) % 4 { - if alonePlayer && j == (trick.Alone + 2) % 4 { - adjust = -1 - break - } - } - - // Add 4 to player, so that it is guaranteed to be after trick.Led, - // but it does not change the final result mod 4. - playedCard := trick.Cards[(player + 4 - trick.Led + adjust) % 4] - if first.AdjSuit(trump) != playedCard.AdjSuit(trump) { - noSuits[player] = append(noSuits[player], first.AdjSuit(trump)) - } - } - } - - return noSuits + noSuits := make(map[int][]deck.Suit) + + for i := 0; i < len(prior); i++ { + // For each trick, find out if a user did not follow suit and therefore + // does not have this suit. + trick := prior[i] + alonePlayer := trick.Alone >= 0 && trick.Alone < 4 + first := trick.Cards[0] + + for player := 0; player < 4; player++ { + if alonePlayer && player == (trick.Alone+2)%4 { + continue + } + + // If between the leading player and the current player, there + // exists the player who is not playing because his partner is + // playing alone. + adjust := 0 + for j := trick.Led; j != player; j = (j + 1) % 4 { + if alonePlayer && j == (trick.Alone+2)%4 { + adjust = -1 + break + } + } + + // Add 4 to player, so that it is guaranteed to be after trick.Led, + // but it does not change the final result mod 4. + playedCard := trick.Cards[(player+4-trick.Led+adjust)%4] + if first.AdjSuit(trump) != playedCard.AdjSuit(trump) { + noSuits[player] = append(noSuits[player], first.AdjSuit(trump)) + } + } + } + + return noSuits } diff --git a/src/euchre/logic_test.go b/src/euchre/logic_test.go index e71960f..1fac7db 100644 --- a/src/euchre/logic_test.go +++ b/src/euchre/logic_test.go @@ -1,254 +1,244 @@ package euchre import ( - "deck" - "fmt" - "testing" + "deck" + "fmt" + "testing" ) - type beatTest struct { - a deck.Card - b deck.Card - trump deck.Suit - expected bool + a deck.Card + b deck.Card + trump deck.Suit + expected bool } - type leaderTest struct { - played []deck.Card - player int - alone int - expected int + played []deck.Card + player int + alone int + expected int } - type noSuitsTest struct { - prior []Trick - trump deck.Suit - expected map[int][]deck.Suit + prior []Trick + trump deck.Suit + expected map[int][]deck.Suit } - type possibleTest struct { - hand []deck.Card - played []deck.Card - trump deck.Suit - expected []int + hand []deck.Card + played []deck.Card + trump deck.Suit + expected []int } - type winnerTest struct { - played []deck.Card - trump deck.Suit - led int - alone int - expected int + played []deck.Card + trump deck.Suit + led int + alone int + expected int } - /* * Test Beat. A helper to determine which card wins in a head to head faceoff * where one card is chosen to be leading and there is a defined trump suit. */ -var beatTests = []beatTest { - // Trump beats non-trump - beatTest { - deck.Card { deck.H, deck.Ten }, - deck.Card { deck.C, deck.A }, - deck.H, - true, - }, - - // Trump beats non-trump v2, with orders flipped - beatTest { - deck.Card { deck.S, deck.Q }, - deck.Card { deck.C, deck.A }, - deck.C, - false, - }, - - // If suits don't match and no trumps, then leader wins - beatTest { - deck.Card { deck.S, deck.Nine }, - deck.Card { deck.C, deck.A }, - deck.D, - true, - }, - - // The higher card of the same suit wins - beatTest { - deck.Card { deck.S, deck.A }, - deck.Card { deck.S, deck.Nine }, - deck.D, - true, - }, - - // The right bower beats the left bower - beatTest { - deck.Card { deck.H, deck.J }, - deck.Card { deck.D, deck.J }, - deck.D, - false, - }, +var beatTests = []beatTest{ + // Trump beats non-trump + beatTest{ + deck.Card{deck.H, deck.Ten}, + deck.Card{deck.C, deck.A}, + deck.H, + true, + }, + + // Trump beats non-trump v2, with orders flipped + beatTest{ + deck.Card{deck.S, deck.Q}, + deck.Card{deck.C, deck.A}, + deck.C, + false, + }, + + // If suits don't match and no trumps, then leader wins + beatTest{ + deck.Card{deck.S, deck.Nine}, + deck.Card{deck.C, deck.A}, + deck.D, + true, + }, + + // The higher card of the same suit wins + beatTest{ + deck.Card{deck.S, deck.A}, + deck.Card{deck.S, deck.Nine}, + deck.D, + true, + }, + + // The right bower beats the left bower + beatTest{ + deck.Card{deck.H, deck.J}, + deck.Card{deck.D, deck.J}, + deck.D, + false, + }, } - /* * Run all the beat tests defined above and output any errors in reference to * the expected and actual values and the index of the test. */ func TestBeat(t *testing.T) { - for i, test := range beatTests { - res := Beat(test.a, test.b, test.trump) + for i, test := range beatTests { + res := Beat(test.a, test.b, test.trump) - if res != test.expected { - errorOut(t, test.expected, res, "beat", i) - } - } + if res != test.expected { + errorOut(t, test.expected, res, "beat", i) + } + } } - -var leaderTests = []leaderTest { - /* - * Routine test. - */ - leaderTest { - []deck.Card { - deck.Card { deck.C, deck.J }, - }, - 0, - -1, - 3, - }, - - /* - * Test for wrapping of current player and leader. - */ - leaderTest { - []deck.Card { - deck.Card { deck.S, deck.K }, - deck.Card { deck.S, deck.A }, - }, - 0, - -1, - 2, - }, - - leaderTest { - []deck.Card { - deck.Card { deck.S, deck.A }, - deck.Card { deck.S, deck.K }, - deck.Card { deck.S, deck.Q }, - }, - 2, - -1, - 3, - }, - - /* - * Test for alone player when it that person has not played yet. - */ - leaderTest { - []deck.Card { - deck.Card { deck.H, deck.Nine }, - }, - 0, - 2, - 3, - }, - - /* - * Test for alone player being the current player. - */ - leaderTest { - []deck.Card { - deck.Card { deck.C, deck.Ten }, - deck.Card { deck.C, deck.J }, - }, - 1, - 1, - 2, - }, - - /* - * Another test for the alone player having to be considered. - */ - leaderTest { - []deck.Card { - deck.Card { deck.H, deck.A }, - }, - 3, - 0, - 1, - }, - - /* - * More alone player tests. - */ - leaderTest { - []deck.Card { - deck.Card { deck.C, deck.Ten }, - }, - 1, - 1, - 0, - }, - - leaderTest { - []deck.Card { - deck.Card { deck.C, deck.Ten }, - deck.Card { deck.H, deck.A }, - }, - 0, - 1, - 1, - }, - - leaderTest { - []deck.Card { - deck.Card { deck.H, deck.Ten }, - deck.Card { deck.H, deck.J }, - }, - 3, - 3, - 0, - }, - - leaderTest { - []deck.Card { }, - 3, - 2, - 3, - }, - - leaderTest { - []deck.Card { - deck.Card { deck.H, deck.J }, - deck.Card { deck.H, deck.A }, - }, - 1, - 2, - 2, - }, +var leaderTests = []leaderTest{ + /* + * Routine test. + */ + leaderTest{ + []deck.Card{ + deck.Card{deck.C, deck.J}, + }, + 0, + -1, + 3, + }, + + /* + * Test for wrapping of current player and leader. + */ + leaderTest{ + []deck.Card{ + deck.Card{deck.S, deck.K}, + deck.Card{deck.S, deck.A}, + }, + 0, + -1, + 2, + }, + + leaderTest{ + []deck.Card{ + deck.Card{deck.S, deck.A}, + deck.Card{deck.S, deck.K}, + deck.Card{deck.S, deck.Q}, + }, + 2, + -1, + 3, + }, + + /* + * Test for alone player when it that person has not played yet. + */ + leaderTest{ + []deck.Card{ + deck.Card{deck.H, deck.Nine}, + }, + 0, + 2, + 3, + }, + + /* + * Test for alone player being the current player. + */ + leaderTest{ + []deck.Card{ + deck.Card{deck.C, deck.Ten}, + deck.Card{deck.C, deck.J}, + }, + 1, + 1, + 2, + }, + + /* + * Another test for the alone player having to be considered. + */ + leaderTest{ + []deck.Card{ + deck.Card{deck.H, deck.A}, + }, + 3, + 0, + 1, + }, + + /* + * More alone player tests. + */ + leaderTest{ + []deck.Card{ + deck.Card{deck.C, deck.Ten}, + }, + 1, + 1, + 0, + }, + + leaderTest{ + []deck.Card{ + deck.Card{deck.C, deck.Ten}, + deck.Card{deck.H, deck.A}, + }, + 0, + 1, + 1, + }, + + leaderTest{ + []deck.Card{ + deck.Card{deck.H, deck.Ten}, + deck.Card{deck.H, deck.J}, + }, + 3, + 3, + 0, + }, + + leaderTest{ + []deck.Card{}, + 3, + 2, + 3, + }, + + leaderTest{ + []deck.Card{ + deck.Card{deck.H, deck.J}, + deck.Card{deck.H, deck.A}, + }, + 1, + 2, + 2, + }, } - /* * */ func TestLeader(t *testing.T) { - for i, fixture := range leaderTests { - res := Leader(fixture.played, fixture.player, fixture.alone) + for i, fixture := range leaderTests { + res := Leader(fixture.played, fixture.player, fixture.alone) - if res != fixture.expected { - errorOut(t, fixture.expected, res, "leader", i) - } - } + if res != fixture.expected { + errorOut(t, fixture.expected, res, "leader", i) + } + } } - /* * Test noSuits. * @@ -256,700 +246,693 @@ func TestLeader(t *testing.T) { * inability to follow. */ -var noSuitsTests = []noSuitsTest { - // No tricks played yet returns no results. - noSuitsTest { - nil, - deck.H, - map[int][]deck.Suit { }, - }, - - // Routine check for sanity. - noSuitsTest { - []Trick { - Trick { - []deck.Card { - deck.Card { deck.S, deck.Nine }, - deck.Card { deck.D, deck.Ten }, - deck.Card { deck.S, deck.K }, - deck.Card { deck.H, deck.Nine }, - }, - 1, - deck.S, - -1, - }, - Trick { - []deck.Card { - deck.Card { deck.C, deck.K }, - deck.Card { deck.C, deck.A }, - deck.Card { deck.C, deck.Nine }, - deck.Card { deck.H, deck.A }, - }, - 3, - deck.S, - -1, - }, - }, - deck.S, - map[int][]deck.Suit { - 0: []deck.Suit { - deck.S, - }, - 2: []deck.Suit { - deck.S, - deck.C, - }, - }, - }, - - // Over two tricks, only one player does not follow suit. - noSuitsTest { - []Trick { - Trick { - []deck.Card { - deck.Card { deck.H, deck.A }, - deck.Card { deck.C, deck.K }, - deck.Card { deck.H, deck.K }, - deck.Card { deck.H, deck.Q }, - }, - 0, - deck.C, - -1, - }, - Trick { - []deck.Card { - deck.Card { deck.S, deck.K }, - deck.Card { deck.S, deck.Ten }, - deck.Card { deck.S, deck.Q }, - deck.Card { deck.S, deck.A }, - }, - 1, - deck.C, - -1, - }, - }, - deck.C, - map[int][]deck.Suit { - 1: []deck.Suit { - deck.H, - }, - }, - }, - - // Player 3 (last in modulo order) and 3 players in total are missing suits. - noSuitsTest { - []Trick { - Trick { - []deck.Card { - deck.Card { deck.H, deck.A }, - deck.Card { deck.C, deck.K }, - deck.Card { deck.H, deck.K }, - deck.Card { deck.H, deck.Q }, - }, - 0, - deck.C, - -1, - }, - Trick { - []deck.Card { - deck.Card { deck.S, deck.K }, - deck.Card { deck.S, deck.Ten }, - deck.Card { deck.C, deck.Ten }, - deck.Card { deck.C, deck.A }, - }, - 1, - deck.C, - -1, - }, - }, - deck.C, - map[int][]deck.Suit { - 1: []deck.Suit { - deck.H, - }, - 3: []deck.Suit { - deck.S, - }, - 0: []deck.Suit { - deck.S, - }, - }, - }, - - // Check that modulo player numbers work in keeping track. - noSuitsTest { - []Trick { - Trick { - []deck.Card { - deck.Card { deck.H, deck.A }, - deck.Card { deck.C, deck.K }, - deck.Card { deck.H, deck.K }, - deck.Card { deck.H, deck.Q }, - }, - 3, - deck.C, - -1, - }, - Trick { - []deck.Card { - deck.Card { deck.S, deck.K }, - deck.Card { deck.S, deck.Ten }, - deck.Card { deck.C, deck.Ten }, - deck.Card { deck.C, deck.A }, - }, - 0, - deck.C, - -1, - }, - }, - deck.C, - map[int][]deck.Suit { - 0: []deck.Suit { - deck.H, - }, - 2: []deck.Suit { - deck.S, - }, - 3: []deck.Suit { - deck.S, - }, - }, - }, - - // One player is missing more than one suit. - noSuitsTest { - []Trick { - Trick { - []deck.Card { - deck.Card { deck.D, deck.A }, - deck.Card { deck.S, deck.J }, - deck.Card { deck.D, deck.Ten }, - deck.Card { deck.D, deck.J }, - }, - 0, - deck.S, - -1, - }, - Trick { - []deck.Card { - deck.Card { deck.C, deck.A }, - deck.Card { deck.C, deck.Q }, - deck.Card { deck.C, deck.K }, - deck.Card { deck.S, deck.Ten }, - }, - 1, - deck.S, - -1, - }, - Trick { - []deck.Card { - deck.Card { deck.H, deck.K }, - deck.Card { deck.H, deck.Ten }, - deck.Card { deck.H, deck.Nine }, - deck.Card { deck.H, deck.A }, - }, - 0, - deck.S, - -1, - }, - Trick { - []deck.Card { - deck.Card { deck.H, deck.Q }, - deck.Card { deck.S, deck.A }, - deck.Card { deck.C, deck.Ten }, - deck.Card { deck.S, deck.K }, - }, - 3, - deck.S, - -1, - }, - }, - deck.S, - map[int][]deck.Suit { - 0: []deck.Suit { - deck.C, - deck.H, - }, - 1: []deck.Suit { - deck.D, - deck.H, - }, - 2: []deck.Suit { - deck.H, - }, - }, - }, - - // Going alone v1 - noSuitsTest { - []Trick { - Trick { - []deck.Card { - deck.Card { deck.C, deck.A }, - deck.Card { deck.C, deck.K }, - deck.Card { deck.H, deck.Nine }, - }, - 0, - deck.H, - 3, - }, - }, - deck.H, - map[int][]deck.Suit { - 3: []deck.Suit { - deck.C, - }, - }, - }, - - // Going alone v2, where we must pass the modulo wrap - noSuitsTest { - []Trick { - Trick { - []deck.Card { - deck.Card { deck.S, deck.J }, - deck.Card { deck.C, deck.J }, - deck.Card { deck.H, deck.Nine }, - }, - 3, - deck.S, - 2, - }, - }, - deck.S, - map[int][]deck.Suit { - 2: []deck.Suit { - deck.S, - }, - }, - }, - - // Going alone v3, where two players do not follow suit across two tricks. - noSuitsTest { - []Trick { - Trick { - []deck.Card { - deck.Card { deck.H, deck.K }, - deck.Card { deck.S, deck.Nine }, - deck.Card { deck.H, deck.A }, - }, - 1, - deck.S, - 2, - }, - Trick { - []deck.Card { - deck.Card { deck.S, deck.K }, - deck.Card { deck.S, deck.Nine }, - deck.Card { deck.D, deck.Q }, - }, - 2, - deck.S, - 2, - }, - }, - deck.S, - map[int][]deck.Suit { - 2: []deck.Suit { - deck.H, - }, - 1: []deck.Suit { - deck.S, - }, - }, - }, - - noSuitsTest { - []Trick { - Trick { - []deck.Card { - deck.Card { deck.C, deck.A }, - deck.Card { deck.C, deck.Q }, - deck.Card { deck.H, deck.Nine, }, - deck.Card { deck.C, deck.Ten, }, - }, - 0, - deck.C, - -1, - }, - }, - deck.C, - map[int][]deck.Suit { - 2: []deck.Suit { - deck.C, - }, - }, - }, - - noSuitsTest { - []Trick { - Trick { - []deck.Card { - deck.Card { deck.D, deck.J }, - deck.Card { deck.H, deck.J }, - deck.Card { deck.S, deck.A }, - deck.Card { deck.C, deck.Nine }, - }, - 2, - deck.D, - -1, - }, - }, - deck.D, - map[int][]deck.Suit { - 0: []deck.Suit { - deck.D, - }, - 1: []deck.Suit { - deck.D, - }, - }, - }, - - noSuitsTest { - []Trick { - Trick { - []deck.Card { - deck.Card { deck.H, deck.Ten }, - deck.Card { deck.D, deck.K }, - deck.Card { deck.H, deck.Nine }, - deck.Card { deck.D, deck.A }, - }, - 3, - deck.C, - -1, - }, - Trick { - []deck.Card { - deck.Card { deck.S, deck.K }, - deck.Card { deck.S, deck.A }, - deck.Card { deck.S, deck.Nine }, - deck.Card { deck.C, deck.A }, - }, - 3, - deck.C, - -1, - }, - }, - deck.C, - map[int][]deck.Suit { - 0: []deck.Suit { - deck.H, - }, - 2: []deck.Suit { - deck.H, - deck.S, - }, - }, - }, - +var noSuitsTests = []noSuitsTest{ + // No tricks played yet returns no results. + noSuitsTest{ + nil, + deck.H, + map[int][]deck.Suit{}, + }, + + // Routine check for sanity. + noSuitsTest{ + []Trick{ + Trick{ + []deck.Card{ + deck.Card{deck.S, deck.Nine}, + deck.Card{deck.D, deck.Ten}, + deck.Card{deck.S, deck.K}, + deck.Card{deck.H, deck.Nine}, + }, + 1, + deck.S, + -1, + }, + Trick{ + []deck.Card{ + deck.Card{deck.C, deck.K}, + deck.Card{deck.C, deck.A}, + deck.Card{deck.C, deck.Nine}, + deck.Card{deck.H, deck.A}, + }, + 3, + deck.S, + -1, + }, + }, + deck.S, + map[int][]deck.Suit{ + 0: []deck.Suit{ + deck.S, + }, + 2: []deck.Suit{ + deck.S, + deck.C, + }, + }, + }, + + // Over two tricks, only one player does not follow suit. + noSuitsTest{ + []Trick{ + Trick{ + []deck.Card{ + deck.Card{deck.H, deck.A}, + deck.Card{deck.C, deck.K}, + deck.Card{deck.H, deck.K}, + deck.Card{deck.H, deck.Q}, + }, + 0, + deck.C, + -1, + }, + Trick{ + []deck.Card{ + deck.Card{deck.S, deck.K}, + deck.Card{deck.S, deck.Ten}, + deck.Card{deck.S, deck.Q}, + deck.Card{deck.S, deck.A}, + }, + 1, + deck.C, + -1, + }, + }, + deck.C, + map[int][]deck.Suit{ + 1: []deck.Suit{ + deck.H, + }, + }, + }, + + // Player 3 (last in modulo order) and 3 players in total are missing suits. + noSuitsTest{ + []Trick{ + Trick{ + []deck.Card{ + deck.Card{deck.H, deck.A}, + deck.Card{deck.C, deck.K}, + deck.Card{deck.H, deck.K}, + deck.Card{deck.H, deck.Q}, + }, + 0, + deck.C, + -1, + }, + Trick{ + []deck.Card{ + deck.Card{deck.S, deck.K}, + deck.Card{deck.S, deck.Ten}, + deck.Card{deck.C, deck.Ten}, + deck.Card{deck.C, deck.A}, + }, + 1, + deck.C, + -1, + }, + }, + deck.C, + map[int][]deck.Suit{ + 1: []deck.Suit{ + deck.H, + }, + 3: []deck.Suit{ + deck.S, + }, + 0: []deck.Suit{ + deck.S, + }, + }, + }, + + // Check that modulo player numbers work in keeping track. + noSuitsTest{ + []Trick{ + Trick{ + []deck.Card{ + deck.Card{deck.H, deck.A}, + deck.Card{deck.C, deck.K}, + deck.Card{deck.H, deck.K}, + deck.Card{deck.H, deck.Q}, + }, + 3, + deck.C, + -1, + }, + Trick{ + []deck.Card{ + deck.Card{deck.S, deck.K}, + deck.Card{deck.S, deck.Ten}, + deck.Card{deck.C, deck.Ten}, + deck.Card{deck.C, deck.A}, + }, + 0, + deck.C, + -1, + }, + }, + deck.C, + map[int][]deck.Suit{ + 0: []deck.Suit{ + deck.H, + }, + 2: []deck.Suit{ + deck.S, + }, + 3: []deck.Suit{ + deck.S, + }, + }, + }, + + // One player is missing more than one suit. + noSuitsTest{ + []Trick{ + Trick{ + []deck.Card{ + deck.Card{deck.D, deck.A}, + deck.Card{deck.S, deck.J}, + deck.Card{deck.D, deck.Ten}, + deck.Card{deck.D, deck.J}, + }, + 0, + deck.S, + -1, + }, + Trick{ + []deck.Card{ + deck.Card{deck.C, deck.A}, + deck.Card{deck.C, deck.Q}, + deck.Card{deck.C, deck.K}, + deck.Card{deck.S, deck.Ten}, + }, + 1, + deck.S, + -1, + }, + Trick{ + []deck.Card{ + deck.Card{deck.H, deck.K}, + deck.Card{deck.H, deck.Ten}, + deck.Card{deck.H, deck.Nine}, + deck.Card{deck.H, deck.A}, + }, + 0, + deck.S, + -1, + }, + Trick{ + []deck.Card{ + deck.Card{deck.H, deck.Q}, + deck.Card{deck.S, deck.A}, + deck.Card{deck.C, deck.Ten}, + deck.Card{deck.S, deck.K}, + }, + 3, + deck.S, + -1, + }, + }, + deck.S, + map[int][]deck.Suit{ + 0: []deck.Suit{ + deck.C, + deck.H, + }, + 1: []deck.Suit{ + deck.D, + deck.H, + }, + 2: []deck.Suit{ + deck.H, + }, + }, + }, + + // Going alone v1 + noSuitsTest{ + []Trick{ + Trick{ + []deck.Card{ + deck.Card{deck.C, deck.A}, + deck.Card{deck.C, deck.K}, + deck.Card{deck.H, deck.Nine}, + }, + 0, + deck.H, + 3, + }, + }, + deck.H, + map[int][]deck.Suit{ + 3: []deck.Suit{ + deck.C, + }, + }, + }, + + // Going alone v2, where we must pass the modulo wrap + noSuitsTest{ + []Trick{ + Trick{ + []deck.Card{ + deck.Card{deck.S, deck.J}, + deck.Card{deck.C, deck.J}, + deck.Card{deck.H, deck.Nine}, + }, + 3, + deck.S, + 2, + }, + }, + deck.S, + map[int][]deck.Suit{ + 2: []deck.Suit{ + deck.S, + }, + }, + }, + + // Going alone v3, where two players do not follow suit across two tricks. + noSuitsTest{ + []Trick{ + Trick{ + []deck.Card{ + deck.Card{deck.H, deck.K}, + deck.Card{deck.S, deck.Nine}, + deck.Card{deck.H, deck.A}, + }, + 1, + deck.S, + 2, + }, + Trick{ + []deck.Card{ + deck.Card{deck.S, deck.K}, + deck.Card{deck.S, deck.Nine}, + deck.Card{deck.D, deck.Q}, + }, + 2, + deck.S, + 2, + }, + }, + deck.S, + map[int][]deck.Suit{ + 2: []deck.Suit{ + deck.H, + }, + 1: []deck.Suit{ + deck.S, + }, + }, + }, + + noSuitsTest{ + []Trick{ + Trick{ + []deck.Card{ + deck.Card{deck.C, deck.A}, + deck.Card{deck.C, deck.Q}, + deck.Card{deck.H, deck.Nine}, + deck.Card{deck.C, deck.Ten}, + }, + 0, + deck.C, + -1, + }, + }, + deck.C, + map[int][]deck.Suit{ + 2: []deck.Suit{ + deck.C, + }, + }, + }, + + noSuitsTest{ + []Trick{ + Trick{ + []deck.Card{ + deck.Card{deck.D, deck.J}, + deck.Card{deck.H, deck.J}, + deck.Card{deck.S, deck.A}, + deck.Card{deck.C, deck.Nine}, + }, + 2, + deck.D, + -1, + }, + }, + deck.D, + map[int][]deck.Suit{ + 0: []deck.Suit{ + deck.D, + }, + 1: []deck.Suit{ + deck.D, + }, + }, + }, + + noSuitsTest{ + []Trick{ + Trick{ + []deck.Card{ + deck.Card{deck.H, deck.Ten}, + deck.Card{deck.D, deck.K}, + deck.Card{deck.H, deck.Nine}, + deck.Card{deck.D, deck.A}, + }, + 3, + deck.C, + -1, + }, + Trick{ + []deck.Card{ + deck.Card{deck.S, deck.K}, + deck.Card{deck.S, deck.A}, + deck.Card{deck.S, deck.Nine}, + deck.Card{deck.C, deck.A}, + }, + 3, + deck.C, + -1, + }, + }, + deck.C, + map[int][]deck.Suit{ + 0: []deck.Suit{ + deck.H, + }, + 2: []deck.Suit{ + deck.H, + deck.S, + }, + }, + }, } - /* * Test the noSuits method. The expected result matches the actual result if the * maps contain the same content but not necessairily in the same order. */ func TestNoSuits(t *testing.T) { - for i, test := range noSuitsTests { - res := noSuits(test.prior, test.trump) - - // Check that the two maps are the same size. - if len(res) != len(test.expected) { - errorOut(t, test.expected, res, "no suits", i) - } - - // For each player check that the suits are the same. - for player, suits := range test.expected { - // Check if this player does exists in the actual results. - actualTmp, ok := res[player] - if !ok { - errorOut(t, test.expected, res, "no suits", i) - } - actual := suitsSliceToSet(actualTmp) - expected := suitsSliceToSet(suits) - - // Check that the two sets of suits are the same length, and then - // have the same contents. - if len(actual) != len(expected) { - errorOut(t, test.expected, res, "no suits", i) - } - - for suit, _ := range expected { - _, ok := actual[suit] - if !ok { - errorOut(t, test.expected, res, "no suits", i) - } - } - } - } + for i, test := range noSuitsTests { + res := noSuits(test.prior, test.trump) + + // Check that the two maps are the same size. + if len(res) != len(test.expected) { + errorOut(t, test.expected, res, "no suits", i) + } + + // For each player check that the suits are the same. + for player, suits := range test.expected { + // Check if this player does exists in the actual results. + actualTmp, ok := res[player] + if !ok { + errorOut(t, test.expected, res, "no suits", i) + } + actual := suitsSliceToSet(actualTmp) + expected := suitsSliceToSet(suits) + + // Check that the two sets of suits are the same length, and then + // have the same contents. + if len(actual) != len(expected) { + errorOut(t, test.expected, res, "no suits", i) + } + + for suit, _ := range expected { + _, ok := actual[suit] + if !ok { + errorOut(t, test.expected, res, "no suits", i) + } + } + } + } } - /* * Test Possible. This returns the cards that can be played given a hand and the * cards currently played. The actual cards are not returned, the indices of the * possible cards are provided. */ -var possibleTests = []possibleTest { - // Can't follow lead card, so all cards are possible. - possibleTest { - []deck.Card { - deck.Card { deck.H, deck.Ten }, - deck.Card { deck.H, deck.A }, - deck.Card { deck.C, deck.J }, - deck.Card { deck.C, deck.K }, - deck.Card { deck.D, deck.J }, - }, - []deck.Card { - deck.Card { deck.S, deck.Ten }, - }, - deck.H, - []int { - 0, 1, 2, 3, 4, - }, - }, - - // Can follow with one card. - possibleTest { - []deck.Card { - deck.Card { deck.H, deck.Ten }, - deck.Card { deck.H, deck.A }, - deck.Card { deck.C, deck.J }, - deck.Card { deck.C, deck.K }, - deck.Card { deck.S, deck.J }, - }, - []deck.Card { - deck.Card { deck.S, deck.Ten }, - }, - deck.H, - []int { - 4, - }, - }, +var possibleTests = []possibleTest{ + // Can't follow lead card, so all cards are possible. + possibleTest{ + []deck.Card{ + deck.Card{deck.H, deck.Ten}, + deck.Card{deck.H, deck.A}, + deck.Card{deck.C, deck.J}, + deck.Card{deck.C, deck.K}, + deck.Card{deck.D, deck.J}, + }, + []deck.Card{ + deck.Card{deck.S, deck.Ten}, + }, + deck.H, + []int{ + 0, 1, 2, 3, 4, + }, + }, + + // Can follow with one card. + possibleTest{ + []deck.Card{ + deck.Card{deck.H, deck.Ten}, + deck.Card{deck.H, deck.A}, + deck.Card{deck.C, deck.J}, + deck.Card{deck.C, deck.K}, + deck.Card{deck.S, deck.J}, + }, + []deck.Card{ + deck.Card{deck.S, deck.Ten}, + }, + deck.H, + []int{ + 4, + }, + }, } - func TestPossible(t *testing.T) { - for i, test := range possibleTests { - res := Possible(test.hand, test.played, test.trump) - - if len(res) != len(test.expected) { - errorOut(t, test.expected, res, "possible", i) - } - - for j := 0; j < len(test.expected); j++ { - if test.expected[j] != res[j] { - errorOut(t, test.expected, res, "possible", i) - } - } - } + for i, test := range possibleTests { + res := Possible(test.hand, test.played, test.trump) + + if len(res) != len(test.expected) { + errorOut(t, test.expected, res, "possible", i) + } + + for j := 0; j < len(test.expected); j++ { + if test.expected[j] != res[j] { + errorOut(t, test.expected, res, "possible", i) + } + } + } } - /* * Test Winner */ -var winnerTests = []winnerTest { - /* - * The start of the non-going alone tests (i.e. where everybody plays). - */ - - // Only one trump - winnerTest { - []deck.Card { - deck.Card { deck.D, deck.A }, - deck.Card { deck.H, deck.Q }, - deck.Card { deck.D, deck.Ten }, - deck.Card { deck.D, deck.Q }, - }, - deck.H, - 2, - -1, - 3, - }, - - // Only one trump v2 - winnerTest { - []deck.Card { - deck.Card { deck.H, deck.A }, - deck.Card { deck.H, deck.Nine }, - deck.Card { deck.S, deck.A }, - deck.Card { deck.D, deck.Q }, - }, - deck.D, - 2, - -1, - 1, - }, - - // Only one trump v3 - winnerTest { - []deck.Card { - deck.Card { deck.D, deck.Q }, - deck.Card { deck.D, deck.K }, - deck.Card { deck.C, deck.Ten }, - deck.Card { deck.D, deck.A }, - }, - deck.C, - 1, - -1, - 3, - }, - - // Only one trump v4 - winnerTest { - []deck.Card { - deck.Card { deck.H, deck.A }, - deck.Card { deck.H, deck.J }, - deck.Card { deck.H, deck.Q }, - deck.Card { deck.C, deck.Ten }, - }, - deck.C, - 2, - -1, - 1, - }, - - // Only one trump v5 - winnerTest { - []deck.Card { - deck.Card { deck.H, deck.A }, - deck.Card { deck.H, deck.J }, - deck.Card { deck.H, deck.Q }, - deck.Card { deck.C, deck.Ten }, - }, - deck.C, - 2, - -1, - 1, - }, - - // All non-trump - winnerTest { - []deck.Card { - deck.Card { deck.H, deck.Ten }, - deck.Card { deck.H, deck.A }, - deck.Card { deck.H, deck.Q }, - deck.Card { deck.H, deck.Ten }, - }, - deck.C, - 3, - -1, - 0, - }, - - // Multiple trump in one trick. - winnerTest { - []deck.Card { - deck.Card { deck.D, deck.J }, - deck.Card { deck.C, deck.Ten }, - deck.Card { deck.S, deck.J }, - deck.Card { deck.C, deck.A }, - }, - deck.C, - 1, - -1, - 3, - }, - - // Routine test - winnerTest { - []deck.Card { - deck.Card { deck.C, deck.Nine }, - deck.Card { deck.S, deck.A }, - deck.Card { deck.H, deck.Nine }, - deck.Card { deck.H, deck.Ten }, - }, - deck.H, - 2, - -1, - 1, - }, - - /* - * The start of going alone tests. - */ - - /* - * A test where the discarded player doesn't matter since the winner is - * before him. - */ - winnerTest { - []deck.Card { - deck.Card { deck.H, deck.A }, - deck.Card { deck.C, deck.Ten }, - deck.Card { deck.C, deck.Q }, - }, - deck.H, - 0, - 0, - 0, - }, - - /* - * A test where the winner is after the person who is cucked. - */ - winnerTest { - []deck.Card { - deck.Card { deck.D, deck.J }, - deck.Card { deck.H, deck.J }, - deck.Card { deck.S, deck.Q }, - }, - deck.H, - 1, - 0, - 3, - }, - - /* - * A test where the leader is the one going alone. - */ - winnerTest { - []deck.Card { - deck.Card { deck.H, deck.Q }, - deck.Card { deck.S, deck.A }, - deck.Card { deck.H, deck.A }, - }, - deck.H, - 1, - 1, - 0, - }, - - /* - * Just another normal test case for the winner when going alone. - */ - winnerTest { - []deck.Card { - deck.Card { deck.C, deck.Nine }, - deck.Card { deck.H, deck.Q }, - deck.Card { deck.S, deck.J }, - }, - deck.S, - 2, - 1, - 1, - }, +var winnerTests = []winnerTest{ + /* + * The start of the non-going alone tests (i.e. where everybody plays). + */ + + // Only one trump + winnerTest{ + []deck.Card{ + deck.Card{deck.D, deck.A}, + deck.Card{deck.H, deck.Q}, + deck.Card{deck.D, deck.Ten}, + deck.Card{deck.D, deck.Q}, + }, + deck.H, + 2, + -1, + 3, + }, + + // Only one trump v2 + winnerTest{ + []deck.Card{ + deck.Card{deck.H, deck.A}, + deck.Card{deck.H, deck.Nine}, + deck.Card{deck.S, deck.A}, + deck.Card{deck.D, deck.Q}, + }, + deck.D, + 2, + -1, + 1, + }, + + // Only one trump v3 + winnerTest{ + []deck.Card{ + deck.Card{deck.D, deck.Q}, + deck.Card{deck.D, deck.K}, + deck.Card{deck.C, deck.Ten}, + deck.Card{deck.D, deck.A}, + }, + deck.C, + 1, + -1, + 3, + }, + + // Only one trump v4 + winnerTest{ + []deck.Card{ + deck.Card{deck.H, deck.A}, + deck.Card{deck.H, deck.J}, + deck.Card{deck.H, deck.Q}, + deck.Card{deck.C, deck.Ten}, + }, + deck.C, + 2, + -1, + 1, + }, + + // Only one trump v5 + winnerTest{ + []deck.Card{ + deck.Card{deck.H, deck.A}, + deck.Card{deck.H, deck.J}, + deck.Card{deck.H, deck.Q}, + deck.Card{deck.C, deck.Ten}, + }, + deck.C, + 2, + -1, + 1, + }, + + // All non-trump + winnerTest{ + []deck.Card{ + deck.Card{deck.H, deck.Ten}, + deck.Card{deck.H, deck.A}, + deck.Card{deck.H, deck.Q}, + deck.Card{deck.H, deck.Ten}, + }, + deck.C, + 3, + -1, + 0, + }, + + // Multiple trump in one trick. + winnerTest{ + []deck.Card{ + deck.Card{deck.D, deck.J}, + deck.Card{deck.C, deck.Ten}, + deck.Card{deck.S, deck.J}, + deck.Card{deck.C, deck.A}, + }, + deck.C, + 1, + -1, + 3, + }, + + // Routine test + winnerTest{ + []deck.Card{ + deck.Card{deck.C, deck.Nine}, + deck.Card{deck.S, deck.A}, + deck.Card{deck.H, deck.Nine}, + deck.Card{deck.H, deck.Ten}, + }, + deck.H, + 2, + -1, + 1, + }, + + /* + * The start of going alone tests. + */ + + /* + * A test where the discarded player doesn't matter since the winner is + * before him. + */ + winnerTest{ + []deck.Card{ + deck.Card{deck.H, deck.A}, + deck.Card{deck.C, deck.Ten}, + deck.Card{deck.C, deck.Q}, + }, + deck.H, + 0, + 0, + 0, + }, + + /* + * A test where the winner is after the person who is cucked. + */ + winnerTest{ + []deck.Card{ + deck.Card{deck.D, deck.J}, + deck.Card{deck.H, deck.J}, + deck.Card{deck.S, deck.Q}, + }, + deck.H, + 1, + 0, + 3, + }, + + /* + * A test where the leader is the one going alone. + */ + winnerTest{ + []deck.Card{ + deck.Card{deck.H, deck.Q}, + deck.Card{deck.S, deck.A}, + deck.Card{deck.H, deck.A}, + }, + deck.H, + 1, + 1, + 0, + }, + + /* + * Just another normal test case for the winner when going alone. + */ + winnerTest{ + []deck.Card{ + deck.Card{deck.C, deck.Nine}, + deck.Card{deck.H, deck.Q}, + deck.Card{deck.S, deck.J}, + }, + deck.S, + 2, + 1, + 1, + }, } - /* * Run all the above tests to make sure the expected winner is actually seen. */ func TestWinner(t *testing.T) { - for _, test := range winnerTests { - res := Winner(test.played, test.trump, test.led, test.alone) + for _, test := range winnerTests { + res := Winner(test.played, test.trump, test.led, test.alone) - if res != test.expected { - t.Errorf("Expected error to be %d but got %d\n", test.expected, res) - } - } + if res != test.expected { + t.Errorf("Expected error to be %d but got %d\n", test.expected, res) + } + } } - /* * Converts a list of suits into a set of those suits. * @@ -962,16 +945,15 @@ func TestWinner(t *testing.T) { * the map. */ func suitsSliceToSet(suits []deck.Suit) map[deck.Suit]bool { - set := make(map[deck.Suit]bool) + set := make(map[deck.Suit]bool) - for _, suit := range suits { - set[suit] = true - } + for _, suit := range suits { + set[suit] = true + } - return set + return set } - /* * Errors out the given test. * @@ -983,7 +965,7 @@ func suitsSliceToSet(suits []deck.Suit) map[deck.Suit]bool { * index: The index of the test so that the exact test is known. */ func errorOut(t *testing.T, expected interface{}, actual interface{}, - test string, index int) { - testName := fmt.Sprintf("%s[%d]", test, index) - t.Errorf("Expected %v but got %v for %s\n", expected, actual, testName) + test string, index int) { + testName := fmt.Sprintf("%s[%d]", test, index) + t.Errorf("Expected %v but got %v for %s\n", expected, actual, testName) } diff --git a/src/euchre/util.go b/src/euchre/util.go index 34facac..3bff597 100644 --- a/src/euchre/util.go +++ b/src/euchre/util.go @@ -1,6 +1,5 @@ package euchre - /* * Intersect two sets that represent player indices. Keys that are mapped to a * true value in both sets are kept in the result. @@ -14,17 +13,16 @@ package euchre * both mapped to a true value. */ func intersectPlayerSets(s1 map[int]bool, s2 map[int]bool) map[int]bool { - result := make(map[int]bool) - for k, _ := range s1 { - if pres, ok := s2[k]; ok && pres { - result[k] = true - } - } + result := make(map[int]bool) + for k, _ := range s1 { + if pres, ok := s2[k]; ok && pres { + result[k] = true + } + } - return result + return result } - /* * Return a random player index from a set representing some player indices. * Only players that correspond to a true value will be considered. Stuff like @@ -37,15 +35,14 @@ func intersectPlayerSets(s1 map[int]bool, s2 map[int]bool) map[int]bool { * A random player index from the set from those that are mapped to true. */ func randomPlayerFromSet(s map[int]bool) int { - keys := make([]int, 0, len(s)) - for k, _ := range s { - keys = append(keys, k) - } + keys := make([]int, 0, len(s)) + for k, _ := range s { + keys = append(keys, k) + } - return keys[r.Intn(len(keys))] + return keys[r.Intn(len(keys))] } - /* * Shuffle the players in a set of players. * @@ -57,16 +54,16 @@ func randomPlayerFromSet(s map[int]bool) int { * is randomly ordered. */ func shufflePlayerSetKeys(m map[int]bool) []int { - src := make([]int, 0, len(m)) - for k, _ := range m { - src = append(src, k) - } + src := make([]int, 0, len(m)) + for k, _ := range m { + src = append(src, k) + } - dst := r.Perm(len(m)) - shuffled := make([]int, len(m)) - for i, item := range dst { - shuffled[i] = src[item] - } + dst := r.Perm(len(m)) + shuffled := make([]int, len(m)) + for i, item := range dst { + shuffled[i] = src[item] + } - return shuffled + return shuffled } diff --git a/src/player/player.go b/src/player/player.go index 102b718..f74496a 100644 --- a/src/player/player.go +++ b/src/player/player.go @@ -1,8 +1,8 @@ package player import ( - "deck" - "euchre" + "deck" + "euchre" ) /* @@ -20,87 +20,83 @@ import ( * through a euchre game. */ type Player interface { - /* - * Returns whether or not a player should tell the top card to be ordered up - * based on their current cards and who is picking it up. - * - * Args: - * hand: The 5 cards currently in the player's hand. - * top: The card on top of the kitty and currently in question to be - * picked up. - * who: Who is picking up the card (the dealer). The number designation - * for each player is as follows. Yourself(0), partner(2), opp to - * left (1), opp to right (3). So a clockwise order. - * - * Returns: - * Returns true if the card should be ordered up when the player gets the - * chance and false otherwise. - */ - Pickup(hand []deck.Card, top deck.Card, who int) bool + /* + * Returns whether or not a player should tell the top card to be ordered up + * based on their current cards and who is picking it up. + * + * Args: + * hand: The 5 cards currently in the player's hand. + * top: The card on top of the kitty and currently in question to be + * picked up. + * who: Who is picking up the card (the dealer). The number designation + * for each player is as follows. Yourself(0), partner(2), opp to + * left (1), opp to right (3). So a clockwise order. + * + * Returns: + * Returns true if the card should be ordered up when the player gets the + * chance and false otherwise. + */ + Pickup(hand []deck.Card, top deck.Card, who int) bool + /* + * Determines what to discard if a player has just picked up the top card + * after their deal. + * + * Args: + * hand: The 5 cards currently in the dealer's hand. + * top: The card that was on top and is now to be picked up. + * + * Returns: + * Returns the new hand after discarding and the card to be discarded. + */ + Discard(hand []deck.Card, top deck.Card) ([]deck.Card, deck.Card) - /* - * Determines what to discard if a player has just picked up the top card - * after their deal. - * - * Args: - * hand: The 5 cards currently in the dealer's hand. - * top: The card that was on top and is now to be picked up. - * - * Returns: - * Returns the new hand after discarding and the card to be discarded. - */ - Discard(hand []deck.Card, top deck.Card) ([]deck.Card, deck.Card) + /* + * Determines whether a player should call a certain suit, such as when all + * players have passed on picking it up. + * + * Args: + * hand: The 5 cards currently in the player's hand. + * top: The card that was passed by all players and was on the kitty. + * who: The player who dealt the cards. + * + * Returns: + * Returns the suit that should be called if given the chance. This result + * valid iff true is returned as well. Otherwise, the returned suit is + * meaningless. + */ + Call(hand []deck.Card, top deck.Card, who int) (deck.Suit, bool) + /* + * Determines whether the player should go alone on a certain hand. A player + * can only call "Going Alone!" when they called the card up. + * + * Args: + * hand: The player's current hand. + * top: The top card on top of the kitty. + * who: The player number designation for the dealer. + * + * Returns: + * True if the player should call "Going Alone!" and false otherwise. + */ + Alone(hand []deck.Card, top deck.Card, who int) bool - /* - * Determines whether a player should call a certain suit, such as when all - * players have passed on picking it up. - * - * Args: - * hand: The 5 cards currently in the player's hand. - * top: The card that was passed by all players and was on the kitty. - * who: The player who dealt the cards. - * - * Returns: - * Returns the suit that should be called if given the chance. This result - * valid iff true is returned as well. Otherwise, the returned suit is - * meaningless. - */ - Call(hand []deck.Card, top deck.Card, who int) (deck.Suit, bool) - - - /* - * Determines whether the player should go alone on a certain hand. A player - * can only call "Going Alone!" when they called the card up. - * - * Args: - * hand: The player's current hand. - * top: The top card on top of the kitty. - * who: The player number designation for the dealer. - * - * Returns: - * True if the player should call "Going Alone!" and false otherwise. - */ - Alone(hand []deck.Card, top deck.Card, who int) bool - - - /* - * Determines which card to play given the current euchre situation. This - * method removes the played card from the hand. - * - * Args: - * player: The current player number designation this player represents. - * setup: The setup of the euchre game before any tricks which consists - * of who was dealer, what the top card was, etc. - * hand: The cards currently in the user's hand. - * played: The cards that have already been played in this trick. - * prior: The cards that have been played in previous tricks. - * - * Args: - * Returns the user's new hand and the card that was chosen from the user's - * hand. - */ - Play(player int, setup euchre.Setup, hand []deck.Card, played []deck.Card, - prior []euchre.Trick) ([]deck.Card, deck.Card) + /* + * Determines which card to play given the current euchre situation. This + * method removes the played card from the hand. + * + * Args: + * player: The current player number designation this player represents. + * setup: The setup of the euchre game before any tricks which consists + * of who was dealer, what the top card was, etc. + * hand: The cards currently in the user's hand. + * played: The cards that have already been played in this trick. + * prior: The cards that have been played in previous tricks. + * + * Args: + * Returns the user's new hand and the card that was chosen from the user's + * hand. + */ + Play(player int, setup euchre.Setup, hand []deck.Card, played []deck.Card, + prior []euchre.Trick) ([]deck.Card, deck.Card) } diff --git a/src/player/player_test.go b/src/player/player_test.go index cd4db98..75f12ce 100644 --- a/src/player/player_test.go +++ b/src/player/player_test.go @@ -1,176 +1,170 @@ package player import ( - "deck" - "testing" + "deck" + "testing" ) - const ( - PICKUP_CONF = 0.6 - CALL_CONF = 0.6 - ALONE_CONF = 1.2 - PICKUP_RUNS = 5000 - PICKUP_DETERMINIZATIONS = 50 - CALL_RUNS = 5000 - CALL_DETERMINIZATIONS = 50 - PLAY_RUNS = 5000 - PLAY_DETERMINIZATIONS = 50 - ALONE_RUNS = 5000 - ALONE_DETERMINIZATIONS = 50 + PICKUP_CONF = 0.6 + CALL_CONF = 0.6 + ALONE_CONF = 1.2 + PICKUP_RUNS = 5000 + PICKUP_DETERMINIZATIONS = 50 + CALL_RUNS = 5000 + CALL_DETERMINIZATIONS = 50 + PLAY_RUNS = 5000 + PLAY_DETERMINIZATIONS = 50 + ALONE_RUNS = 5000 + ALONE_DETERMINIZATIONS = 50 ) - /* * Tests the different types of players functionality. */ - /* * A test that defines the inputs and the expected output for a given test case. */ type discardTest struct { - hand []deck.Card - top deck.Card - expected deck.Card + hand []deck.Card + top deck.Card + expected deck.Card } - -var discardTests = []discardTest { - /* - * Test that the lowest trump card is discarded if all cards are trump. - */ - discardTest { - []deck.Card { - deck.Card { deck.H, deck.J }, - deck.Card { deck.H, deck.Ten }, - deck.Card { deck.H, deck.Nine }, - deck.Card { deck.H, deck.A }, - deck.Card { deck.D, deck.J }, - }, - deck.Card { deck.H, deck.Q }, - deck.Card { deck.H, deck.Nine }, - }, - - /* - * Whitebox testing where trumps are in ascending order. The test is to see - * if the lowest of a trump suit will be chosen independent of order. - */ - discardTest { - []deck.Card { - deck.Card { deck.C, deck.Nine }, - deck.Card { deck.C, deck.Ten }, - deck.Card { deck.C, deck.Q }, - deck.Card { deck.C, deck.K }, - deck.Card { deck.C, deck.J }, - }, - deck.Card { deck.C, deck.A }, - deck.Card { deck.C, deck.Nine }, - }, - - /* - * Another whitebox test to assure that discarding properly handles bowers. - */ - discardTest { - []deck.Card { - deck.Card { deck.C, deck.J }, - deck.Card { deck.S, deck.J }, - deck.Card { deck.C, deck.Q }, - deck.Card { deck.C, deck.K }, - deck.Card { deck.C, deck.A }, - }, - deck.Card { deck.C, deck.Ten }, - deck.Card { deck.C, deck.Ten }, - }, - - /* - * When there is a suit with only one card in it, but it is an A, do not discard - * it since it is valuable. Discard the lowest card of a non-trump suit. - */ - discardTest { - []deck.Card { - deck.Card { deck.C, deck.A }, - deck.Card { deck.H, deck.Nine }, - deck.Card { deck.H, deck.J }, - deck.Card { deck.S, deck.K }, - deck.Card { deck.S, deck.Q }, - }, - deck.Card { deck.H, deck.Q }, - deck.Card { deck.S, deck.Q }, - }, - - /* - * Test that a suit with only one card is discarded. This makes sense if you - * have trumps. Otherwise, the lowest overall card should be dropped. - */ - discardTest { - []deck.Card { - deck.Card { deck.C, deck.Q }, - deck.Card { deck.H, deck.Nine }, - deck.Card { deck.H, deck.J }, - deck.Card { deck.S, deck.K }, - deck.Card { deck.S, deck.Q }, - }, - deck.Card { deck.H, deck.Q }, - deck.Card { deck.C, deck.Q }, - }, - - /* - * When there is only one card for a certain suit but no trumps, just - * discard the lowest valued card. - */ - discardTest { - []deck.Card { - deck.Card { deck.C, deck.Q }, - deck.Card { deck.H, deck.Nine }, - deck.Card { deck.H, deck.A }, - deck.Card { deck.S, deck.K }, - deck.Card { deck.S, deck.Q }, - }, - deck.Card { deck.D, deck.A }, - deck.Card { deck.H, deck.Nine }, - }, - - /* - * When there is more than one card that is non-ace, non-trump and solitary - * in its suit, then choose the smallest one. - */ - discardTest { - []deck.Card { - deck.Card { deck.D, deck.J }, - deck.Card { deck.S, deck.A }, - deck.Card { deck.H, deck.Nine }, - deck.Card { deck.S, deck.Q }, - deck.Card { deck.C, deck.J }, - }, - deck.Card { deck.S, deck.J }, - deck.Card { deck.H, deck.Nine }, - }, +var discardTests = []discardTest{ + /* + * Test that the lowest trump card is discarded if all cards are trump. + */ + discardTest{ + []deck.Card{ + deck.Card{deck.H, deck.J}, + deck.Card{deck.H, deck.Ten}, + deck.Card{deck.H, deck.Nine}, + deck.Card{deck.H, deck.A}, + deck.Card{deck.D, deck.J}, + }, + deck.Card{deck.H, deck.Q}, + deck.Card{deck.H, deck.Nine}, + }, + + /* + * Whitebox testing where trumps are in ascending order. The test is to see + * if the lowest of a trump suit will be chosen independent of order. + */ + discardTest{ + []deck.Card{ + deck.Card{deck.C, deck.Nine}, + deck.Card{deck.C, deck.Ten}, + deck.Card{deck.C, deck.Q}, + deck.Card{deck.C, deck.K}, + deck.Card{deck.C, deck.J}, + }, + deck.Card{deck.C, deck.A}, + deck.Card{deck.C, deck.Nine}, + }, + + /* + * Another whitebox test to assure that discarding properly handles bowers. + */ + discardTest{ + []deck.Card{ + deck.Card{deck.C, deck.J}, + deck.Card{deck.S, deck.J}, + deck.Card{deck.C, deck.Q}, + deck.Card{deck.C, deck.K}, + deck.Card{deck.C, deck.A}, + }, + deck.Card{deck.C, deck.Ten}, + deck.Card{deck.C, deck.Ten}, + }, + + /* + * When there is a suit with only one card in it, but it is an A, do not discard + * it since it is valuable. Discard the lowest card of a non-trump suit. + */ + discardTest{ + []deck.Card{ + deck.Card{deck.C, deck.A}, + deck.Card{deck.H, deck.Nine}, + deck.Card{deck.H, deck.J}, + deck.Card{deck.S, deck.K}, + deck.Card{deck.S, deck.Q}, + }, + deck.Card{deck.H, deck.Q}, + deck.Card{deck.S, deck.Q}, + }, + + /* + * Test that a suit with only one card is discarded. This makes sense if you + * have trumps. Otherwise, the lowest overall card should be dropped. + */ + discardTest{ + []deck.Card{ + deck.Card{deck.C, deck.Q}, + deck.Card{deck.H, deck.Nine}, + deck.Card{deck.H, deck.J}, + deck.Card{deck.S, deck.K}, + deck.Card{deck.S, deck.Q}, + }, + deck.Card{deck.H, deck.Q}, + deck.Card{deck.C, deck.Q}, + }, + + /* + * When there is only one card for a certain suit but no trumps, just + * discard the lowest valued card. + */ + discardTest{ + []deck.Card{ + deck.Card{deck.C, deck.Q}, + deck.Card{deck.H, deck.Nine}, + deck.Card{deck.H, deck.A}, + deck.Card{deck.S, deck.K}, + deck.Card{deck.S, deck.Q}, + }, + deck.Card{deck.D, deck.A}, + deck.Card{deck.H, deck.Nine}, + }, + + /* + * When there is more than one card that is non-ace, non-trump and solitary + * in its suit, then choose the smallest one. + */ + discardTest{ + []deck.Card{ + deck.Card{deck.D, deck.J}, + deck.Card{deck.S, deck.A}, + deck.Card{deck.H, deck.Nine}, + deck.Card{deck.S, deck.Q}, + deck.Card{deck.C, deck.J}, + }, + deck.Card{deck.S, deck.J}, + deck.Card{deck.H, deck.Nine}, + }, } - /* * The main driver for discard tests that runs all discardTests outlined above * globally on all testable players. */ func TestDiscard(t *testing.T) { - players := getTestablePlayers() - - for i, fixture := range discardTests { - for j, player := range players { - copyHand := make([]deck.Card, len(fixture.hand)) - copy(copyHand, fixture.hand) - - _, discarded := player.Discard(copyHand, fixture.top) - if discarded != fixture.expected { - t.Logf("Fixture %d, implementation %d failed.\n", i + 1, j + 1) - t.Errorf("Gave %s instead of %s.\n", discarded, fixture.expected) - } - } - } + players := getTestablePlayers() + + for i, fixture := range discardTests { + for j, player := range players { + copyHand := make([]deck.Card, len(fixture.hand)) + copy(copyHand, fixture.hand) + + _, discarded := player.Discard(copyHand, fixture.top) + if discarded != fixture.expected { + t.Logf("Fixture %d, implementation %d failed.\n", i+1, j+1) + t.Errorf("Gave %s instead of %s.\n", discarded, fixture.expected) + } + } + } } - /* * Returns a list of all the different Player implementations to test. * @@ -179,14 +173,14 @@ func TestDiscard(t *testing.T) { * order of the players is [rule, smart]. */ func getTestablePlayers() []Player { - rule := NewRule("") - smart := NewSmart(PICKUP_CONF, CALL_CONF, ALONE_CONF, - PICKUP_RUNS, PICKUP_DETERMINIZATIONS, - CALL_RUNS, CALL_DETERMINIZATIONS, - PLAY_RUNS, PLAY_DETERMINIZATIONS, - ALONE_RUNS, ALONE_DETERMINIZATIONS) + rule := NewRule("") + smart := NewSmart(PICKUP_CONF, CALL_CONF, ALONE_CONF, + PICKUP_RUNS, PICKUP_DETERMINIZATIONS, + CALL_RUNS, CALL_DETERMINIZATIONS, + PLAY_RUNS, PLAY_DETERMINIZATIONS, + ALONE_RUNS, ALONE_DETERMINIZATIONS) - players := []Player { rule, smart } + players := []Player{rule, smart} - return players + return players } diff --git a/src/player/rand.go b/src/player/rand.go index f871238..9c682c9 100644 --- a/src/player/rand.go +++ b/src/player/rand.go @@ -1,15 +1,14 @@ package player import ( - "deck" - "euchre" - "math/rand" - "time" + "deck" + "euchre" + "math/rand" + "time" ) var r = rand.New(rand.NewSource(time.Now().UnixNano())) - /* * A RandPlayer. This player is non deterministic, so for the same input, you * can get different outputs. The distribution of picking up, discarding or @@ -18,12 +17,11 @@ var r = rand.New(rand.NewSource(time.Now().UnixNano())) * have any meaning. */ type RandPlayer struct { - pickupProb float64 - callProb float64 - aloneProb float64 + pickupProb float64 + callProb float64 + aloneProb float64 } - /* * Used to create a new RandPlayer struct that is properly constructed. Returns * a RandPlayer pointer. @@ -36,72 +34,67 @@ type RandPlayer struct { * aloneProb: The probability that the player will go alone if he calls suit or * tells the dealer to pickup. */ -func NewRand(pickupProb float64, callProb float64, aloneProb float64) (*RandPlayer) { - return &RandPlayer{ - pickupProb, - callProb, - aloneProb, - } +func NewRand(pickupProb float64, callProb float64, aloneProb float64) *RandPlayer { + return &RandPlayer{ + pickupProb, + callProb, + aloneProb, + } } - /* * Player decides to pickup with probability pickupProb. */ func (p *RandPlayer) Pickup(hand []deck.Card, top deck.Card, who int) bool { - return r.Float64() < p.pickupProb + return r.Float64() < p.pickupProb } - /* * TODO: */ func (p *RandPlayer) Discard(hand []deck.Card, top deck.Card) ([]deck.Card, deck.Card) { - hand = append(hand, top) + hand = append(hand, top) - // Delete a random card not preserving order. - i := r.Intn(len(hand)) - chosen := hand[i] - hand[i] = hand[len(hand) - 1] - hand = hand[:len(hand) - 1] + // Delete a random card not preserving order. + i := r.Intn(len(hand)) + chosen := hand[i] + hand[i] = hand[len(hand)-1] + hand = hand[:len(hand)-1] - return hand, chosen + return hand, chosen } - /* * Player decision method to call suit once all other players have passed on * telling the dealer to pickup. */ func (p *RandPlayer) Call(hand []deck.Card, top deck.Card, - who int) (deck.Suit, bool) { - s := deck.SUITS[r.Intn(len(deck.SUITS))] - for s == top.Suit { - s = deck.SUITS[r.Intn(len(deck.SUITS))] - } + who int) (deck.Suit, bool) { + s := deck.SUITS[r.Intn(len(deck.SUITS))] + for s == top.Suit { + s = deck.SUITS[r.Intn(len(deck.SUITS))] + } - return s, r.Float64() < p.callProb + return s, r.Float64() < p.callProb } - /* * Player decision method to go alone or not. The player will go alone with * probability aloneProb. */ func (p *RandPlayer) Alone(hand []deck.Card, top deck.Card, who int) bool { - return r.Float64() < p.aloneProb + return r.Float64() < p.aloneProb } - func (p *RandPlayer) Play(player int, setup euchre.Setup, hand, - played []deck.Card, - prior []euchre.Trick) ([]deck.Card, deck.Card) { - playable := euchre.Possible(hand, played, setup.Trump) + played []deck.Card, + prior []euchre.Trick) ([]deck.Card, deck.Card) { + playable := euchre.Possible(hand, played, setup.Trump) - chosen := playable[r.Intn(len(playable))] - final := hand[chosen] - hand[chosen] = hand[len(hand) - 1] - hand = hand[:len(hand) - 1] + chosen := playable[r.Intn(len(playable))] + final := hand[chosen] + hand[chosen] = hand[len(hand)-1] + hand = hand[:len(hand)-1] - return hand, final + return hand, final } diff --git a/src/player/rule.go b/src/player/rule.go index dff3bf9..40c661d 100644 --- a/src/player/rule.go +++ b/src/player/rule.go @@ -1,14 +1,14 @@ package player import ( - "ai" - "bufio" - "deck" - "euchre" - "fmt" - "math/rand" - "os" - "time" + "ai" + "bufio" + "deck" + "euchre" + "fmt" + "math/rand" + "os" + "time" ) /* @@ -17,12 +17,11 @@ import ( * ai.Input interface. */ type Input struct { - Top deck.Card - Hand []deck.Card - Dealer int + Top deck.Card + Hand []deck.Card + Dealer int } - /* * Converts an input to a Perceptron used to tell a player to tell the dealer to * pick up a card or not to a vector of binary features. The slice is of size 12 @@ -45,102 +44,99 @@ type Input struct { * If the feature existed then the value is 1, and 0 otherwise. */ func (i Input) Features() []int { - features := make([]int, 12, 12) - - // The following maps create a relation between a certain feature and its - // position within the features vector. - indexes := map[deck.Value]int { - deck.Nine: 0, - deck.Ten: 1, - deck.J: 2, - deck.Q: 3, - deck.K: 4, - deck.A: 5, - } - - aces := map[deck.Suit]int { - deck.H: 7, - deck.D: 8, - deck.S: 9, - deck.C: 10, - } - - // Used to keep track of how many suits are in the hand. - suitsPresent := make(map[deck.Suit]int) - - for _, card := range i.Hand { - if card.Suit == i.Top.Suit { - features[indexes[card.Value]] = 1 - } else if card.Suit == i.Top.Suit.Left() && card.Value == deck.J { - features[6] = 1 - } else if card.Value == deck.A { - features[aces[card.Suit]] = 1 - } - - // Adjust suit count for left bower and increment the count for the - // current card's suit. - adjSuit := card.AdjSuit(i.Top.Suit) - if _, ok := suitsPresent[adjSuit]; ok { - suitsPresent[adjSuit] += 1 - } else { - suitsPresent[adjSuit] = 1 - } - } - - suitCount := len(suitsPresent) - // If the hand has less then 2 suits and no card is being picked up then one - // can be sure we have 2 suits. - if suitCount <= 2 && i.Dealer != 0 { - features[11] = 1 - } else if suitCount <= 3 && i.Dealer == 0 { - // Else, if there are less than 3 suits and we are picking up, one suit - // might be gotten rid of yet. Even if we had 2 suits we would to check that - // we already had the trump suit if we are picking up. - _, trumpPresent := suitsPresent[i.Top.Suit] - if suitCount <= 2 && trumpPresent { - features[11] = 1 - } else if suitCount == 3 && trumpPresent { - for _, card := range i.Hand { - // If this is the only card in the hand of a given suit, and it - // is not a trump (left bower or of the same suit as the top - // card), and it is not an A, then by removing it we get rid of - // one suit. So if there were only 3 suits, then this means we - // have 2 suits after discarding if we were to pick up. We also - // have to make sure that this removal will result in at 2 suits - // and that picking up the top card does not increase the suit - // amount. - if suitsPresent[card.Suit] == 1 && card.Value != deck.A && - card.AdjSuit(i.Top.Suit) != i.Top.Suit { - features[11] = 1 - - // There could be more than one card that matches these - // requirements, we only care if one exists, so break on finding - // one. - break - } - } - } - } - - // If you are picking it up then consider the weight of the card on top as - // well. The discarded card need not be considered since, it is most likely - // non-trump, and will not affect this analysis which only considers trump - // cards. If it is a trump card, then every other card is a trump as well - // and the final decision should not be affected. - if i.Dealer == 0 && i.Dealer == 2 { - features[indexes[i.Top.Value]] = 1 - } - - return features + features := make([]int, 12, 12) + + // The following maps create a relation between a certain feature and its + // position within the features vector. + indexes := map[deck.Value]int{ + deck.Nine: 0, + deck.Ten: 1, + deck.J: 2, + deck.Q: 3, + deck.K: 4, + deck.A: 5, + } + + aces := map[deck.Suit]int{ + deck.H: 7, + deck.D: 8, + deck.S: 9, + deck.C: 10, + } + + // Used to keep track of how many suits are in the hand. + suitsPresent := make(map[deck.Suit]int) + + for _, card := range i.Hand { + if card.Suit == i.Top.Suit { + features[indexes[card.Value]] = 1 + } else if card.Suit == i.Top.Suit.Left() && card.Value == deck.J { + features[6] = 1 + } else if card.Value == deck.A { + features[aces[card.Suit]] = 1 + } + + // Adjust suit count for left bower and increment the count for the + // current card's suit. + adjSuit := card.AdjSuit(i.Top.Suit) + if _, ok := suitsPresent[adjSuit]; ok { + suitsPresent[adjSuit] += 1 + } else { + suitsPresent[adjSuit] = 1 + } + } + + suitCount := len(suitsPresent) + // If the hand has less then 2 suits and no card is being picked up then one + // can be sure we have 2 suits. + if suitCount <= 2 && i.Dealer != 0 { + features[11] = 1 + } else if suitCount <= 3 && i.Dealer == 0 { + // Else, if there are less than 3 suits and we are picking up, one suit + // might be gotten rid of yet. Even if we had 2 suits we would to check that + // we already had the trump suit if we are picking up. + _, trumpPresent := suitsPresent[i.Top.Suit] + if suitCount <= 2 && trumpPresent { + features[11] = 1 + } else if suitCount == 3 && trumpPresent { + for _, card := range i.Hand { + // If this is the only card in the hand of a given suit, and it + // is not a trump (left bower or of the same suit as the top + // card), and it is not an A, then by removing it we get rid of + // one suit. So if there were only 3 suits, then this means we + // have 2 suits after discarding if we were to pick up. We also + // have to make sure that this removal will result in at 2 suits + // and that picking up the top card does not increase the suit + // amount. + if suitsPresent[card.Suit] == 1 && card.Value != deck.A && + card.AdjSuit(i.Top.Suit) != i.Top.Suit { + features[11] = 1 + + // There could be more than one card that matches these + // requirements, we only care if one exists, so break on finding + // one. + break + } + } + } + } + + // If you are picking it up then consider the weight of the card on top as + // well. The discarded card need not be considered since, it is most likely + // non-trump, and will not affect this analysis which only considers trump + // cards. If it is a trump card, then every other card is a trump as well + // and the final decision should not be affected. + if i.Dealer == 0 && i.Dealer == 2 { + features[indexes[i.Top.Value]] = 1 + } + + return features } - - type RulePlayer struct { - pickupFn string + pickupFn string } - /* * Used to create a new RulePlayer struct along with a data source for training. * @@ -150,165 +146,161 @@ type RulePlayer struct { * Returns: * A RulePlayer pointer that reads data from the given filename. */ -func NewRule(pickupFn string) (*RulePlayer) { - return &RulePlayer{ pickupFn } +func NewRule(pickupFn string) *RulePlayer { + return &RulePlayer{pickupFn} } - func (p *RulePlayer) Pickup(hand []deck.Card, top deck.Card, who int) bool { - inputs, expected := loadInputs(p.pickupFn) - prcp := ai.CreatePerceptron(12, 0, 1) - - // Move the perceptron toward linear separability if possible and then - // return the result for the given input. - prcp.Converge(inputs, expected, 0.005, 0.07, 10000) - nextInput := Input { - top, - hand, - who, - } - res := prcp.Process(nextInput) - - return res == 1 + inputs, expected := loadInputs(p.pickupFn) + prcp := ai.CreatePerceptron(12, 0, 1) + + // Move the perceptron toward linear separability if possible and then + // return the result for the given input. + prcp.Converge(inputs, expected, 0.005, 0.07, 10000) + nextInput := Input{ + top, + hand, + who, + } + res := prcp.Process(nextInput) + + return res == 1 } - func (p *RulePlayer) Discard(hand []deck.Card, - top deck.Card) ([]deck.Card, deck.Card) { - // First construct a map that holds counts for all of the suits and the - // lowest card for each suit. - suitsCount := make(map[deck.Suit]int) - lowest := make(map[deck.Suit]int) - - hand = append(hand, top) - for i, card := range hand { - adjSuit := hand[i].AdjSuit(top.Suit) - suitsCount[adjSuit]++ - - if _, ok := lowest[adjSuit]; !ok { - lowest[adjSuit] = i - } else { - // If the lowest card for a given suit can beat the current card, - // the current card is smaller. - curLowest := hand[lowest[adjSuit]] - if euchre.Beat(curLowest, card, top.Suit) { - lowest[adjSuit] = i - } - } - } - - singleFound := false - trumpExists := suitsCount[top.Suit] > 1 - minCard := deck.Card { top.Suit, deck.J } - minIndex := -1 - - for suit, i := range lowest { - card := hand[i] - - // If there's only one of a card that is not trump and is not an A and - // the current min card is of greater value (it is trump or its value is - // less), or the current min card is not the only card of its suit then - // update the trackers. - if trumpExists && suitsCount[suit] == 1 && suit != top.Suit && - card.Value != deck.A && (minCard.IsTrump(top.Suit) || - deck.ValueCompare(card, minCard) < 0 || suitsCount[minCard.Suit] > 1) { - singleFound = true - minCard = card - minIndex = i - } else if !singleFound { - // If a single card that is non-trump and non-ace has not been found - // then try to find the smallest card otherwise. In other words, any - // single suit card is preferred to a multi-suit card as long as said - // card is not trump or A. - - // If the two cards of the same suit we can compare them using Beat - // since it wouldn't matter who led. - if card.AdjSuit(top.Suit) == minCard.AdjSuit(top.Suit) { - if euchre.Beat(minCard, card, top.Suit) { - minCard = card - minIndex = i - } - } else if minCard.IsTrump(top.Suit) || (!card.IsTrump(top.Suit) && - deck.ValueCompare(card, minCard) < 0) { - // If the cards are not of the same suit then if the current min is - // trump, the new card must be less. Otherwise, as long as the - // card in question is not trump and it's value is less then the - // current min card, update the trackers. - minCard = card - minIndex = i - } - } - } - - hand[minIndex] = top - hand = hand[:len(hand) - 1] - - return hand, minCard + top deck.Card) ([]deck.Card, deck.Card) { + // First construct a map that holds counts for all of the suits and the + // lowest card for each suit. + suitsCount := make(map[deck.Suit]int) + lowest := make(map[deck.Suit]int) + + hand = append(hand, top) + for i, card := range hand { + adjSuit := hand[i].AdjSuit(top.Suit) + suitsCount[adjSuit]++ + + if _, ok := lowest[adjSuit]; !ok { + lowest[adjSuit] = i + } else { + // If the lowest card for a given suit can beat the current card, + // the current card is smaller. + curLowest := hand[lowest[adjSuit]] + if euchre.Beat(curLowest, card, top.Suit) { + lowest[adjSuit] = i + } + } + } + + singleFound := false + trumpExists := suitsCount[top.Suit] > 1 + minCard := deck.Card{top.Suit, deck.J} + minIndex := -1 + + for suit, i := range lowest { + card := hand[i] + + // If there's only one of a card that is not trump and is not an A and + // the current min card is of greater value (it is trump or its value is + // less), or the current min card is not the only card of its suit then + // update the trackers. + if trumpExists && suitsCount[suit] == 1 && suit != top.Suit && + card.Value != deck.A && (minCard.IsTrump(top.Suit) || + deck.ValueCompare(card, minCard) < 0 || suitsCount[minCard.Suit] > 1) { + singleFound = true + minCard = card + minIndex = i + } else if !singleFound { + // If a single card that is non-trump and non-ace has not been found + // then try to find the smallest card otherwise. In other words, any + // single suit card is preferred to a multi-suit card as long as said + // card is not trump or A. + + // If the two cards of the same suit we can compare them using Beat + // since it wouldn't matter who led. + if card.AdjSuit(top.Suit) == minCard.AdjSuit(top.Suit) { + if euchre.Beat(minCard, card, top.Suit) { + minCard = card + minIndex = i + } + } else if minCard.IsTrump(top.Suit) || (!card.IsTrump(top.Suit) && + deck.ValueCompare(card, minCard) < 0) { + // If the cards are not of the same suit then if the current min is + // trump, the new card must be less. Otherwise, as long as the + // card in question is not trump and it's value is less then the + // current min card, update the trackers. + minCard = card + minIndex = i + } + } + } + + hand[minIndex] = top + hand = hand[:len(hand)-1] + + return hand, minCard } - func (p *RulePlayer) Call(hand []deck.Card, top deck.Card, - who int) (deck.Suit, bool) { - chosen := false - maxT := top.Suit - maxConf := float32(0) - - for _, trump := range deck.SUITS { - if trump == top.Suit { - continue - } - - conf := float32(0.08) - - // The following maps create a relation between a certain feature and its - // position within the features vector. - weights := map[deck.Value]float32 { - deck.Nine: 0.05, - deck.Ten: 0.07, - deck.J: 0.3, - deck.Q: 0.12, - deck.K: 0.15, - deck.A: 0.2, - } - - // Used to keep track of how many suits are in the hand. - suitsPresent := make(map[deck.Suit]int) - - for _, card := range hand { - if card.Suit == trump { - conf += weights[card.Value] - } else if card.AdjSuit(trump) == trump { - conf += 0.25 - } else if card.Value == deck.A { - conf += 0.04 - } - - // Adjust suit count for left bower and increment the count for the - // current card's suit. - adjSuit := card.AdjSuit(trump) - if _, ok := suitsPresent[adjSuit]; ok { - suitsPresent[adjSuit] += 1 - } else { - suitsPresent[adjSuit] = 1 - } - } - - // If the hand has 2 suits or less we can have more confidence. - if len(suitsPresent) <= 2 { - conf += 0.08 - } - - if conf > 0.5 && conf > maxConf { - maxConf = conf - maxT = trump - chosen = true - } - } - - return maxT, chosen + who int) (deck.Suit, bool) { + chosen := false + maxT := top.Suit + maxConf := float32(0) + + for _, trump := range deck.SUITS { + if trump == top.Suit { + continue + } + + conf := float32(0.08) + + // The following maps create a relation between a certain feature and its + // position within the features vector. + weights := map[deck.Value]float32{ + deck.Nine: 0.05, + deck.Ten: 0.07, + deck.J: 0.3, + deck.Q: 0.12, + deck.K: 0.15, + deck.A: 0.2, + } + + // Used to keep track of how many suits are in the hand. + suitsPresent := make(map[deck.Suit]int) + + for _, card := range hand { + if card.Suit == trump { + conf += weights[card.Value] + } else if card.AdjSuit(trump) == trump { + conf += 0.25 + } else if card.Value == deck.A { + conf += 0.04 + } + + // Adjust suit count for left bower and increment the count for the + // current card's suit. + adjSuit := card.AdjSuit(trump) + if _, ok := suitsPresent[adjSuit]; ok { + suitsPresent[adjSuit] += 1 + } else { + suitsPresent[adjSuit] = 1 + } + } + + // If the hand has 2 suits or less we can have more confidence. + if len(suitsPresent) <= 2 { + conf += 0.08 + } + + if conf > 0.5 && conf > maxConf { + maxConf = conf + maxT = trump + chosen = true + } + } + + return maxT, chosen } - /* * Player decision method to go alone or not. * @@ -321,137 +313,135 @@ func (p *RulePlayer) Call(hand []deck.Card, top deck.Card, * True if the player calls going alone and false otherwise. */ func (p *RulePlayer) Alone(hand []deck.Card, top deck.Card, who int) bool { - // TODO: For now just use the random approach. - r := rand.New(rand.NewSource(time.Now().UnixNano())) - return r.Float64() < 0.5 + // TODO: For now just use the random approach. + r := rand.New(rand.NewSource(time.Now().UnixNano())) + return r.Float64() < 0.5 } - /* * TODO */ func (p *RulePlayer) Play(player int, setup euchre.Setup, hand, - played []deck.Card, - prior []euchre.Trick) ([]deck.Card, deck.Card) { - // The approach for the rule player will be to always try to win the current - // hand. If the player cannot win, it plays its least valuable card. If the - // player can possibly win, it has two logic branches. If the card that it - // can win with is a non-trump then it will play the highest such non-trump. - // If the highest card is a trump, it will the lowest such trump card. - // Other logic branches include, if the partner is already winning then do - // not try to win and throw off. Lastly, if no card has been played, the - // most valuable card will be played. - - playable := euchre.Possible(hand, played, setup.Trump) - - // Check if all the possible cards are of the same suit, (i.e. we have at - // least one card with the same suit) or there is some card that isn't the - // same. - first := deck.Card { } - matchingSuits := false - curWinnerIdx := -1 - curWinCard := deck.Card { } - if len(played) > 0 { - first = played[0] - matchingSuits = deck.SameSuit(first, hand[playable[0]], setup.Trump) - - curWinnerIdx = euchre.WinnerIdx(played, setup.Trump) - curWinCard = played[curWinnerIdx] - } - - leader := euchre.Leader(played, player, setup.AlonePlayer) - curWinner := euchre.Winner(played, setup.Trump, leader, setup.AlonePlayer) - partner := (player + 2) % 4 - - var chosen deck.Card - chosenIdx := -1 - - // Logic for picking a winner. If the current winner is our partner, just - // play the lowest card in the hand. - if curWinner != partner && len(played) > 0 { - for _, idx := range playable { - curCard := hand[idx] - var wins bool - wins = !euchre.Beat(curWinCard, curCard, setup.Trump) - - // All cards are of the same suit and the first card played is not a - // trump. So pick the card with the lowest value that wins. - if (matchingSuits && !first.IsTrump(setup.Trump)) { - higher := true - if chosenIdx >= 0 { - higher = !euchre.Beat(chosen, curCard, setup.Trump) - } - - if wins && higher { - chosenIdx = idx - chosen = curCard - } - } else { - if curCard.IsTrump(setup.Trump) { - // We don't have any matching cards of the same suit, so pick the - // lowest card that can win. Which boils down to the lowest trump. - - less := true - if chosenIdx >= 0 { - less = euchre.Beat(chosen, curCard, setup.Trump) - } - - // If this card can beat the current highest played card and is - // valued less than the current chosen card, swap it. - if wins && less { - chosenIdx = idx - chosen = curCard - } - } - } - } - } else if len(played) == 0 { - // If no cards are currently played, play the highest value card. - chosenIdx = playable[0] - chosen = hand[chosenIdx] - - for _, idx := range playable[1:] { - curCard := hand[idx] - var wins bool - - if deck.SameSuit(curCard, chosen, setup.Trump) || - curCard.IsTrump(setup.Trump) || chosen.IsTrump(setup.Trump) { - wins = euchre.Beat(curCard, chosen, setup.Trump) - } else { - wins = deck.ValueCompare(curCard, chosen) > 0 - } - - if wins { - chosenIdx = idx - chosen = curCard - } - } - } - - // There is no card that can win amongst our possible choices or our partner - // is currently winning. So choose the least valuable card. - if chosenIdx < 0 { - chosenIdx = playable[0] - chosen = hand[chosenIdx] - - for _, idx := range playable[:1] { - curCard := hand[idx] - - if (chosen.IsTrump(setup.Trump) && euchre.Beat(chosen, curCard, setup.Trump)) || - (!chosen.IsTrump(setup.Trump) && !curCard.IsTrump(setup.Trump) && deck.ValueCompare(chosen, curCard) > 0) { - chosenIdx = idx - chosen = curCard - } - } - } - - hand[chosenIdx] = hand[len(hand) - 1] - hand = hand[:len(hand) - 1] - - return hand, chosen + played []deck.Card, + prior []euchre.Trick) ([]deck.Card, deck.Card) { + // The approach for the rule player will be to always try to win the current + // hand. If the player cannot win, it plays its least valuable card. If the + // player can possibly win, it has two logic branches. If the card that it + // can win with is a non-trump then it will play the highest such non-trump. + // If the highest card is a trump, it will the lowest such trump card. + // Other logic branches include, if the partner is already winning then do + // not try to win and throw off. Lastly, if no card has been played, the + // most valuable card will be played. + + playable := euchre.Possible(hand, played, setup.Trump) + + // Check if all the possible cards are of the same suit, (i.e. we have at + // least one card with the same suit) or there is some card that isn't the + // same. + first := deck.Card{} + matchingSuits := false + curWinnerIdx := -1 + curWinCard := deck.Card{} + if len(played) > 0 { + first = played[0] + matchingSuits = deck.SameSuit(first, hand[playable[0]], setup.Trump) + + curWinnerIdx = euchre.WinnerIdx(played, setup.Trump) + curWinCard = played[curWinnerIdx] + } + + leader := euchre.Leader(played, player, setup.AlonePlayer) + curWinner := euchre.Winner(played, setup.Trump, leader, setup.AlonePlayer) + partner := (player + 2) % 4 + + var chosen deck.Card + chosenIdx := -1 + + // Logic for picking a winner. If the current winner is our partner, just + // play the lowest card in the hand. + if curWinner != partner && len(played) > 0 { + for _, idx := range playable { + curCard := hand[idx] + var wins bool + wins = !euchre.Beat(curWinCard, curCard, setup.Trump) + + // All cards are of the same suit and the first card played is not a + // trump. So pick the card with the lowest value that wins. + if matchingSuits && !first.IsTrump(setup.Trump) { + higher := true + if chosenIdx >= 0 { + higher = !euchre.Beat(chosen, curCard, setup.Trump) + } + + if wins && higher { + chosenIdx = idx + chosen = curCard + } + } else { + if curCard.IsTrump(setup.Trump) { + // We don't have any matching cards of the same suit, so pick the + // lowest card that can win. Which boils down to the lowest trump. + + less := true + if chosenIdx >= 0 { + less = euchre.Beat(chosen, curCard, setup.Trump) + } + + // If this card can beat the current highest played card and is + // valued less than the current chosen card, swap it. + if wins && less { + chosenIdx = idx + chosen = curCard + } + } + } + } + } else if len(played) == 0 { + // If no cards are currently played, play the highest value card. + chosenIdx = playable[0] + chosen = hand[chosenIdx] + + for _, idx := range playable[1:] { + curCard := hand[idx] + var wins bool + + if deck.SameSuit(curCard, chosen, setup.Trump) || + curCard.IsTrump(setup.Trump) || chosen.IsTrump(setup.Trump) { + wins = euchre.Beat(curCard, chosen, setup.Trump) + } else { + wins = deck.ValueCompare(curCard, chosen) > 0 + } + + if wins { + chosenIdx = idx + chosen = curCard + } + } + } + + // There is no card that can win amongst our possible choices or our partner + // is currently winning. So choose the least valuable card. + if chosenIdx < 0 { + chosenIdx = playable[0] + chosen = hand[chosenIdx] + + for _, idx := range playable[:1] { + curCard := hand[idx] + + if (chosen.IsTrump(setup.Trump) && euchre.Beat(chosen, curCard, setup.Trump)) || + (!chosen.IsTrump(setup.Trump) && !curCard.IsTrump(setup.Trump) && deck.ValueCompare(chosen, curCard) > 0) { + chosenIdx = idx + chosen = curCard + } + } + } + + hand[chosenIdx] = hand[len(hand)-1] + hand = hand[:len(hand)-1] + + return hand, chosen } - /* * Loads the inputs in a file and returns a slice of the inputs and their * expected values. @@ -464,44 +454,43 @@ func (p *RulePlayer) Play(player int, setup euchre.Setup, hand, * each of these inputs. */ func loadInputs(fn string) ([]ai.Input, []int) { - file, err := os.Open(fn) - check(err) - scanner := bufio.NewScanner(file) - - // Scan all the training data from the file into the samples slice. - var samples []ai.Input - var expected []int - for scanner.Scan() { - line := scanner.Text() - - // Declare all variables needed to input a sample instance with the - // input (top and hand) and the answer as well (up). - var nextInput Input - var tmpTop string - var tmpHand [5]string - var up int - // Read in a line from the file and parse it for the different needed - // fields for a pickup problem instance. - fmt.Sscanf(line, "%s %s %s %s %s %s %d %d", &tmpTop, &tmpHand[0], - &tmpHand[1], &tmpHand[2], - &tmpHand[3], &tmpHand[4], - &nextInput.Dealer, &up) - - // Initialize the card from the values read in and add it to the samples - // slice. - nextInput.Top, _ = deck.CreateCard(tmpTop) - for i, tmpCard := range tmpHand { - nextInput.Hand[i], _ = deck.CreateCard(tmpCard) - } - - samples = append(samples, nextInput) - expected = append(expected, up) - } - - return samples, expected + file, err := os.Open(fn) + check(err) + scanner := bufio.NewScanner(file) + + // Scan all the training data from the file into the samples slice. + var samples []ai.Input + var expected []int + for scanner.Scan() { + line := scanner.Text() + + // Declare all variables needed to input a sample instance with the + // input (top and hand) and the answer as well (up). + var nextInput Input + var tmpTop string + var tmpHand [5]string + var up int + // Read in a line from the file and parse it for the different needed + // fields for a pickup problem instance. + fmt.Sscanf(line, "%s %s %s %s %s %s %d %d", &tmpTop, &tmpHand[0], + &tmpHand[1], &tmpHand[2], + &tmpHand[3], &tmpHand[4], + &nextInput.Dealer, &up) + + // Initialize the card from the values read in and add it to the samples + // slice. + nextInput.Top, _ = deck.CreateCard(tmpTop) + for i, tmpCard := range tmpHand { + nextInput.Hand[i], _ = deck.CreateCard(tmpCard) + } + + samples = append(samples, nextInput) + expected = append(expected, up) + } + + return samples, expected } - /* * Small utility method to see if an error is actually an error. If it is an * actual error then the program panics. @@ -510,7 +499,7 @@ func loadInputs(fn string) ([]ai.Input, []int) { * err: The possible error to check. */ func check(err error) { - if err != nil { - panic(err) - } + if err != nil { + panic(err) + } } diff --git a/src/player/rule_player_test.go b/src/player/rule_player_test.go index 8c9272d..c0c8a93 100644 --- a/src/player/rule_player_test.go +++ b/src/player/rule_player_test.go @@ -1,322 +1,320 @@ package player import ( - "deck" - "euchre" - "testing" + "deck" + "euchre" + "testing" ) // TODO: still add the tests for AlonePlayers. Much ado about that. - /* * Tests the expectations specific to the RulePlayer. This includes the normal * game play. Since it is rule based it can be exactly predicted what a rule * based player will do. */ type playTest struct { - player int - setup euchre.Setup - hand []deck.Card - played []deck.Card - prior []euchre.Trick - expected deck.Card + player int + setup euchre.Setup + hand []deck.Card + played []deck.Card + prior []euchre.Trick + expected deck.Card } -var playTests = []playTest { - /* - * Routine test with only one card played. - */ - playTest { - 0, - euchre.Setup { - 2, - 3, - false, - deck.Card { deck.C, deck.Ten }, - deck.H, - deck.Card { }, - -1, - }, - []deck.Card { - deck.Card { deck.D, deck.J }, - deck.Card { deck.H, deck.J }, - deck.Card { deck.S, deck.Q }, - deck.Card { deck.D, deck.K }, - deck.Card { deck.C, deck.Nine }, - }, - []deck.Card { - deck.Card { deck.C, deck.Q }, - }, - []euchre.Trick { }, - deck.Card { deck.C, deck.Nine }, - }, +var playTests = []playTest{ + /* + * Routine test with only one card played. + */ + playTest{ + 0, + euchre.Setup{ + 2, + 3, + false, + deck.Card{deck.C, deck.Ten}, + deck.H, + deck.Card{}, + -1, + }, + []deck.Card{ + deck.Card{deck.D, deck.J}, + deck.Card{deck.H, deck.J}, + deck.Card{deck.S, deck.Q}, + deck.Card{deck.D, deck.K}, + deck.Card{deck.C, deck.Nine}, + }, + []deck.Card{ + deck.Card{deck.C, deck.Q}, + }, + []euchre.Trick{}, + deck.Card{deck.C, deck.Nine}, + }, - /* - * Test for playing first. The most valuable card in the hand should be - * played. - */ - playTest { - 0, - euchre.Setup { - 3, - 0, - true, - deck.Card { deck.H, deck.Nine }, - deck.H, - deck.Card { }, - -1, - }, - []deck.Card { - deck.Card { deck.H, deck.Ten }, - deck.Card { deck.H, deck.J }, - deck.Card { deck.S, deck.Ten }, - deck.Card { deck.S, deck.K }, - deck.Card { deck.C, deck.Ten }, - }, - []deck.Card { }, - []euchre.Trick { }, - deck.Card { deck.H, deck.J }, - }, + /* + * Test for playing first. The most valuable card in the hand should be + * played. + */ + playTest{ + 0, + euchre.Setup{ + 3, + 0, + true, + deck.Card{deck.H, deck.Nine}, + deck.H, + deck.Card{}, + -1, + }, + []deck.Card{ + deck.Card{deck.H, deck.Ten}, + deck.Card{deck.H, deck.J}, + deck.Card{deck.S, deck.Ten}, + deck.Card{deck.S, deck.K}, + deck.Card{deck.C, deck.Ten}, + }, + []deck.Card{}, + []euchre.Trick{}, + deck.Card{deck.H, deck.J}, + }, - /* - * Test for playing when partner is already winning. The lowest value card - * should be thrown. - */ - playTest { - 0, - euchre.Setup { - 3, - 0, - true, - deck.Card { deck.H, deck.Nine }, - deck.H, - deck.Card { }, - -1, - }, - []deck.Card { - deck.Card { deck.H, deck.Ten }, - deck.Card { deck.H, deck.J }, - deck.Card { deck.S, deck.Ten }, - deck.Card { deck.C, deck.Ten }, - }, - []deck.Card { - deck.Card { deck.H, deck.Q }, - deck.Card { deck.H, deck.Nine }, - }, - []euchre.Trick { - euchre.Trick { - []deck.Card { - deck.Card { deck.S, deck.K }, - deck.Card { deck.S, deck.J }, - deck.Card { deck.S, deck.A }, - deck.Card { deck.S, deck.Nine }, - }, - 0, - deck.H, - -1, - }, - }, - deck.Card { deck.H, deck.Ten }, - }, + /* + * Test for playing when partner is already winning. The lowest value card + * should be thrown. + */ + playTest{ + 0, + euchre.Setup{ + 3, + 0, + true, + deck.Card{deck.H, deck.Nine}, + deck.H, + deck.Card{}, + -1, + }, + []deck.Card{ + deck.Card{deck.H, deck.Ten}, + deck.Card{deck.H, deck.J}, + deck.Card{deck.S, deck.Ten}, + deck.Card{deck.C, deck.Ten}, + }, + []deck.Card{ + deck.Card{deck.H, deck.Q}, + deck.Card{deck.H, deck.Nine}, + }, + []euchre.Trick{ + euchre.Trick{ + []deck.Card{ + deck.Card{deck.S, deck.K}, + deck.Card{deck.S, deck.J}, + deck.Card{deck.S, deck.A}, + deck.Card{deck.S, deck.Nine}, + }, + 0, + deck.H, + -1, + }, + }, + deck.Card{deck.H, deck.Ten}, + }, - /* - * Test for playing when partner is already winning on non trump hand. The - * lowest value card should be thrown, which is not trump if available. - */ - playTest { - 0, - euchre.Setup { - 3, - 0, - true, - deck.Card { deck.H, deck.Nine }, - deck.H, - deck.Card { }, - -1, - }, - []deck.Card { - deck.Card { deck.H, deck.Ten }, - deck.Card { deck.H, deck.J }, - deck.Card { deck.S, deck.Ten }, - deck.Card { deck.C, deck.Nine }, - }, - []deck.Card { - deck.Card { deck.C, deck.A }, - deck.Card { deck.C, deck.J }, - }, - []euchre.Trick { - euchre.Trick { - []deck.Card { - deck.Card { deck.S, deck.K }, - deck.Card { deck.S, deck.J }, - deck.Card { deck.S, deck.A }, - deck.Card { deck.S, deck.Nine }, - }, - 0, - deck.H, - -1, - }, - }, - deck.Card { deck.C, deck.Nine }, - }, + /* + * Test for playing when partner is already winning on non trump hand. The + * lowest value card should be thrown, which is not trump if available. + */ + playTest{ + 0, + euchre.Setup{ + 3, + 0, + true, + deck.Card{deck.H, deck.Nine}, + deck.H, + deck.Card{}, + -1, + }, + []deck.Card{ + deck.Card{deck.H, deck.Ten}, + deck.Card{deck.H, deck.J}, + deck.Card{deck.S, deck.Ten}, + deck.Card{deck.C, deck.Nine}, + }, + []deck.Card{ + deck.Card{deck.C, deck.A}, + deck.Card{deck.C, deck.J}, + }, + []euchre.Trick{ + euchre.Trick{ + []deck.Card{ + deck.Card{deck.S, deck.K}, + deck.Card{deck.S, deck.J}, + deck.Card{deck.S, deck.A}, + deck.Card{deck.S, deck.Nine}, + }, + 0, + deck.H, + -1, + }, + }, + deck.Card{deck.C, deck.Nine}, + }, - /* - * Test for playing lowest trump when that is all you have and partner is - * winning. - */ - playTest { - 0, - euchre.Setup { - 3, - 0, - true, - deck.Card { deck.H, deck.Nine }, - deck.H, - deck.Card { }, - -1, - }, - []deck.Card { - deck.Card { deck.H, deck.Ten }, - deck.Card { deck.H, deck.J }, - deck.Card { deck.H, deck.Q }, - deck.Card { deck.H, deck.K }, - }, - []deck.Card { - deck.Card { deck.C, deck.A }, - deck.Card { deck.C, deck.J }, - }, - []euchre.Trick { - euchre.Trick { - []deck.Card { - deck.Card { deck.S, deck.K }, - deck.Card { deck.S, deck.J }, - deck.Card { deck.S, deck.A }, - deck.Card { deck.S, deck.Nine }, - }, - 0, - deck.H, - -1, - }, - }, - deck.Card { deck.H, deck.Ten }, - }, + /* + * Test for playing lowest trump when that is all you have and partner is + * winning. + */ + playTest{ + 0, + euchre.Setup{ + 3, + 0, + true, + deck.Card{deck.H, deck.Nine}, + deck.H, + deck.Card{}, + -1, + }, + []deck.Card{ + deck.Card{deck.H, deck.Ten}, + deck.Card{deck.H, deck.J}, + deck.Card{deck.H, deck.Q}, + deck.Card{deck.H, deck.K}, + }, + []deck.Card{ + deck.Card{deck.C, deck.A}, + deck.Card{deck.C, deck.J}, + }, + []euchre.Trick{ + euchre.Trick{ + []deck.Card{ + deck.Card{deck.S, deck.K}, + deck.Card{deck.S, deck.J}, + deck.Card{deck.S, deck.A}, + deck.Card{deck.S, deck.Nine}, + }, + 0, + deck.H, + -1, + }, + }, + deck.Card{deck.H, deck.Ten}, + }, - /* - * Test for playing lowest card when there is no way to win. - */ - playTest { - 0, - euchre.Setup { - 0, - 0, - false, - deck.Card { deck.D, deck.Q }, - deck.S, - deck.Card { }, - -1, - }, - []deck.Card { - deck.Card { deck.S, deck.Ten }, - deck.Card { deck.C, deck.J }, - deck.Card { deck.D, deck.Nine }, - }, - []deck.Card { - deck.Card { deck.S, deck.J }, - deck.Card { deck.S, deck.K }, - }, - []euchre.Trick { - euchre.Trick { - []deck.Card { - deck.Card { deck.H, deck.Ten }, - deck.Card { deck.H, deck.Q }, - deck.Card { deck.H, deck.A }, - deck.Card { deck.H, deck.K }, - }, - 1, - deck.S, - -1, - }, - euchre.Trick { - []deck.Card { - deck.Card { deck.C, deck.K }, - deck.Card { deck.C, deck.Q }, - deck.Card { deck.C, deck.Nine }, - deck.Card { deck.S, deck.A }, - }, - 3, - deck.S, - -1, - }, - }, - deck.Card { deck.S, deck.Ten }, - }, + /* + * Test for playing lowest card when there is no way to win. + */ + playTest{ + 0, + euchre.Setup{ + 0, + 0, + false, + deck.Card{deck.D, deck.Q}, + deck.S, + deck.Card{}, + -1, + }, + []deck.Card{ + deck.Card{deck.S, deck.Ten}, + deck.Card{deck.C, deck.J}, + deck.Card{deck.D, deck.Nine}, + }, + []deck.Card{ + deck.Card{deck.S, deck.J}, + deck.Card{deck.S, deck.K}, + }, + []euchre.Trick{ + euchre.Trick{ + []deck.Card{ + deck.Card{deck.H, deck.Ten}, + deck.Card{deck.H, deck.Q}, + deck.Card{deck.H, deck.A}, + deck.Card{deck.H, deck.K}, + }, + 1, + deck.S, + -1, + }, + euchre.Trick{ + []deck.Card{ + deck.Card{deck.C, deck.K}, + deck.Card{deck.C, deck.Q}, + deck.Card{deck.C, deck.Nine}, + deck.Card{deck.S, deck.A}, + }, + 3, + deck.S, + -1, + }, + }, + deck.Card{deck.S, deck.Ten}, + }, - /* - * Test for playing the highest possible non trump possible if both can beat - * the current winner. - */ - playTest { - 0, - euchre.Setup { - 1, - 3, - false, - deck.Card { deck.S, deck.A }, - deck.C, - deck.Card { }, - -1, - }, - []deck.Card { - deck.Card { deck.H, deck.A }, - deck.Card { deck.S, deck.Q }, - deck.Card { deck.S, deck.A }, - }, - []deck.Card { - deck.Card { deck.S, deck.Ten }, - deck.Card { deck.S, deck.Nine }, - }, - []euchre.Trick { - euchre.Trick { - []deck.Card { - deck.Card { deck.D, deck.K }, - deck.Card { deck.C, deck.Q }, - deck.Card { deck.D, deck.A }, - deck.Card { deck.D, deck.Q }, - }, - 2, - deck.C, - -1, - }, - euchre.Trick { - []deck.Card { - deck.Card { deck.S, deck.J }, - deck.Card { deck.C, deck.Nine }, - deck.Card { deck.C, deck.A }, - deck.Card { deck.C, deck.J }, - }, - 1, - deck.C, - -1, - }, - }, - deck.Card { deck.S, deck.Q }, - }, + /* + * Test for playing the highest possible non trump possible if both can beat + * the current winner. + */ + playTest{ + 0, + euchre.Setup{ + 1, + 3, + false, + deck.Card{deck.S, deck.A}, + deck.C, + deck.Card{}, + -1, + }, + []deck.Card{ + deck.Card{deck.H, deck.A}, + deck.Card{deck.S, deck.Q}, + deck.Card{deck.S, deck.A}, + }, + []deck.Card{ + deck.Card{deck.S, deck.Ten}, + deck.Card{deck.S, deck.Nine}, + }, + []euchre.Trick{ + euchre.Trick{ + []deck.Card{ + deck.Card{deck.D, deck.K}, + deck.Card{deck.C, deck.Q}, + deck.Card{deck.D, deck.A}, + deck.Card{deck.D, deck.Q}, + }, + 2, + deck.C, + -1, + }, + euchre.Trick{ + []deck.Card{ + deck.Card{deck.S, deck.J}, + deck.Card{deck.C, deck.Nine}, + deck.Card{deck.C, deck.A}, + deck.Card{deck.C, deck.J}, + }, + 1, + deck.C, + -1, + }, + }, + deck.Card{deck.S, deck.Q}, + }, } - /* * The main driver to test the rule players general playing logic. */ func TestPlay(t *testing.T) { - player := NewRule("") + player := NewRule("") - for i, fixture := range playTests { - _, chosen := player.Play(fixture.player, fixture.setup, fixture.hand, - fixture.played, fixture.prior) - if chosen != fixture.expected { - t.Logf("Fixture %d failed.\n", i + 1) - t.Errorf("Gave %s instead of %s", chosen, fixture.expected) - } - } + for i, fixture := range playTests { + _, chosen := player.Play(fixture.player, fixture.setup, fixture.hand, + fixture.played, fixture.prior) + if chosen != fixture.expected { + t.Logf("Fixture %d failed.\n", i+1) + t.Errorf("Gave %s instead of %s", chosen, fixture.expected) + } + } } diff --git a/src/player/smart.go b/src/player/smart.go index 2a96b7d..a497576 100644 --- a/src/player/smart.go +++ b/src/player/smart.go @@ -1,32 +1,30 @@ package player import ( - "ai" - "deck" - "euchre" - "math" + "ai" + "deck" + "euchre" + "math" ) - type SmartPlayer struct { - pickupConfidence float64 - callConfidence float64 - aloneConfidence float64 + pickupConfidence float64 + callConfidence float64 + aloneConfidence float64 - pickupRuns int - pickupDeterminizations int + pickupRuns int + pickupDeterminizations int - callRuns int - callDeterminizations int + callRuns int + callDeterminizations int - playRuns int - playDeterminizations int + playRuns int + playDeterminizations int - aloneRuns int - aloneDeterminizations int + aloneRuns int + aloneDeterminizations int } - /* * Creates a new SmartPlayer with the given attributes. These attributes are the * inputs to the models behind the SmartPlayer's logic. Confidence is on a scale @@ -51,228 +49,223 @@ type SmartPlayer struct { * A SmartPlayer that uses the given parameters in its decision making. */ func NewSmart(pickupConfidence float64, callConfidence float64, - aloneConfidence float64, - pickupRuns int, pickupDeterminizations int, - callRuns int, callDeterminizations int, - playRuns int, playDeterminizations int, - aloneRuns int, aloneDeterminizations int) (*SmartPlayer) { - - return &SmartPlayer{ - pickupConfidence, - callConfidence, - aloneConfidence, - pickupRuns, - pickupDeterminizations, - callRuns, - callDeterminizations, - playRuns, - playDeterminizations, - aloneRuns, - aloneDeterminizations, - } + aloneConfidence float64, + pickupRuns int, pickupDeterminizations int, + callRuns int, callDeterminizations int, + playRuns int, playDeterminizations int, + aloneRuns int, aloneDeterminizations int) *SmartPlayer { + + return &SmartPlayer{ + pickupConfidence, + callConfidence, + aloneConfidence, + pickupRuns, + pickupDeterminizations, + callRuns, + callDeterminizations, + playRuns, + playDeterminizations, + aloneRuns, + aloneDeterminizations, + } } - func (p *SmartPlayer) Pickup(hand []deck.Card, top deck.Card, who int) bool { - var setup euchre.Setup - var discard deck.Card - - actualHand := make([]deck.Card, len(hand)) - - played := make([]deck.Card, 0) - prior := make([]euchre.Trick, 0) - - if who == 0 { - copy(actualHand, hand) - - actualHand, discard = p.Discard(actualHand, top) - setup = euchre.Setup { - who, - 0, - true, - top, - top.Suit, - discard, - -1, - } - } else { - copy(actualHand, hand) - setup = euchre.Setup { - who, - 0, - true, - top, - top.Suit, - deck.Card{ }, - -1, - } - } - - nPlayer := (who + 1) % 4 - s := euchre.NewUndeterminizedState(setup, nPlayer, actualHand, played, - prior) - e := euchre.Engine{ } - _, expected := ai.MCTS(s, e, p.pickupRuns, p.pickupDeterminizations) - - return (nPlayer % 2 == 0 && expected > p.pickupConfidence) || - (nPlayer % 2 == 1 && expected < -1 * p.pickupConfidence) + var setup euchre.Setup + var discard deck.Card + + actualHand := make([]deck.Card, len(hand)) + + played := make([]deck.Card, 0) + prior := make([]euchre.Trick, 0) + + if who == 0 { + copy(actualHand, hand) + + actualHand, discard = p.Discard(actualHand, top) + setup = euchre.Setup{ + who, + 0, + true, + top, + top.Suit, + discard, + -1, + } + } else { + copy(actualHand, hand) + setup = euchre.Setup{ + who, + 0, + true, + top, + top.Suit, + deck.Card{}, + -1, + } + } + + nPlayer := (who + 1) % 4 + s := euchre.NewUndeterminizedState(setup, nPlayer, actualHand, played, + prior) + e := euchre.Engine{} + _, expected := ai.MCTS(s, e, p.pickupRuns, p.pickupDeterminizations) + + return (nPlayer%2 == 0 && expected > p.pickupConfidence) || + (nPlayer%2 == 1 && expected < -1*p.pickupConfidence) } - func (p *SmartPlayer) Discard(hand []deck.Card, - top deck.Card) ([]deck.Card, deck.Card) { - // First construct a map that holds counts for all of the suits and the - // lowest card for each suit. - suitsCount := make(map[deck.Suit]int) - lowest := make(map[deck.Suit]int) - - hand = append(hand, top) - for i, card := range hand { - adjSuit := hand[i].AdjSuit(top.Suit) - suitsCount[adjSuit]++ - - if _, ok := lowest[adjSuit]; !ok { - lowest[adjSuit] = i - } else { - // If the lowest card for a given suit can beat the current card, - // the current card is smaller. - curLowest := hand[lowest[adjSuit]] - if euchre.Beat(curLowest, card, top.Suit) { - lowest[adjSuit] = i - } - } - } - - singleFound := false - trumpExists := suitsCount[top.Suit] > 1 - minCard := deck.Card { top.Suit, deck.J } - minIndex := -1 - - for suit, i := range lowest { - card := hand[i] - - // If there's only one of a card that is not trump and is not an A and - // the current min card is of greater value (it is trump or its value is - // less), or the current min card is not the only card of its suit then - // update the trackers. - if trumpExists && suitsCount[suit] == 1 && suit != top.Suit && - card.Value != deck.A && (minCard.IsTrump(top.Suit) || - deck.ValueCompare(card, minCard) < 0 || - suitsCount[minCard.Suit] > 1) { - singleFound = true - minCard = card - minIndex = i - } else if !singleFound { - // If a single card that is non-trump and non-ace has not been found - // then try to find the smallest card otherwise. In other words, any - // single suit card is preferred to a multi-suit card as long as said - // card is not trump or A. - - // If the two cards of the same suit we can compare them using Beat - // since it wouldn't matter who led. - if card.AdjSuit(top.Suit) == minCard.AdjSuit(top.Suit) { - if euchre.Beat(minCard, card, top.Suit) { - minCard = card - minIndex = i - } - } else if minCard.IsTrump(top.Suit) || (!card.IsTrump(top.Suit) && - deck.ValueCompare(card, minCard) < 0) { - // If the cards are not of the same suit then if the current min is - // trump, the new card must be less. Otherwise, as long as the - // card in question is not trump and it's value is less then the - // current min card, update the trackers. - minCard = card - minIndex = i - } - } - } - - hand[minIndex] = top - hand = hand[:len(hand) - 1] - - return hand, minCard + top deck.Card) ([]deck.Card, deck.Card) { + // First construct a map that holds counts for all of the suits and the + // lowest card for each suit. + suitsCount := make(map[deck.Suit]int) + lowest := make(map[deck.Suit]int) + + hand = append(hand, top) + for i, card := range hand { + adjSuit := hand[i].AdjSuit(top.Suit) + suitsCount[adjSuit]++ + + if _, ok := lowest[adjSuit]; !ok { + lowest[adjSuit] = i + } else { + // If the lowest card for a given suit can beat the current card, + // the current card is smaller. + curLowest := hand[lowest[adjSuit]] + if euchre.Beat(curLowest, card, top.Suit) { + lowest[adjSuit] = i + } + } + } + + singleFound := false + trumpExists := suitsCount[top.Suit] > 1 + minCard := deck.Card{top.Suit, deck.J} + minIndex := -1 + + for suit, i := range lowest { + card := hand[i] + + // If there's only one of a card that is not trump and is not an A and + // the current min card is of greater value (it is trump or its value is + // less), or the current min card is not the only card of its suit then + // update the trackers. + if trumpExists && suitsCount[suit] == 1 && suit != top.Suit && + card.Value != deck.A && (minCard.IsTrump(top.Suit) || + deck.ValueCompare(card, minCard) < 0 || + suitsCount[minCard.Suit] > 1) { + singleFound = true + minCard = card + minIndex = i + } else if !singleFound { + // If a single card that is non-trump and non-ace has not been found + // then try to find the smallest card otherwise. In other words, any + // single suit card is preferred to a multi-suit card as long as said + // card is not trump or A. + + // If the two cards of the same suit we can compare them using Beat + // since it wouldn't matter who led. + if card.AdjSuit(top.Suit) == minCard.AdjSuit(top.Suit) { + if euchre.Beat(minCard, card, top.Suit) { + minCard = card + minIndex = i + } + } else if minCard.IsTrump(top.Suit) || (!card.IsTrump(top.Suit) && + deck.ValueCompare(card, minCard) < 0) { + // If the cards are not of the same suit then if the current min is + // trump, the new card must be less. Otherwise, as long as the + // card in question is not trump and it's value is less then the + // current min card, update the trackers. + minCard = card + minIndex = i + } + } + } + + hand[minIndex] = top + hand = hand[:len(hand)-1] + + return hand, minCard } - func (p *SmartPlayer) Call(hand []deck.Card, top deck.Card, - who int) (deck.Suit, bool) { - played := make([]deck.Card, 0) - prior := make([]euchre.Trick, 0) - max := math.Inf(-1) - var maxSuit deck.Suit - - for i := 0; i < len(deck.SUITS); i++ { - suit := deck.SUITS[i] - - if suit != top.Suit { - setup := euchre.Setup { - who, - 0, - false, - top, - suit, - deck.Card{}, - -1, - } - - s := euchre.NewUndeterminizedState(setup, 0, hand, played, prior) - e := euchre.Engine{ } - _, expected := ai.MCTS(s, e, p.callRuns, p.callDeterminizations) - - if expected > max { - max = expected - maxSuit = suit - } - } - } - - return maxSuit, max > p.callConfidence + who int) (deck.Suit, bool) { + played := make([]deck.Card, 0) + prior := make([]euchre.Trick, 0) + max := math.Inf(-1) + var maxSuit deck.Suit + + for i := 0; i < len(deck.SUITS); i++ { + suit := deck.SUITS[i] + + if suit != top.Suit { + setup := euchre.Setup{ + who, + 0, + false, + top, + suit, + deck.Card{}, + -1, + } + + s := euchre.NewUndeterminizedState(setup, 0, hand, played, prior) + e := euchre.Engine{} + _, expected := ai.MCTS(s, e, p.callRuns, p.callDeterminizations) + + if expected > max { + max = expected + maxSuit = suit + } + } + } + + return maxSuit, max > p.callConfidence } - func (p *SmartPlayer) Alone(hand []deck.Card, top deck.Card, who int) bool { - played := make([]deck.Card, 0) - prior := make([]euchre.Trick, 0) - nPlayer := (who + 1) % 4 - - nHand := make([]deck.Card, len(hand)) - copy(nHand, hand) - _, discard := p.Discard(nHand, top) - - setup := euchre.Setup { - who, - 0, - false, - top, - top.Suit, - discard, - 0, - } - - s := euchre.NewUndeterminizedState(setup, nPlayer, hand, played, prior) - e := euchre.Engine{} - _, expected := ai.MCTS(s, e, p.aloneRuns, p.aloneDeterminizations) - - return (nPlayer % 2 == 0 && expected > p.aloneConfidence) || - (nPlayer % 2 == 1 && expected < -1 * p.aloneConfidence) + played := make([]deck.Card, 0) + prior := make([]euchre.Trick, 0) + nPlayer := (who + 1) % 4 + + nHand := make([]deck.Card, len(hand)) + copy(nHand, hand) + _, discard := p.Discard(nHand, top) + + setup := euchre.Setup{ + who, + 0, + false, + top, + top.Suit, + discard, + 0, + } + + s := euchre.NewUndeterminizedState(setup, nPlayer, hand, played, prior) + e := euchre.Engine{} + _, expected := ai.MCTS(s, e, p.aloneRuns, p.aloneDeterminizations) + + return (nPlayer%2 == 0 && expected > p.aloneConfidence) || + (nPlayer%2 == 1 && expected < -1*p.aloneConfidence) } - func (p *SmartPlayer) Play(player int, setup euchre.Setup, hand, - played []deck.Card, - prior []euchre.Trick) ([]deck.Card, deck.Card) { - s := euchre.NewUndeterminizedState(setup, player, hand, played, prior) - e := euchre.Engine{ } - chosenMove, _ := ai.MCTS(s, e, p.playRuns, p.playDeterminizations) - - card := chosenMove.Action.(deck.Card) - - nHand := make([]deck.Card, 0) - for i := 0; i < len(hand); i++ { - if card != hand[i] { - nHand = append(nHand, hand[i]) - } - } - - return nHand, card + played []deck.Card, + prior []euchre.Trick) ([]deck.Card, deck.Card) { + s := euchre.NewUndeterminizedState(setup, player, hand, played, prior) + e := euchre.Engine{} + chosenMove, _ := ai.MCTS(s, e, p.playRuns, p.playDeterminizations) + + card := chosenMove.Action.(deck.Card) + + nHand := make([]deck.Card, 0) + for i := 0; i < len(hand); i++ { + if card != hand[i] { + nHand = append(nHand, hand[i]) + } + } + + return nHand, card }