This commit is contained in:
a 2022-04-13 19:57:28 -05:00
parent 95e6383f94
commit 105e28af36
5 changed files with 192 additions and 9 deletions

View File

@ -62,15 +62,8 @@ lives: %0.4v | score: %d | combo: %d
if err != nil { if err != nil {
log.Println(err) log.Println(err)
} }
} else {
trial.SelectCard(0)
} }
fmt.Printf("hand: %s\n", trial.ShowString()) fmt.Printf("hand: %s\n", trial.ShowString())
case "debug":
fmt.Println(trial)
case "exit", "quit", "q":
os.Exit(0)
} }
} }
} }

145
cmd/server/main.go Normal file
View File

@ -0,0 +1,145 @@
package main
import (
"encoding/json"
"log"
"os"
"strconv"
"context"
"net/http"
"git.tuxpa.in/a/card_id/common/game"
"git.tuxpa.in/a/card_id/common/game/card"
"lukechampine.com/frand"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"github.com/go-chi/render"
)
type Engine struct {
dealer *card.Dealer
games map[string]*game.Game
}
func main() {
dealer, err := card.NewDealer().ReadFromRoot(os.Getenv("CARD_DATA_DIR"))
if err != nil {
log.Panicln(err)
}
e := &Engine{
dealer: dealer,
games: map[string]*game.Game{},
}
r := chi.NewRouter()
r.Use(middleware.RequestID)
r.Use(middleware.Logger)
r.Use(middleware.Recoverer)
r.Use(middleware.URLFormat)
r.Use(render.SetContentType(render.ContentTypeJSON))
r.Get("/healthz", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("OK"))
})
r.Get("/ping", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("pong"))
})
// RESTy routes for "instances" resource
r.Route("/game", func(r chi.Router) {
// r.Use(oauth.Authorize(os.Getenv("OAUTH_TOKEN"), nil))
r.Post("/new", e.CreateGame)
r.Route("/{instanceId}", func(r chi.Router) {
r.Use(e.GameCtx)
r.Route("/play", func(r chi.Router) {
r.Get("/click/{cardId}", nil)
})
r.Route("/debug", func(r chi.Router) {
r.Get("/info", e.GetGame)
})
})
})
port := ":3333"
log.Println(port)
http.ListenAndServe(port, r)
}
// GameCtx middleware is used to load an Game object from
// the URL parameters passed through as the request. In case
// the Game could not be found, we stop here and return a 404.
func (e *Engine) GameCtx(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var instance *game.Game
if instanceID := chi.URLParam(r, "instanceId"); instanceID != "" {
instance = e.games[instanceID]
}
if instance == nil {
render.Render(w, r, ErrNotFound)
return
}
ctx := context.WithValue(r.Context(), "instance", instance)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
func (e *Engine) CreateGame(w http.ResponseWriter, r *http.Request) {
id := strconv.Itoa(frand.Intn(0xfff))
e.games[id] = game.New("", e.dealer)
w.Write([]byte(id))
w.WriteHeader(200)
}
func (e *Engine) GetGame(w http.ResponseWriter, r *http.Request) {
instance, ok := r.Context().Value("instance").(*game.Game)
if !ok {
render.Render(w, r, ErrNotFound)
return
}
err := json.NewEncoder(w).Encode(instance)
if err != nil {
render.Render(w, r, ErrInvalidRequest(err))
return
}
w.WriteHeader(200)
}
var ErrNotFound = &ErrResponse{HTTPStatusCode: 404, StatusText: "game not found."}
type ErrResponse struct {
Err error `json:"-"` // low-level runtime error
HTTPStatusCode int `json:"-"` // http response status code
StatusText string `json:"status"` // user-level status message
AppCode int64 `json:"code,omitempty"` // application-specific error code
ErrorText string `json:"error,omitempty"` // application-level error message, for debugging
}
func (e *ErrResponse) Render(w http.ResponseWriter, r *http.Request) error {
render.Status(r, e.HTTPStatusCode)
return nil
}
func ErrInvalidRequest(err error) render.Renderer {
return &ErrResponse{
Err: err,
HTTPStatusCode: 400,
StatusText: "Invalid request.",
ErrorText: err.Error(),
}
}
func ErrRender(err error) render.Renderer {
return &ErrResponse{
Err: err,
HTTPStatusCode: 422,
StatusText: "Error rendering response.",
ErrorText: err.Error(),
}
}

View File

@ -1,7 +1,11 @@
package game package game
import ( import (
"strconv"
"git.tuxpa.in/a/card_id/common/errs"
"git.tuxpa.in/a/card_id/common/game/card" "git.tuxpa.in/a/card_id/common/game/card"
"git.tuxpa.in/a/card_id/common/game/score"
"lukechampine.com/frand" "lukechampine.com/frand"
) )
@ -22,15 +26,44 @@ type Game struct {
Dealer *card.Dealer `json:"-"` Dealer *card.Dealer `json:"-"`
} }
func (g *Game) New(user string, dealer *card.Dealer) *Game { func New(user string, dealer *card.Dealer) *Game {
return &Game{ return &Game{
Dealer: dealer, Dealer: dealer,
Player: user, Player: user,
} }
} }
func (g *Game) Resolve(action string, args []string) { func (g *Game) Resolve(action string, args []string) (resp any, err error) {
switch action {
case "click":
resp, err = g.ActionClick(args)
default:
err = errs.Invalid("cmd not found")
}
return
}
func (g *Game) ActionClick(args []string) (resp any, err error) {
if len(args) > 1 {
num, _ := strconv.Atoi(args[1])
_, err := g.Trial.SelectCard(num)
if err != nil {
return nil, err
}
}
return nil, errs.Invalid("missing arg")
}
func (g *Game) Tick() (err error) {
sc, lives := g.Trial.CheckSelection()
if sc != 0 {
g.Combo = g.Combo + 1
g.Score = g.Score + sc*score.DefaultTable.Ratio(g.Combo)*g.CurrentTrial
} else {
g.Combo = 0
}
g.Lives = g.Lives + lives
return nil
} }
func (g *Game) CreateTrial(pairs int, extra int) *Trial { func (g *Game) CreateTrial(pairs int, extra int) *Trial {

4
go.mod
View File

@ -3,11 +3,15 @@ module git.tuxpa.in/a/card_id
go 1.18 go 1.18
require ( require (
github.com/go-chi/chi/v5 v5.0.7
github.com/go-chi/oauth v0.0.0-20210913085627-d937e221b3ef
github.com/go-chi/render v1.0.1
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
lukechampine.com/frand v1.4.2 lukechampine.com/frand v1.4.2
) )
require ( require (
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect
github.com/gofrs/uuid v4.0.0+incompatible // indirect
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb // indirect golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb // indirect
) )

8
go.sum
View File

@ -1,5 +1,13 @@
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY= github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA= github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
github.com/go-chi/chi/v5 v5.0.7 h1:rDTPXLDHGATaeHvVlLcR4Qe0zftYethFucbjVQ1PxU8=
github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-chi/oauth v0.0.0-20210913085627-d937e221b3ef h1:lqU8HyH6bzhV+HHvgFaT2xBl19tcjs9F4UULmw3hTxc=
github.com/go-chi/oauth v0.0.0-20210913085627-d937e221b3ef/go.mod h1:eFAdB6Jo7GOKhl1PWiN2lKPxgFr7dBFkRrsz6S5IwOs=
github.com/go-chi/render v1.0.1 h1:4/5tis2cKaNdnv9zFLfXzcquC9HbeZgCnxGnKrltBS8=
github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns=
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb h1:fgwFCsaw9buMuxNd6+DQfAuSFqbNiQZpcgJQAgJsK6k= golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb h1:fgwFCsaw9buMuxNd6+DQfAuSFqbNiQZpcgJQAgJsK6k=
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=