Compare commits
6 Commits
clientSide
...
60adca804b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
60adca804b | ||
|
|
b72c48f705 | ||
|
|
bce32ed6a3 | ||
|
|
4bf83b3e40 | ||
|
|
cb42bdc8d0 | ||
|
|
ebbf21d23d |
33
games/poker.yaml
Normal file
33
games/poker.yaml
Normal file
@@ -0,0 +1,33 @@
|
||||
# after each phase, given game, evaluate if end
|
||||
end:
|
||||
- op: playerCount
|
||||
operand: 1
|
||||
phases:
|
||||
- op: charge
|
||||
operand: 5
|
||||
- op: deal
|
||||
operand: 5
|
||||
- op: bet
|
||||
- op: trade
|
||||
operand: 5
|
||||
- op: bet
|
||||
- op: end
|
||||
# always return an number type (or zero if not qualified) w/ high card
|
||||
# ie
|
||||
# two pair and a card
|
||||
# king, king, 4, 4, 3 => 1000 * (13) + 10 * (4) + 3 = 1343
|
||||
# TODO: inverted ranking, I think negative is enough
|
||||
hands:
|
||||
- royalFlush
|
||||
- straightFlush
|
||||
- fourOfAKind
|
||||
- fullHouse
|
||||
- flush
|
||||
- straight
|
||||
- threeOfAKind
|
||||
- twoPair
|
||||
- pair
|
||||
- highCard
|
||||
deck:
|
||||
exclude:
|
||||
- joker
|
||||
25
main.go
Normal file
25
main.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"local/sandbox/cards/src/config"
|
||||
"local/sandbox/cards/src/game"
|
||||
"local/sandbox/cards/src/server"
|
||||
"local/sandbox/cards/src/storage"
|
||||
"log"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func main() {
|
||||
config := config.NewConfig()
|
||||
storage := storage.NewStorage(config)
|
||||
gm := game.NewMaster(config, storage)
|
||||
server := server.NewServer(config, gm)
|
||||
if err := server.Routes(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
log.Println(config)
|
||||
if err := http.ListenAndServe(fmt.Sprintf(":%d", config.Server.Port), server); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,18 @@
|
||||
package main
|
||||
package config
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"local/args"
|
||||
"local/storage"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Game struct {
|
||||
db storage.DB
|
||||
DB storage.DB
|
||||
}
|
||||
Server struct {
|
||||
Port int
|
||||
@@ -42,7 +46,7 @@ func NewConfig() Config {
|
||||
}
|
||||
|
||||
config := Config{}
|
||||
config.Game.db = db
|
||||
config.Game.DB = db
|
||||
config.Server.Port = as.GetInt("p")
|
||||
config.Server.File.Root = as.GetString("root")
|
||||
config.Server.File.Prefix = "/" + strings.TrimPrefix(as.GetString("root-http-prefix"), "/")
|
||||
@@ -50,3 +54,21 @@ func NewConfig() Config {
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
func NewTestConfig(t *testing.T) Config {
|
||||
d := t.TempDir()
|
||||
|
||||
config := Config{}
|
||||
config.Game.DB = storage.NewMap()
|
||||
config.Server.Port = 9999
|
||||
config.Server.File.Root = d
|
||||
config.Server.File.Prefix = "/file"
|
||||
config.Server.API.Prefix = "/api"
|
||||
|
||||
err := ioutil.WriteFile(path.Join(d, "index.html"), []byte("Hello, world"), os.ModePerm)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
35
src/consts/errors.go
Normal file
35
src/consts/errors.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package consts
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrGameExists = NewCodeError("game exists", http.StatusConflict)
|
||||
)
|
||||
|
||||
type CodeError struct {
|
||||
Err string
|
||||
Status int
|
||||
}
|
||||
|
||||
func NewCodeError(err string, status int) CodeError {
|
||||
return CodeError{
|
||||
Err: err,
|
||||
Status: status,
|
||||
}
|
||||
}
|
||||
|
||||
func (ce CodeError) Error() string {
|
||||
if ce.Err == "" {
|
||||
return "unspecified error occurred"
|
||||
}
|
||||
return ce.Err
|
||||
}
|
||||
|
||||
func (ce CodeError) Code() int {
|
||||
if ce.Status == 0 {
|
||||
return http.StatusInternalServerError
|
||||
}
|
||||
return ce.Status
|
||||
}
|
||||
6
src/entity/card.go
Normal file
6
src/entity/card.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package entity
|
||||
|
||||
type Card struct {
|
||||
Suit int
|
||||
Value int
|
||||
}
|
||||
3
src/entity/currency.go
Normal file
3
src/entity/currency.go
Normal file
@@ -0,0 +1,3 @@
|
||||
package entity
|
||||
|
||||
type Currency int
|
||||
14
src/entity/game.go
Normal file
14
src/entity/game.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package entity
|
||||
|
||||
import "fmt"
|
||||
|
||||
type Game struct {
|
||||
Pot Currency
|
||||
Turn int
|
||||
Players Players
|
||||
Log string
|
||||
}
|
||||
|
||||
func (game Game) Equals(gameB Game) bool {
|
||||
return fmt.Sprint(game) != fmt.Sprint(gameB)
|
||||
}
|
||||
15
src/entity/player.go
Normal file
15
src/entity/player.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package entity
|
||||
|
||||
type Player struct {
|
||||
ID string
|
||||
Name string
|
||||
Card Card
|
||||
Balance Currency
|
||||
Pot Currency
|
||||
Active bool
|
||||
Checked bool
|
||||
}
|
||||
|
||||
func (p Player) Empty() bool {
|
||||
return p == (Player{})
|
||||
}
|
||||
16
src/entity/player_test.go
Normal file
16
src/entity/player_test.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package entity
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPlayerEmpty(t *testing.T) {
|
||||
var p Player
|
||||
if !p.Empty() {
|
||||
t.Fatal(p)
|
||||
}
|
||||
p.ID = "id"
|
||||
if p.Empty() {
|
||||
t.Fatal(p)
|
||||
}
|
||||
}
|
||||
7
src/entity/players.go
Normal file
7
src/entity/players.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package entity
|
||||
|
||||
type Players []Player
|
||||
|
||||
func (players *Players) Add(player Player) {
|
||||
*players = append(*players, player)
|
||||
}
|
||||
43
src/game/game.go
Normal file
43
src/game/game.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package game
|
||||
|
||||
import (
|
||||
"local/sandbox/cards/src/consts"
|
||||
"local/sandbox/cards/src/entity"
|
||||
)
|
||||
|
||||
func (gm *Master) ListGames() ([]string, error) {
|
||||
return gm.storage.ListGames()
|
||||
}
|
||||
|
||||
func (gm *Master) GetGame(id string) (entity.Game, error) {
|
||||
gm.locks.RLock(id)
|
||||
defer gm.locks.RUnlock(id)
|
||||
|
||||
game, err := gm.storage.GetGame(id)
|
||||
if game.Players == nil {
|
||||
game.Players = make(entity.Players, 0)
|
||||
}
|
||||
return game, err
|
||||
}
|
||||
|
||||
func (gm *Master) CreateGame(id string) error {
|
||||
gm.locks.Lock(id)
|
||||
defer gm.locks.Unlock(id)
|
||||
|
||||
if _, err := gm.storage.GetGame(id); err == nil {
|
||||
return consts.ErrGameExists
|
||||
}
|
||||
|
||||
return gm.storage.CreateGame(id)
|
||||
}
|
||||
|
||||
func (gm *Master) ReplaceGame(id string, game entity.Game) error {
|
||||
gm.locks.Lock(id)
|
||||
defer gm.locks.Unlock(id)
|
||||
|
||||
if _, err := gm.storage.GetGame(id); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return gm.storage.ReplaceGame(id, game)
|
||||
}
|
||||
27
src/game/master.go
Normal file
27
src/game/master.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package game
|
||||
|
||||
import (
|
||||
"local/sandbox/cards/src/config"
|
||||
"local/sandbox/cards/src/storage"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type Master struct {
|
||||
config config.Config
|
||||
storage Storage
|
||||
locks *storage.RWLockMap
|
||||
}
|
||||
|
||||
func NewMaster(config config.Config, s Storage) *Master {
|
||||
return &Master{
|
||||
config: config,
|
||||
storage: s,
|
||||
locks: storage.NewRWLockMap(),
|
||||
}
|
||||
}
|
||||
|
||||
func NewTestMaster(t *testing.T) *Master {
|
||||
config := config.NewTestConfig(t)
|
||||
storage := storage.NewStorage(config)
|
||||
return NewMaster(config, storage)
|
||||
}
|
||||
4
src/game/rule.go
Normal file
4
src/game/rule.go
Normal file
@@ -0,0 +1,4 @@
|
||||
package game
|
||||
|
||||
type Rule interface {
|
||||
}
|
||||
12
src/game/storage.go
Normal file
12
src/game/storage.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package game
|
||||
|
||||
import (
|
||||
"local/sandbox/cards/src/entity"
|
||||
)
|
||||
|
||||
type Storage interface {
|
||||
CreateGame(string) error
|
||||
GetGame(string) (entity.Game, error)
|
||||
ListGames() ([]string, error)
|
||||
ReplaceGame(string, entity.Game) error
|
||||
}
|
||||
@@ -1,15 +1,12 @@
|
||||
package main
|
||||
package game
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"local/sandbox/cards/src/entity"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func mockGameMaster(t *testing.T) *GameMaster {
|
||||
config := mockConfig(t)
|
||||
storage := NewStorage(config)
|
||||
return NewGameMaster(config, storage)
|
||||
}
|
||||
|
||||
func TestGameMasterGetCreateGetList(t *testing.T) {
|
||||
gm := mockGameMaster(t)
|
||||
func TestMasterGetCreateGetList(t *testing.T) {
|
||||
gm := NewTestMaster(t)
|
||||
id := "game"
|
||||
|
||||
if games, err := gm.ListGames(); err != nil {
|
||||
@@ -33,8 +30,8 @@ func TestGameMasterGetCreateGetList(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGameMasterUpdate(t *testing.T) {
|
||||
gm := mockGameMaster(t)
|
||||
func TestMasterUpdate(t *testing.T) {
|
||||
gm := NewTestMaster(t)
|
||||
id := "game"
|
||||
|
||||
err := gm.CreateGame(id)
|
||||
@@ -47,7 +44,9 @@ func TestGameMasterUpdate(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
game.Players[2].ID = "hi"
|
||||
game.Players.Add(entity.Player{
|
||||
ID: "hi",
|
||||
})
|
||||
game.Pot = 123
|
||||
err = gm.ReplaceGame(id, game)
|
||||
if err != nil {
|
||||
@@ -58,7 +57,7 @@ func TestGameMasterUpdate(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if game2 != game {
|
||||
if game2.Equals(game) {
|
||||
t.Fatalf("replace+get don't match:\nwant\t%+v\ngot\t%+v", game, game2)
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"local/storage"
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func mockConfig(t *testing.T) Config {
|
||||
d := t.TempDir()
|
||||
|
||||
config := Config{}
|
||||
config.Game.db = storage.NewMap()
|
||||
config.Server.Port = 9999
|
||||
config.Server.File.Root = d
|
||||
config.Server.File.Prefix = "/file"
|
||||
config.Server.API.Prefix = "/api"
|
||||
|
||||
err := ioutil.WriteFile(path.Join(d, "index.html"), []byte("Hello, world"), os.ModePerm)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package main
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
errGameExists = errors.New("cannot create game: already exists")
|
||||
)
|
||||
@@ -1,41 +0,0 @@
|
||||
package main
|
||||
|
||||
type Players [16]Player
|
||||
|
||||
type Game struct {
|
||||
Pot Currency
|
||||
Turn int
|
||||
Players Players
|
||||
Log string
|
||||
}
|
||||
|
||||
type Player struct {
|
||||
ID string
|
||||
Name string
|
||||
Card Card
|
||||
Balance Currency
|
||||
Active bool
|
||||
Participating bool
|
||||
Checked bool
|
||||
}
|
||||
|
||||
type Currency int
|
||||
|
||||
type Card struct {
|
||||
Suit int
|
||||
Value int
|
||||
}
|
||||
|
||||
func (game Game) GetPlayers() []Player {
|
||||
players := []Player{}
|
||||
for _, player := range game.Players {
|
||||
if !player.Empty() {
|
||||
players = append(players, player)
|
||||
}
|
||||
}
|
||||
return players
|
||||
}
|
||||
|
||||
func (p Player) Empty() bool {
|
||||
return p == (Player{})
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPlayerEmpty(t *testing.T) {
|
||||
var p Player
|
||||
if !p.Empty() {
|
||||
t.Fatal(p)
|
||||
}
|
||||
p.ID = "id"
|
||||
if p.Empty() {
|
||||
t.Fatal(p)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGameGetPlayers(t *testing.T) {
|
||||
var g Game
|
||||
if players := g.GetPlayers(); len(players) != 0 {
|
||||
t.Fatal(players)
|
||||
}
|
||||
g.Players[5].ID = "id"
|
||||
if players := g.GetPlayers(); len(players) != 1 {
|
||||
t.Fatal(players)
|
||||
} else if players[0].ID != "id" {
|
||||
t.Fatal(players[0])
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
package main
|
||||
|
||||
type GameMaster struct {
|
||||
config Config
|
||||
storage *Storage
|
||||
locks *RWLockMap
|
||||
}
|
||||
|
||||
func NewGameMaster(config Config, storage *Storage) *GameMaster {
|
||||
return &GameMaster{
|
||||
config: config,
|
||||
storage: storage,
|
||||
locks: NewRWLockMap(),
|
||||
}
|
||||
}
|
||||
|
||||
func (gm *GameMaster) ListGames() ([]string, error) {
|
||||
return gm.storage.ListGames()
|
||||
}
|
||||
|
||||
func (gm *GameMaster) GetGame(id string) (Game, error) {
|
||||
gm.locks.RLock(id)
|
||||
defer gm.locks.RUnlock(id)
|
||||
|
||||
return gm.storage.GetGame(id)
|
||||
}
|
||||
|
||||
func (gm *GameMaster) CreateGame(id string) error {
|
||||
gm.locks.Lock(id)
|
||||
defer gm.locks.Unlock(id)
|
||||
|
||||
if _, err := gm.storage.GetGame(id); err == nil {
|
||||
return errGameExists
|
||||
}
|
||||
|
||||
return gm.storage.CreateGame(id)
|
||||
}
|
||||
|
||||
func (gm *GameMaster) ReplaceGame(id string, game Game) error {
|
||||
gm.locks.Lock(id)
|
||||
defer gm.locks.Unlock(id)
|
||||
|
||||
if _, err := gm.storage.GetGame(id); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return gm.storage.ReplaceGame(id, game)
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func main() {
|
||||
config := NewConfig()
|
||||
storage := NewStorage(config)
|
||||
gm := NewGameMaster(config, storage)
|
||||
server := NewServer(config, gm)
|
||||
if err := server.Routes(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
log.Println(config)
|
||||
if err := http.ListenAndServe(fmt.Sprintf(":%d", config.Server.Port), server); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,12 @@
|
||||
package main
|
||||
package server
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"local/router"
|
||||
"local/sandbox/cards/src/config"
|
||||
"local/sandbox/cards/src/entity"
|
||||
"local/sandbox/cards/src/game"
|
||||
"local/storage"
|
||||
"log"
|
||||
"net/http"
|
||||
@@ -11,12 +14,12 @@ import (
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
config Config
|
||||
gm *GameMaster
|
||||
config config.Config
|
||||
gm *game.Master
|
||||
*router.Router
|
||||
}
|
||||
|
||||
func NewServer(config Config, gm *GameMaster) *Server {
|
||||
func NewServer(config config.Config, gm *game.Master) *Server {
|
||||
return &Server{
|
||||
config: config,
|
||||
gm: gm,
|
||||
@@ -102,7 +105,7 @@ func (server *Server) GameReplace(w http.ResponseWriter, r *http.Request) {
|
||||
badRequest(w, err.Error())
|
||||
return
|
||||
}
|
||||
var game Game
|
||||
var game entity.Game
|
||||
err = json.NewDecoder(r.Body).Decode(&game)
|
||||
if err != nil {
|
||||
badRequest(w, err.Error())
|
||||
@@ -128,7 +131,7 @@ func (server *Server) GameInsert(w http.ResponseWriter, r *http.Request) {
|
||||
internalError(w, err.Error())
|
||||
return
|
||||
}
|
||||
json.NewEncoder(w).Encode(Game{})
|
||||
json.NewEncoder(w).Encode(entity.Game{})
|
||||
}
|
||||
|
||||
func notImpl(w http.ResponseWriter) {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package main
|
||||
package server
|
||||
|
||||
import (
|
||||
"local/sandbox/cards/src/config"
|
||||
"local/sandbox/cards/src/game"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"path"
|
||||
@@ -9,8 +11,9 @@ import (
|
||||
)
|
||||
|
||||
func TestServerRouter(t *testing.T) {
|
||||
gm := mockGameMaster(t)
|
||||
server := NewServer(gm.config, gm)
|
||||
config := config.NewTestConfig(t)
|
||||
gm := game.NewTestMaster(t)
|
||||
server := NewServer(config, gm)
|
||||
if err := server.Routes(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -55,7 +58,7 @@ func TestServerRouter(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestServerByMethod(t *testing.T) {
|
||||
server := NewServer(Config{}, nil)
|
||||
server := NewServer(config.Config{}, nil)
|
||||
|
||||
gotGet := false
|
||||
get := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { gotGet = true })
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package main
|
||||
package storage
|
||||
|
||||
import "sync"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package main
|
||||
package storage
|
||||
|
||||
import "testing"
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package main
|
||||
package storage
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"local/sandbox/cards/src/config"
|
||||
"local/sandbox/cards/src/entity"
|
||||
"local/storage"
|
||||
)
|
||||
|
||||
@@ -10,18 +12,18 @@ const (
|
||||
)
|
||||
|
||||
type Storage struct {
|
||||
config Config
|
||||
config config.Config
|
||||
db storage.DB
|
||||
}
|
||||
|
||||
func NewStorage(config Config) *Storage {
|
||||
func NewStorage(config config.Config) *Storage {
|
||||
return &Storage{
|
||||
config: config,
|
||||
db: config.Game.db,
|
||||
db: config.Game.DB,
|
||||
}
|
||||
}
|
||||
|
||||
func (storage Storage) ReplaceGame(id string, game Game) error {
|
||||
func (storage Storage) ReplaceGame(id string, game entity.Game) error {
|
||||
b, err := json.Marshal(game)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -29,8 +31,8 @@ func (storage Storage) ReplaceGame(id string, game Game) error {
|
||||
return storage.db.Set(id, b, nsGames)
|
||||
}
|
||||
|
||||
func (storage Storage) GetGame(id string) (Game, error) {
|
||||
var game Game
|
||||
func (storage Storage) GetGame(id string) (entity.Game, error) {
|
||||
var game entity.Game
|
||||
|
||||
b, err := storage.db.Get(id, nsGames)
|
||||
if err != nil {
|
||||
@@ -42,7 +44,7 @@ func (storage Storage) GetGame(id string) (Game, error) {
|
||||
}
|
||||
|
||||
func (storage Storage) CreateGame(id string) error {
|
||||
b, err := json.Marshal(Game{})
|
||||
b, err := json.Marshal(entity.Game{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
Reference in New Issue
Block a user