impl some hands

master
Bel LaPointe 2021-03-17 08:16:47 -05:00
parent ec81bb24ad
commit 0c406e3163
5 changed files with 597 additions and 1 deletions

View File

@ -1,5 +1,9 @@
package entity package entity
import (
"sort"
)
type Hand struct { type Hand struct {
Public []Card Public []Card
Private []Card Private []Card
@ -15,3 +19,38 @@ func (hand *Hand) Push(card Card) {
func (hand Hand) Len() int { func (hand Hand) Len() int {
return len(hand.Public) + len(hand.Private) return len(hand.Public) + len(hand.Private)
} }
func (hand Hand) AllCards() []Card {
allcards := make([]Card, 0, len(hand.Public)+len(hand.Private))
for _, cards := range [][]Card{hand.Public, hand.Private} {
allcards = append(allcards, cards...)
}
return allcards
}
func (hand Hand) Flush() bool {
suit := -1
for _, card := range hand.AllCards() {
if suit == -1 {
suit = card.Suit
} else if suit != card.Suit {
return false
}
}
return suit != -1
}
func (hand Hand) Straight() bool {
cards := hand.AllCards()
values := make([]int, 0, len(cards))
for _, card := range cards {
values = append(values, card.Value)
}
sort.Ints(values)
for i := 1; i < len(values); i++ {
if values[i-1]+1 != values[i] {
return false
}
}
return len(values) > 1
}

107
src/entity/hand_test.go Normal file
View File

@ -0,0 +1,107 @@
package entity
import (
"testing"
)
func TestHandAllCards(t *testing.T) {
t.Run("all pub", func(t *testing.T) {
hand := Hand{
Public: []Card{
Card{Value: 1},
Card{Value: 2},
},
}
all := hand.AllCards()
if len(all) != 2 {
t.Fatal(all)
}
})
t.Run("all pri", func(t *testing.T) {
hand := Hand{
Private: []Card{
Card{Value: 1},
Card{Value: 2},
},
}
all := hand.AllCards()
if len(all) != 2 {
t.Fatal(all)
}
})
t.Run("both", func(t *testing.T) {
hand := Hand{
Public: []Card{
Card{Value: 3},
Card{Value: 4},
},
Private: []Card{
Card{Value: 1},
Card{Value: 2},
},
}
all := hand.AllCards()
if len(all) != 4 {
t.Fatal(all)
}
})
}
func TestHandStraight(t *testing.T) {
cases := map[string]struct {
public []Card
private []Card
want bool
}{
"empty": {
want: false,
},
"one public card": {
public: []Card{Card{Value: 3}},
want: false,
},
"one private card": {
private: []Card{Card{Value: 3}},
want: false,
},
"two public card nonseq": {
public: []Card{Card{Value: 3}, Card{Value: 1}},
want: false,
},
"two private card nonseq": {
private: []Card{Card{Value: 3}, Card{Value: 1}},
want: false,
},
"one public card one private card nonseq": {
public: []Card{Card{Value: 3}},
private: []Card{Card{Value: 1}},
want: false,
},
"two public card seq": {
public: []Card{Card{Value: 3}, Card{Value: 2}},
want: true,
},
"two private card seq": {
private: []Card{Card{Value: 3}, Card{Value: 2}},
want: true,
},
"one public card one private card seq": {
public: []Card{Card{Value: 2}},
private: []Card{Card{Value: 1}},
want: true,
},
}
for name, d := range cases {
c := d
t.Run(name, func(t *testing.T) {
hand := Hand{Public: c.public, Private: c.private}
got := hand.Straight()
if got != c.want {
t.Fatal(c.want, got)
}
})
}
}

View File

@ -0,0 +1,135 @@
package operation
import (
"local/sandbox/cards/src/entity"
"sort"
)
func royalFlush(game *entity.Game, _ interface{}) int {
hand := getHand(game)
if !hand.Flush() || !hand.Straight() {
return 0
}
for _, card := range hand.AllCards() {
if card.Value < 11 {
return 0
}
}
return 1
}
func straightFlush(game *entity.Game, _ interface{}) int {
hand := getHand(game)
if !hand.Flush() || !hand.Straight() {
return 0
}
biggest := 0
for _, card := range hand.AllCards() {
if card.Value > biggest {
biggest = card.Value
}
}
return biggest
}
func fourOfAKind(game *entity.Game, _ interface{}) int {
hand := getHand(game)
counts := counts(hand)
best := entity.Card{}
high := entity.Card{}
for k, v := range counts {
if v >= 4 && k.Value > best.Value {
best = k
}
if v != 4 && k.Value > high.Value {
high = k
}
}
if best.Value == 0 {
return 0
}
return best.Value*100 + high.Value
}
func fullHouse(game *entity.Game, _ interface{}) int {
hand := getHand(game)
counts := counts(hand)
trio := entity.Card{}
duo := entity.Card{}
for k, v := range counts {
if v == 3 && k.Value > trio.Value {
trio = k
}
if v == 2 && k.Value > duo.Value {
duo = k
}
}
if trio.Value == 0 || duo.Value == 0 {
return 0
}
return trio.Value*100 + duo.Value
}
func flush(game *entity.Game, _ interface{}) int {
hand := getHand(game)
if !hand.Flush() {
return 0
}
values := make([]int, 0)
for _, card := range hand.AllCards() {
values = append(values, card.Value)
}
sort.Ints(values)
rank := 0
for i := len(values) - 1; i >= 0; i-- {
rank = rank*100 + values[i]
}
return rank
}
func straight(game *entity.Game, _ interface{}) int {
hand := getHand(game)
if !hand.Straight() {
return 0
}
big := 0
for _, card := range hand.AllCards() {
if card.Value > big {
big = card.Value
}
}
return big
}
func threeOfAKind(game *entity.Game, _ interface{}) int {
panic("not impl")
}
func twoPair(game *entity.Game, _ interface{}) int {
panic("not impl")
}
func pair(game *entity.Game, _ interface{}) int {
panic("not impl")
}
func highCard(game *entity.Game, _ interface{}) int {
panic("not impl")
}
func getHand(game *entity.Game) entity.Hand {
player := game.Players[game.Current.Turn]
return player.Hand
}
func counts(hand entity.Hand) map[entity.Card]int {
cards := hand.AllCards()
m := make(map[entity.Card]int, len(cards))
for _, card := range cards {
if _, ok := m[card]; !ok {
m[card] = 0
}
m[card] += 1
}
return m
}

View File

@ -0,0 +1,279 @@
package operation
import (
"local/sandbox/cards/src/entity"
"testing"
)
type testHandCase struct {
public []entity.Card
private []entity.Card
want int
}
func TestStraight(t *testing.T) {
cases := map[string]testHandCase{
"no cards": {
want: 0,
},
"straight big 3": {
public: []entity.Card{
entity.Card{Value: 11},
entity.Card{Value: 12},
entity.Card{Value: 13},
},
want: 13,
},
"straight sm 3": {
public: []entity.Card{
entity.Card{Value: 1},
entity.Card{Value: 2},
entity.Card{Value: 3},
},
want: 3,
},
"straight sm 2": {
public: []entity.Card{
entity.Card{Value: 1},
entity.Card{Value: 2},
},
want: 2,
},
"not a straight": {
public: []entity.Card{
entity.Card{Value: 1},
entity.Card{Value: 3},
},
want: 0,
},
}
for name, d := range cases {
testHand(t, name, straight, d.public, d.private, d.want)
}
}
func TestFlush(t *testing.T) {
cases := map[string]testHandCase{
"no cards": {
want: 0,
},
"not a flush": {
public: []entity.Card{
entity.Card{Value: 1, Suit: 0},
entity.Card{Value: 2, Suit: 1},
},
want: 0,
},
"big, big, small, small flush": {
public: []entity.Card{
entity.Card{Value: 12},
entity.Card{Value: 10},
entity.Card{Value: 2},
entity.Card{Value: 1},
},
want: 12100201,
},
"small, small flush": {
public: []entity.Card{
entity.Card{Value: 2},
entity.Card{Value: 1},
},
want: 201,
},
"big, small flush": {
public: []entity.Card{
entity.Card{Value: 11},
entity.Card{Value: 1},
},
want: 1101,
},
"big, big flush": {
public: []entity.Card{
entity.Card{Value: 11},
entity.Card{Value: 12},
},
want: 1211,
},
}
for name, d := range cases {
testHand(t, name, flush, d.public, d.private, d.want)
}
}
func TestFullHouse(t *testing.T) {
cases := map[string]testHandCase{
"no cards": {
want: 0,
},
"10 > 11": {
public: []entity.Card{
entity.Card{Value: 11},
entity.Card{Value: 11},
entity.Card{Value: 10},
entity.Card{Value: 10},
entity.Card{Value: 10},
},
want: 1011,
},
"five of a kind": {
public: []entity.Card{
entity.Card{Value: 5},
entity.Card{Value: 5},
entity.Card{Value: 5},
entity.Card{Value: 5},
entity.Card{Value: 5},
},
want: 0,
},
"two pair": {
public: []entity.Card{
entity.Card{Value: 4},
entity.Card{Value: 4},
entity.Card{Value: 5},
entity.Card{Value: 5},
entity.Card{Value: 6},
},
want: 0,
},
}
for name, d := range cases {
testHand(t, name, fullHouse, d.public, d.private, d.want)
}
}
func TestFourOfAKind(t *testing.T) {
cases := map[string]testHandCase{
"no cards": {
want: 0,
},
"one card": {
public: []entity.Card{
entity.Card{Value: 5},
},
want: 0,
},
"two cards": {
public: []entity.Card{
entity.Card{Value: 5},
entity.Card{Value: 5},
},
want: 0,
},
"three cards": {
public: []entity.Card{
entity.Card{Value: 5},
entity.Card{Value: 5},
entity.Card{Value: 5},
},
want: 0,
},
"four cards": {
public: []entity.Card{
entity.Card{Value: 5},
entity.Card{Value: 5},
entity.Card{Value: 5},
entity.Card{Value: 5},
},
want: 500,
},
"five cards": {
public: []entity.Card{
entity.Card{Value: 5},
entity.Card{Value: 5},
entity.Card{Value: 5},
entity.Card{Value: 5},
entity.Card{Value: 5},
},
want: 505,
},
}
for name, d := range cases {
testHand(t, name, fourOfAKind, d.public, d.private, d.want)
}
}
func TestStraightFlush(t *testing.T) {
cases := map[string]testHandCase{
"no cards": {
want: 0,
},
"one nonroyal cards": {
public: []entity.Card{entity.Card{Value: 5}},
want: 0,
},
"one royal cards": {
public: []entity.Card{entity.Card{Value: 12}},
want: 0,
},
"two nonroyal cards": {
public: []entity.Card{entity.Card{Value: 5}, entity.Card{Value: 6}},
want: 6,
},
"twe royal cards": {
public: []entity.Card{entity.Card{Value: 12}, entity.Card{Value: 13}},
want: 13,
},
"twe mix cards": {
public: []entity.Card{entity.Card{Value: 11}, entity.Card{Value: 10}},
want: 11,
},
}
for name, d := range cases {
testHand(t, name, straightFlush, d.public, d.private, d.want)
}
}
func TestRoyalFlush(t *testing.T) {
cases := map[string]testHandCase{
"no cards": {
want: 0,
},
"one nonroyal cards": {
public: []entity.Card{entity.Card{Value: 5}},
want: 0,
},
"one royal cards": {
public: []entity.Card{entity.Card{Value: 12}},
want: 0,
},
"two nonroyal cards": {
public: []entity.Card{entity.Card{Value: 5}, entity.Card{Value: 6}},
want: 0,
},
"twe royal cards": {
public: []entity.Card{entity.Card{Value: 12}, entity.Card{Value: 13}},
want: 1,
},
"twe mix cards": {
public: []entity.Card{entity.Card{Value: 11}, entity.Card{Value: 10}},
want: 0,
},
}
for name, d := range cases {
testHand(t, name, royalFlush, d.public, d.private, d.want)
}
}
func testHand(t *testing.T, name string, foo Int, public []entity.Card, private []entity.Card, want int) {
t.Run(name, func(t *testing.T) {
hand := entity.Hand{
Public: public,
Private: private,
}
game := &entity.Game{
Players: []entity.Player{
entity.Player{Hand: hand},
},
}
got := foo(game, nil)
if got != want {
t.Fatal(want, got)
}
})
}

View File

@ -1,5 +1,41 @@
package operation package operation
import "local/sandbox/cards/src/entity" import (
"encoding/json"
"errors"
"local/sandbox/cards/src/entity"
)
type Int func(*entity.Game, interface{}) int type Int func(*entity.Game, interface{}) int
var intStringified = map[string]Int{
"royalFlush": royalFlush,
"straightFlush": straightFlush,
"fourOfAKind": fourOfAKind,
"fullHouse": fullHouse,
"flush": flush,
"straight": straight,
"threeOfAKind": threeOfAKind,
"twoPair": twoPair,
"pair": pair,
"highCard": highCard,
}
func (foo *Int) UnmarshalJSON(b []byte) error {
var s string
if err := json.Unmarshal(b, &s); err != nil {
return err
}
return foo.FromString(s)
}
func (foo *Int) FromString(s string) error {
for k, v := range intStringified {
if k == s {
*foo = v
return nil
}
}
return errors.New("unknown int method " + s)
}