From d79721b760b62531cb7b702d4dd45f9f52c0fed1 Mon Sep 17 00:00:00 2001 From: Bel LaPointe Date: Sun, 14 Mar 2021 23:29:50 -0500 Subject: [PATCH] Create some subroutines and test --- src/game/rule/operation/bool.go | 36 +++ src/game/rule/operation/compare.go | 20 ++ src/game/rule/operation/compare_test.go | 37 +++ src/game/rule/operation/convert.go | 22 ++ src/game/rule/operation/int.go | 5 + src/game/rule/operation/operation_test.go | 13 + src/game/rule/operation/phase.go | 80 +++++ src/game/rule/operation/phase_test.go | 365 ++++++++++++++++++++++ 8 files changed, 578 insertions(+) create mode 100644 src/game/rule/operation/bool.go create mode 100644 src/game/rule/operation/compare.go create mode 100644 src/game/rule/operation/compare_test.go create mode 100644 src/game/rule/operation/convert.go create mode 100644 src/game/rule/operation/int.go create mode 100644 src/game/rule/operation/operation_test.go create mode 100644 src/game/rule/operation/phase.go create mode 100644 src/game/rule/operation/phase_test.go diff --git a/src/game/rule/operation/bool.go b/src/game/rule/operation/bool.go new file mode 100644 index 0000000..a6273ea --- /dev/null +++ b/src/game/rule/operation/bool.go @@ -0,0 +1,36 @@ +package operation + +import ( + "encoding/json" + "errors" + "local/sandbox/cards/src/entity" +) + +type Bool func(*entity.Game, interface{}) bool + +var boolStringified = map[string]Bool{ + "charge": charge, + "deal": deal, + "bet": bet, + "trade": trade, + "end": end, +} + +func (foo *Bool) UnmarshalJSON(b []byte) error { + var s string + if err := json.Unmarshal(b, &s); err != nil { + return err + } + + return foo.FromString(s) +} + +func (foo *Bool) FromString(s string) error { + for k, v := range boolStringified { + if k == s { + *foo = v + return nil + } + } + return errors.New("unknown bool method " + s) +} diff --git a/src/game/rule/operation/compare.go b/src/game/rule/operation/compare.go new file mode 100644 index 0000000..aa5689f --- /dev/null +++ b/src/game/rule/operation/compare.go @@ -0,0 +1,20 @@ +package operation + +import ( + "strings" +) + +func compareNumber(a int, b interface{}) int { + v := convertNumber(b) + if a == v { + return 0 + } + if a > v { + return 1 + } + return -1 +} + +func compareString(a string, b interface{}) int { + return strings.Compare(a, convertString(b)) +} diff --git a/src/game/rule/operation/compare_test.go b/src/game/rule/operation/compare_test.go new file mode 100644 index 0000000..15fc2c8 --- /dev/null +++ b/src/game/rule/operation/compare_test.go @@ -0,0 +1,37 @@ +package operation + +import "testing" + +func TestCompareNumber(t *testing.T) { + cases := map[string]struct { + a int + b interface{} + want int + }{ + "int int equal": { + a: 1, + b: 1, + want: 0, + }, + "int int gt": { + a: 2, + b: 1, + want: 1, + }, + "int int lt": { + a: 0, + b: 1, + want: -1, + }, + } + + for name, d := range cases { + c := d + t.Run(name, func(t *testing.T) { + got := compareNumber(c.a, c.b) + if got != c.want { + t.Fatal(got, c.a, c.b) + } + }) + } +} diff --git a/src/game/rule/operation/convert.go b/src/game/rule/operation/convert.go new file mode 100644 index 0000000..7ee42f5 --- /dev/null +++ b/src/game/rule/operation/convert.go @@ -0,0 +1,22 @@ +package operation + +import ( + "fmt" + "strconv" +) + +func convertNumber(v interface{}) int { + s := fmt.Sprint(v) + v2, _ := strconv.ParseFloat(s, 64) + return int(v2) +} + +func convertString(v interface{}) string { + switch v.(type) { + case string: + return v.(string) + case []byte: + return string(v.([]byte)) + } + return fmt.Sprint(v) +} diff --git a/src/game/rule/operation/int.go b/src/game/rule/operation/int.go new file mode 100644 index 0000000..c3cd13c --- /dev/null +++ b/src/game/rule/operation/int.go @@ -0,0 +1,5 @@ +package operation + +import "local/sandbox/cards/src/entity" + +type Int func(*entity.Game, interface{}) int diff --git a/src/game/rule/operation/operation_test.go b/src/game/rule/operation/operation_test.go new file mode 100644 index 0000000..5ecad98 --- /dev/null +++ b/src/game/rule/operation/operation_test.go @@ -0,0 +1,13 @@ +package operation + +import ( + "local/sandbox/cards/src/entity" + "testing" +) + +func TestOperationInterface(t *testing.T) { + foo := func(*entity.Game, interface{}) int { return 0 } + var _ Int = foo + bar := func(*entity.Game, interface{}) bool { return false } + var _ Bool = bar +} diff --git a/src/game/rule/operation/phase.go b/src/game/rule/operation/phase.go new file mode 100644 index 0000000..44f4668 --- /dev/null +++ b/src/game/rule/operation/phase.go @@ -0,0 +1,80 @@ +package operation + +import ( + "local/sandbox/cards/src/entity" +) + +func charge(game *entity.Game, charge interface{}) bool { + if game == nil { + return false + } + v := entity.Currency(convertNumber(charge)) + game.ChargeActivePlayers(v) + game.NextPhase() + return true +} + +func deal(game *entity.Game, deal interface{}) bool { + if game == nil { + return false + } + n := convertNumber(deal) + game.Deal(n) + game.NextPhase() + return true +} + +func bet(game *entity.Game, _ interface{}) bool { + if game == nil { + return false + } + if len(game.ActivePlayers()) == 0 { + game.NextPhase() + game.NextTurn() + return true + } + + player := &game.Players[game.Current.Turn] + raised := player.Active && player.Bet > game.Current.Bet + if raised { + game.Current.Bet = player.Bet + for i := range game.Players { + game.Players[i].Checked = false + } + } + player.Checked = player.Active && (player.Checked || player.Balance == 0 || player.Bet == game.Current.Bet) + + if game.IsAllActivePlayersChecked() && game.IsPotRight() { + game.NextPhase() + game.NextTurn() + return true + } + + if player.Checked { + game.NextTurn() + } + + return false +} + +func trade(game *entity.Game, max interface{}) bool { + if game == nil { + return false + } + panic("not impl") +} + +func end(game *entity.Game, _ interface{}) bool { + if game == nil { + return false + } + panic("not impl") +} + +func playerCount(game *entity.Game, v interface{}) bool { + if game == nil { + return false + } + v2 := convertNumber(charge) + return len(game.ActivePlayers()) == v2 +} diff --git a/src/game/rule/operation/phase_test.go b/src/game/rule/operation/phase_test.go new file mode 100644 index 0000000..2e55602 --- /dev/null +++ b/src/game/rule/operation/phase_test.go @@ -0,0 +1,365 @@ +package operation + +import ( + "local/sandbox/cards/src/entity" + "testing" +) + +func TestBoolInterface(t *testing.T) { + var _ Bool = charge + var _ Bool = deal + var _ Bool = bet + var _ Bool = trade + var _ Bool = end +} + +func TestCharge(t *testing.T) { + cases := map[string]struct { + game *entity.Game + charge interface{} + check func(bool, *entity.Game) bool + }{ + "game is nil": { + check: func(a bool, _ *entity.Game) bool { + return a == false + }, + }, + "players is nil": { + game: &entity.Game{}, + check: func(a bool, _ *entity.Game) bool { + return a == true + }, + }, + "cannot afford, active": { + game: &entity.Game{ + Players: []entity.Player{ + entity.Player{Balance: 4, Active: true}, + }, + }, + charge: 5, + check: func(a bool, game *entity.Game) bool { + player := game.Players[0] + return a == true && player.Balance == 4 && player.Pot == 0 && game.Pot() == 0 + }, + }, + "cannot afford, not active": { + game: &entity.Game{ + Players: []entity.Player{ + entity.Player{Balance: 4}, + }, + }, + charge: 5, + check: func(a bool, game *entity.Game) bool { + player := game.Players[0] + return a == true && player.Balance == 4 && player.Pot == 0 && game.Pot() == 0 + }, + }, + "can afford, active": { + game: &entity.Game{ + Players: []entity.Player{ + entity.Player{Balance: 6, Active: true}, + }, + }, + charge: 5, + check: func(a bool, game *entity.Game) bool { + player := game.Players[0] + return a == true && player.Balance == 1 && player.Pot == 5 && game.Pot() == 5 + }, + }, + "can afford, not active": { + game: &entity.Game{ + Players: []entity.Player{ + entity.Player{Balance: 6}, + }, + }, + charge: 5, + check: func(a bool, game *entity.Game) bool { + player := game.Players[0] + return a == true && player.Balance == 6 && player.Pot == 0 && game.Pot() == 0 + }, + }, + } + + for name, d := range cases { + c := d + t.Run(name, func(t *testing.T) { + got := charge(c.game, c.charge) + if approved := c.check(got, c.game); !approved { + t.Fatalf("not approved: got=%v, game=%+v", got, c.game) + } + }) + } +} + +func TestDeal(t *testing.T) { + cases := map[string]struct { + game *entity.Game + deal interface{} + check func(bool, *entity.Game) bool + }{ + "game is nil": { + check: func(a bool, _ *entity.Game) bool { + return a == false + }, + }, + "players is nil": { + game: &entity.Game{}, + check: func(a bool, _ *entity.Game) bool { + return a == true + }, + }, + "no active, no hand": { + game: &entity.Game{ + Players: []entity.Player{ + entity.Player{}, + }, + }, + deal: 5, + check: func(a bool, game *entity.Game) bool { + player := game.Players[0] + return a == true && player.Hand.Len() == 0 + }, + }, + "active, no hand": { + game: &entity.Game{ + Players: []entity.Player{ + entity.Player{Active: true}, + }, + }, + deal: 5, + check: func(a bool, game *entity.Game) bool { + player := game.Players[0] + return a == true && player.Hand.Len() == 5 + }, + }, + } + + for name, d := range cases { + c := d + t.Run(name, func(t *testing.T) { + if c.game != nil { + c.game.Deck = newDeck() + } + got := deal(c.game, c.deal) + if approved := c.check(got, c.game); !approved { + t.Fatalf("not approved: got=%v, game=%+v", got, c.game) + } + }) + } +} + +func newDeck() entity.Deck { + deck := make([]entity.Card, 0, 26) + discard := make([]entity.Card, 0, 26) + for i := 0; i < 52; i++ { + card := entity.Card{Suit: i % 4, Value: i % 13} + if i%2 == 1 { + deck = append(deck, card) + } else { + discard = append(discard, card) + } + } + return entity.Deck{ + Deck: deck, + Discard: discard, + } +} + +func TestBet(t *testing.T) { + cases := map[string]struct { + game *entity.Game + check func(bool, *entity.Game) bool + }{ + "game is nil": { + check: func(a bool, _ *entity.Game) bool { + return a == false + }, + }, + "players is nil": { + game: &entity.Game{}, + check: func(a bool, _ *entity.Game) bool { + return a == true + }, + }, + "no active": { + game: &entity.Game{ + Players: []entity.Player{ + entity.Player{}, + }, + }, + check: func(a bool, game *entity.Game) bool { + return a == true && !game.Players[0].Checked + }, + }, + "active, checked": { + game: &entity.Game{ + Players: []entity.Player{ + entity.Player{Active: true, Checked: true}, + }, + }, + check: func(a bool, game *entity.Game) bool { + return a == true && !game.Players[0].Checked + }, + }, + "active, checked, pots wrong, has balance": { + game: &entity.Game{ + Players: []entity.Player{ + entity.Player{Active: true, Checked: true, Bet: 2, Balance: 3}, + }, + Current: entity.Current{ + Bet: 3, + }, + }, + check: func(a bool, _ *entity.Game) bool { + return a == false + }, + }, + "active, checked, pots wrong, no balance": { + game: &entity.Game{ + Players: []entity.Player{ + entity.Player{Active: true, Checked: true, Bet: 2, Balance: 0}, + }, + Current: entity.Current{ + Bet: 3, + }, + }, + check: func(a bool, game *entity.Game) bool { + return a == true && !game.Players[0].Checked + }, + }, + "first turn in phase: checks": { + game: &entity.Game{ + Players: []entity.Player{ + entity.Player{Active: true, Checked: true, Bet: 0, Balance: 5}, + entity.Player{Active: true, Checked: false, Bet: 0, Balance: 5}, + }, + Current: entity.Current{ + Bet: 0, + Turn: 0, + }, + }, + check: func(a bool, game *entity.Game) bool { + return a == false && game.Players[0].Checked && game.Current.Turn == 1 + }, + }, + "only turn in phase: checks": { + game: &entity.Game{ + Players: []entity.Player{ + entity.Player{Active: true, Checked: true, Bet: 0, Balance: 5}, + }, + Current: entity.Current{ + Bet: 0, + Turn: 0, + }, + }, + check: func(a bool, game *entity.Game) bool { + return a == true && !game.Players[0].Checked && game.Current.Turn == 0 + }, + }, + "only turn in phase: raises": { + game: &entity.Game{ + Players: []entity.Player{ + entity.Player{Active: true, Checked: false, Bet: 5, Balance: 5}, + }, + Current: entity.Current{ + Bet: 0, + Turn: 0, + }, + }, + check: func(a bool, game *entity.Game) bool { + return a == true && !game.Players[0].Checked && game.Current.Turn == 0 && game.Players[0].Bet == 0 && game.Players[0].Pot == 5 && game.Current.Bet == 0 + }, + }, + "first turn in phase: raises": { + game: &entity.Game{ + Players: []entity.Player{ + entity.Player{Active: true, Checked: false, Bet: 5, Balance: 5}, + entity.Player{Active: true, Checked: false, Bet: 0, Balance: 5}, + }, + Current: entity.Current{ + Bet: 0, + Turn: 0, + }, + }, + check: func(a bool, game *entity.Game) bool { + return a == false && game.Players[0].Checked && game.Current.Turn == 1 && game.Players[0].Bet == 5 && game.Players[0].Pot == 0 && game.Current.Bet == 5 + }, + }, + "last: check": { + game: &entity.Game{ + Players: []entity.Player{ + entity.Player{Active: true, Checked: true, Bet: 5, Balance: 5}, + entity.Player{Active: true, Checked: true, Bet: 5, Balance: 5}, + }, + Current: entity.Current{ + Bet: 5, + Turn: 1, + }, + }, + check: func(a bool, game *entity.Game) bool { + return a == true && !game.Players[0].Checked && game.Current.Turn == 0 && game.Players[0].Bet == 0 && game.Players[0].Pot == 5 && game.Current.Bet == 0 + }, + }, + "mid: raise": { + game: &entity.Game{ + Players: []entity.Player{ + entity.Player{Active: true, Checked: false, Bet: 5, Balance: 15}, + entity.Player{Active: true, Checked: false, Bet: 7, Balance: 15}, + entity.Player{Active: true, Checked: false, Bet: 0, Balance: 15}, + }, + Current: entity.Current{ + Bet: 5, + Turn: 1, + }, + }, + check: func(a bool, game *entity.Game) bool { + return a == false && !game.Players[0].Checked && game.Current.Turn == 2 && game.Players[0].Bet == 5 && game.Players[0].Pot == 0 && game.Current.Bet == 7 && game.Players[1].Checked && !game.Players[2].Checked && game.Players[1].Bet == 7 && game.Players[1].Pot == 0 + }, + }, + "mid: check": { + game: &entity.Game{ + Players: []entity.Player{ + entity.Player{Active: true, Checked: true, Bet: 0, Balance: 15}, + entity.Player{Active: true, Checked: false, Bet: 0, Balance: 15}, + entity.Player{Active: true, Checked: false, Bet: 0, Balance: 15}, + }, + Current: entity.Current{ + Bet: 0, + Turn: 1, + }, + }, + check: func(a bool, game *entity.Game) bool { + return a == false && game.Players[0].Checked && game.Current.Turn == 2 && game.Players[0].Bet == 0 && game.Players[0].Pot == 0 && game.Current.Bet == 0 && game.Players[1].Checked && !game.Players[2].Checked && game.Players[1].Bet == 0 && game.Players[1].Pot == 0 + }, + }, + "last: raise": { + game: &entity.Game{ + Players: []entity.Player{ + entity.Player{Active: true, Checked: true, Bet: 0, Balance: 15}, + entity.Player{Active: true, Checked: true, Bet: 0, Balance: 15}, + entity.Player{Active: true, Checked: false, Bet: 10, Balance: 5}, + }, + Current: entity.Current{ + Bet: 0, + Turn: 2, + }, + }, + check: func(a bool, game *entity.Game) bool { + return a == false && !game.Players[0].Checked && game.Current.Turn == 0 && game.Players[0].Bet == 0 && game.Players[0].Pot == 0 && game.Current.Bet == 10 && !game.Players[1].Checked && game.Players[2].Checked && game.Players[1].Bet == 0 && game.Players[1].Pot == 0 + }, + }, + } + + for name, d := range cases { + c := d + t.Run(name, func(t *testing.T) { + if c.game != nil { + c.game.Deck = newDeck() + } + got := bet(c.game, nil) + if approved := c.check(got, c.game); !approved { + t.Fatalf("not approved: got=%v, game=%+v", got, c.game) + } + }) + } +}