Put the api in docker #2
@ -11,4 +11,6 @@ Currently stores no state and has no user authentication, so using it should jus
|
||||
POST /game
|
||||
GET /game/{id}
|
||||
GET /game/{id}/status
|
||||
POST /game/{id}/move
|
||||
GET /game/{id}/move/{movenum}
|
||||
```
|
||||
|
133
api.go
133
api.go
@ -2,6 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
@ -22,9 +23,34 @@ func NewAPI() *API {
|
||||
}
|
||||
}
|
||||
|
||||
//NewGame takes a POST and creates a new game
|
||||
//NewGame takes a POST and creates a new game or returns an open one
|
||||
func (a *API) NewGame(res http.ResponseWriter, req *http.Request) {
|
||||
for i, g := range a.games {
|
||||
if !g.redPlayer.Ready {
|
||||
log.Println("red player somehow not ready")
|
||||
g.redPlayer.Ready = true
|
||||
if g.bluePlayer.Ready {
|
||||
g.simulator.Setup()
|
||||
initDummy(g.simulator)
|
||||
log.Println("dummy game started")
|
||||
}
|
||||
respondWithJSON(res, http.StatusOK, newGameResp{i, "red"})
|
||||
return
|
||||
}
|
||||
if !g.bluePlayer.Ready {
|
||||
g.bluePlayer.Ready = true
|
||||
if g.redPlayer.Ready {
|
||||
g.simulator.Setup()
|
||||
initDummy(g.simulator)
|
||||
log.Println("dummy game started")
|
||||
}
|
||||
respondWithJSON(res, http.StatusOK, newGameResp{i, "blue"})
|
||||
return
|
||||
}
|
||||
}
|
||||
log.Printf("creating new game %v", a.nextInt)
|
||||
a.games[a.nextInt] = NewSession()
|
||||
a.games[a.nextInt].redPlayer.Ready = true
|
||||
respondWithJSON(res, http.StatusOK, newGameResp{a.nextInt, "red"})
|
||||
a.nextInt = a.nextInt + 1
|
||||
}
|
||||
@ -40,7 +66,8 @@ func (a *API) GetGame(res http.ResponseWriter, req *http.Request) {
|
||||
var gr gameReq
|
||||
decoder := json.NewDecoder(req.Body)
|
||||
if err := decoder.Decode(&gr); err != nil {
|
||||
respondWithError(res, http.StatusBadRequest, "Invalid resquest payload")
|
||||
log.Println(err)
|
||||
respondWithError(res, http.StatusBadRequest, "Invalid request payload")
|
||||
return
|
||||
}
|
||||
defer req.Body.Close()
|
||||
@ -48,12 +75,21 @@ func (a *API) GetGame(res http.ResponseWriter, req *http.Request) {
|
||||
respondWithError(res, http.StatusBadRequest, "Bad player ID")
|
||||
return
|
||||
}
|
||||
var p *Player
|
||||
|
||||
s, isset := a.games[id]
|
||||
if !isset {
|
||||
respondWithError(res, http.StatusBadRequest, "No such game")
|
||||
return
|
||||
}
|
||||
respondWithJSON(res, http.StatusOK, gameResp{s.simulator})
|
||||
if gr.PlayerID == "red" {
|
||||
p = s.redPlayer
|
||||
} else {
|
||||
p = s.bluePlayer
|
||||
}
|
||||
|
||||
log.Println("sending game state")
|
||||
respondWithJSON(res, http.StatusOK, gameResp{s.getBoard(p)})
|
||||
return
|
||||
}
|
||||
|
||||
@ -81,5 +117,96 @@ func (a *API) GetGameStatus(res http.ResponseWriter, req *http.Request) {
|
||||
respondWithError(res, http.StatusBadRequest, "No such game")
|
||||
return
|
||||
}
|
||||
log.Println("sending game status")
|
||||
respondWithJSON(res, http.StatusOK, gameStatusResp{s.simulator.State, s.moveNum})
|
||||
}
|
||||
|
||||
//PostMove attempts to make a game move
|
||||
func (a *API) PostMove(res http.ResponseWriter, req *http.Request) {
|
||||
vars := mux.Vars(req)
|
||||
id, err := strconv.Atoi(vars["id"])
|
||||
if err != nil {
|
||||
respondWithError(res, http.StatusBadRequest, "Invalid game ID")
|
||||
return
|
||||
}
|
||||
var gr gameMovePostReq
|
||||
decoder := json.NewDecoder(req.Body)
|
||||
if err := decoder.Decode(&gr); err != nil {
|
||||
respondWithError(res, http.StatusBadRequest, "Invalid resquest payload")
|
||||
return
|
||||
}
|
||||
defer req.Body.Close()
|
||||
if gr.PlayerID != "red" && gr.PlayerID != "blue" {
|
||||
respondWithError(res, http.StatusBadRequest, "Bad player ID")
|
||||
return
|
||||
}
|
||||
var p *Player
|
||||
|
||||
s, isset := a.games[id]
|
||||
if !isset {
|
||||
respondWithError(res, http.StatusBadRequest, "No such game")
|
||||
return
|
||||
}
|
||||
if gr.PlayerID == "red" {
|
||||
p = s.redPlayer
|
||||
} else {
|
||||
p = s.bluePlayer
|
||||
}
|
||||
parsed, err := s.tryMove(p, gr.Move)
|
||||
if err != nil {
|
||||
respondWithError(res, http.StatusBadRequest, err.Error())
|
||||
|
||||
}
|
||||
result, err := s.mutate(parsed)
|
||||
if err != nil {
|
||||
respondWithError(res, http.StatusBadRequest, err.Error())
|
||||
}
|
||||
if result == "" {
|
||||
respondWithJSON(res, http.StatusOK, gameMovePostRes{true, false, "", err})
|
||||
}
|
||||
respondWithJSON(res, http.StatusOK, gameMovePostRes{true, true, result, err})
|
||||
}
|
||||
|
||||
//GetMove returns the move made at turn X
|
||||
func (a *API) GetMove(res http.ResponseWriter, req *http.Request) {
|
||||
vars := mux.Vars(req)
|
||||
id, err := strconv.Atoi(vars["id"])
|
||||
if err != nil {
|
||||
respondWithError(res, http.StatusBadRequest, "Invalid game ID")
|
||||
return
|
||||
}
|
||||
move, err := strconv.Atoi(vars["movenum"])
|
||||
if err != nil {
|
||||
respondWithError(res, http.StatusBadRequest, "Invalid move number")
|
||||
return
|
||||
}
|
||||
var gr gameMoveReq
|
||||
decoder := json.NewDecoder(req.Body)
|
||||
if err := decoder.Decode(&gr); err != nil {
|
||||
respondWithError(res, http.StatusBadRequest, "Invalid resquest payload")
|
||||
return
|
||||
}
|
||||
defer req.Body.Close()
|
||||
if gr.PlayerID != "red" && gr.PlayerID != "blue" {
|
||||
respondWithError(res, http.StatusBadRequest, "Bad player ID")
|
||||
return
|
||||
}
|
||||
var p *Player
|
||||
|
||||
s, isset := a.games[id]
|
||||
if !isset {
|
||||
respondWithError(res, http.StatusBadRequest, "No such game")
|
||||
return
|
||||
}
|
||||
if gr.PlayerID == "red" {
|
||||
p = s.redPlayer
|
||||
} else {
|
||||
p = s.bluePlayer
|
||||
}
|
||||
moveRes, err := s.getMove(p, move)
|
||||
if err != nil {
|
||||
respondWithError(res, http.StatusBadRequest, "No such move")
|
||||
return
|
||||
}
|
||||
respondWithJSON(res, http.StatusOK, gameMoveRes{moveRes})
|
||||
}
|
||||
|
24
api_types.go
24
api_types.go
@ -1,6 +1,6 @@
|
||||
package main
|
||||
|
||||
import "git.saintnet.tech/freego"
|
||||
import "git.saintnet.tech/stryan/freego"
|
||||
|
||||
//type newGameReq struct{}
|
||||
|
||||
@ -14,7 +14,7 @@ type gameReq struct {
|
||||
}
|
||||
|
||||
type gameResp struct {
|
||||
GameBoard *freego.Game `json:"board"`
|
||||
GameBoard [8][8]*ViewTile `json:"board"`
|
||||
}
|
||||
|
||||
type gameStatusReq struct {
|
||||
@ -25,3 +25,23 @@ type gameStatusResp struct {
|
||||
GameStatus freego.GameState `json:"game_status"`
|
||||
Move int `json:"move"`
|
||||
}
|
||||
|
||||
type gameMovePostReq struct {
|
||||
PlayerID string `json:"player_id"`
|
||||
Move string `json:"move"`
|
||||
}
|
||||
|
||||
type gameMovePostRes struct {
|
||||
Valid bool `json:"valid"`
|
||||
Result bool `json:"result"`
|
||||
Parsed string `json:"parsed"`
|
||||
Error error `json:"error"`
|
||||
}
|
||||
|
||||
type gameMoveReq struct {
|
||||
PlayerID string `json:"player_id"`
|
||||
}
|
||||
|
||||
type gameMoveRes struct {
|
||||
Move string `json:"move"`
|
||||
}
|
||||
|
4
go.mod
4
go.mod
@ -1,10 +1,8 @@
|
||||
module git.saintnet.tech/freego_api
|
||||
|
||||
replace git.saintnet.tech/freego => ../freego
|
||||
|
||||
go 1.17
|
||||
|
||||
require (
|
||||
git.saintnet.tech/freego v0.0.0-00010101000000-000000000000 // indirect
|
||||
git.saintnet.tech/stryan/freego v0.0.0-20220307210942-38a41634101a // indirect
|
||||
github.com/gorilla/mux v1.8.0 // indirect
|
||||
)
|
||||
|
12
go.sum
12
go.sum
@ -1,2 +1,14 @@
|
||||
git.saintnet.tech/stryan/freego v0.0.0-20220307180035-64bab97c38d6 h1:0GzkfU8R4Rj7SVxTpkOf9oZN4YpW2xe/IjYbNP5Zxjs=
|
||||
git.saintnet.tech/stryan/freego v0.0.0-20220307180035-64bab97c38d6/go.mod h1:NXXisQVSPklkvs2Qg6Iv3LqXNaJwwEtho/2WmzhvAZc=
|
||||
git.saintnet.tech/stryan/freego v0.0.0-20220307194514-b5fc90339454 h1:KK8YuhJYMjbmyREbCDX+DshFaarrzxhjRTyPDIDhzDs=
|
||||
git.saintnet.tech/stryan/freego v0.0.0-20220307194514-b5fc90339454/go.mod h1:NXXisQVSPklkvs2Qg6Iv3LqXNaJwwEtho/2WmzhvAZc=
|
||||
git.saintnet.tech/stryan/freego v0.0.0-20220307202314-dd8dce886942 h1:fgVoPchS1ETLuPKMtu4pO6zU5JRqXp+O4iy49uIaxhE=
|
||||
git.saintnet.tech/stryan/freego v0.0.0-20220307202314-dd8dce886942/go.mod h1:NXXisQVSPklkvs2Qg6Iv3LqXNaJwwEtho/2WmzhvAZc=
|
||||
git.saintnet.tech/stryan/freego v0.0.0-20220307205125-093ef4caf221 h1:OfBW5x9SAjpk+dGRbdmw0qmh8Ru3eJl6wjDTD4QwxAU=
|
||||
git.saintnet.tech/stryan/freego v0.0.0-20220307205125-093ef4caf221/go.mod h1:NXXisQVSPklkvs2Qg6Iv3LqXNaJwwEtho/2WmzhvAZc=
|
||||
git.saintnet.tech/stryan/freego v0.0.0-20220307205900-a33244481774 h1:V5V4/SEX3HjeilyO7gi9fp7DpevkDIrKPukdgLOMEXw=
|
||||
git.saintnet.tech/stryan/freego v0.0.0-20220307205900-a33244481774/go.mod h1:NXXisQVSPklkvs2Qg6Iv3LqXNaJwwEtho/2WmzhvAZc=
|
||||
git.saintnet.tech/stryan/freego v0.0.0-20220307210942-38a41634101a h1:y/UwFZN0yZS0OYcdXnjvbdiwY4ZKIi8+K8x2xRwJ1L8=
|
||||
git.saintnet.tech/stryan/freego v0.0.0-20220307210942-38a41634101a/go.mod h1:NXXisQVSPklkvs2Qg6Iv3LqXNaJwwEtho/2WmzhvAZc=
|
||||
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||
|
4
main.go
4
main.go
@ -14,8 +14,8 @@ func main() {
|
||||
router.HandleFunc("/game", api.NewGame).Methods("POST")
|
||||
router.HandleFunc("/game/{id}", api.GetGame).Methods("GET")
|
||||
router.HandleFunc("/game/{id}/status", api.GetGameStatus).Methods("GET")
|
||||
router.HandleFunc("/game/{id}/move", nil).Methods("POST")
|
||||
router.HandleFunc("/game/{id}/move/{movenum}", nil).Methods("GET")
|
||||
router.HandleFunc("/game/{id}/move", api.PostMove).Methods("POST")
|
||||
router.HandleFunc("/game/{id}/move/{movenum}", api.GetMove).Methods("GET")
|
||||
log.Fatal(http.ListenAndServe(":1379", router))
|
||||
|
||||
}
|
||||
|
109
session.go
109
session.go
@ -1,21 +1,116 @@
|
||||
package main
|
||||
|
||||
import "git.saintnet.tech/freego"
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"git.saintnet.tech/stryan/freego"
|
||||
)
|
||||
|
||||
//Session represents an active game
|
||||
type Session struct {
|
||||
simulator *freego.Game
|
||||
redReady bool
|
||||
blueReady bool
|
||||
redPlayer *Player
|
||||
bluePlayer *Player
|
||||
moveNum int
|
||||
moveList []freego.ParsedCommand
|
||||
}
|
||||
|
||||
//Player is a player in a match
|
||||
type Player struct {
|
||||
Ready bool
|
||||
Team freego.Colour
|
||||
}
|
||||
|
||||
//ID returns player ID
|
||||
func (p *Player) ID() int {
|
||||
panic("not implemented") // TODO: Implement
|
||||
}
|
||||
|
||||
//Colour returns player team
|
||||
func (p *Player) Colour() freego.Colour {
|
||||
return p.Team
|
||||
}
|
||||
|
||||
//NewSession creates a new game session
|
||||
func NewSession() *Session {
|
||||
sim := freego.NewGame()
|
||||
return &Session{
|
||||
simulator: freego.NewGame(),
|
||||
redReady: false,
|
||||
blueReady: false,
|
||||
moveNum: 0,
|
||||
simulator: sim,
|
||||
redPlayer: &Player{false, freego.Red},
|
||||
bluePlayer: &Player{false, freego.Blue},
|
||||
moveNum: 1,
|
||||
moveList: make([]freego.ParsedCommand, 20),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Session) tryMove(player *Player, move string) (*freego.ParsedCommand, error) {
|
||||
raw, err := freego.NewRawCommand(move)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p, err := s.simulator.Parse(player, raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func (s *Session) mutate(p *freego.ParsedCommand) (string, error) {
|
||||
success, err := s.simulator.Mutate(p)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if success {
|
||||
s.moveList[s.moveNum] = *p
|
||||
s.moveNum++
|
||||
return fmt.Sprintf("%v %v", s.moveNum-1, p.String()), nil
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (s *Session) getMove(p *Player, num int) (string, error) {
|
||||
if num <= 0 || num >= s.moveNum {
|
||||
log.Printf("tried to get move number %v when move is %v", num, s.moveNum)
|
||||
return "", errors.New("invalid move number")
|
||||
}
|
||||
return fmt.Sprintf("%v %v", num, s.moveList[num].String()), nil
|
||||
}
|
||||
|
||||
func (s *Session) getBoard(p *Player) [8][8]*ViewTile {
|
||||
var res [8][8]*ViewTile
|
||||
for i := 0; i < 8; i++ {
|
||||
for j := 0; j < 8; j++ {
|
||||
cur := NewViewTile()
|
||||
terrain, err := s.simulator.Board.IsTerrain(i, j)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if terrain {
|
||||
cur.Terrain = true
|
||||
} else {
|
||||
piece, err := s.simulator.Board.GetPiece(i, j)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if piece != nil {
|
||||
if piece.Hidden {
|
||||
cur.Hidden = true
|
||||
if piece.Owner == p.Colour() {
|
||||
cur.Piece = piece.Rank.String()
|
||||
} else {
|
||||
cur.Piece = "Unknown"
|
||||
}
|
||||
} else {
|
||||
cur.Piece = piece.Rank.String()
|
||||
}
|
||||
} else {
|
||||
cur.Empty = true
|
||||
}
|
||||
}
|
||||
res[i][j] = cur
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
50
util.go
50
util.go
@ -2,7 +2,11 @@ package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"git.saintnet.tech/stryan/freego"
|
||||
)
|
||||
|
||||
func respondWithError(res http.ResponseWriter, code int, message string) {
|
||||
@ -16,3 +20,49 @@ func respondWithJSON(res http.ResponseWriter, code int, payload interface{}) {
|
||||
res.WriteHeader(code)
|
||||
res.Write(response)
|
||||
}
|
||||
|
||||
//TODO remove this when you can actually setup a game
|
||||
func initDummy(g *freego.Game) {
|
||||
//Setup terrain
|
||||
terrain := []struct {
|
||||
x, y, t int
|
||||
}{
|
||||
{1, 1, 1},
|
||||
{2, 2, 1},
|
||||
}
|
||||
for _, tt := range terrain {
|
||||
res, err := g.Board.AddTerrain(tt.x, tt.y, tt.t)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if !res {
|
||||
panic(errors.New("Error creating terrain"))
|
||||
}
|
||||
}
|
||||
pieces := []struct {
|
||||
x, y int
|
||||
p *freego.Piece
|
||||
}{
|
||||
{0, 0, freego.NewPiece(freego.Flag, freego.Blue)},
|
||||
{3, 0, freego.NewPiece(freego.Spy, freego.Blue)},
|
||||
{2, 0, freego.NewPiece(freego.Captain, freego.Blue)},
|
||||
{3, 1, freego.NewPiece(freego.Marshal, freego.Blue)},
|
||||
{0, 1, freego.NewPiece(freego.Bomb, freego.Blue)},
|
||||
|
||||
{1, 6, freego.NewPiece(freego.Flag, freego.Red)},
|
||||
{3, 6, freego.NewPiece(freego.Spy, freego.Red)},
|
||||
{2, 7, freego.NewPiece(freego.Captain, freego.Red)},
|
||||
{0, 6, freego.NewPiece(freego.Marshal, freego.Red)},
|
||||
{0, 7, freego.NewPiece(freego.Bomb, freego.Red)},
|
||||
}
|
||||
for _, tt := range pieces {
|
||||
res, err := g.SetupPiece(tt.x, tt.y, tt.p)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("Piece %v,%v:%v", tt.x, tt.y, err))
|
||||
}
|
||||
if !res {
|
||||
panic(errors.New("error placing dummy piece"))
|
||||
}
|
||||
}
|
||||
g.Start()
|
||||
}
|
||||
|
14
view_tile.go
Normal file
14
view_tile.go
Normal file
@ -0,0 +1,14 @@
|
||||
package main
|
||||
|
||||
//ViewTile is a json friendly version of a tile
|
||||
type ViewTile struct {
|
||||
Piece string `json:"piece"`
|
||||
Terrain bool `json:"terrain"`
|
||||
Hidden bool `json:"hidden"`
|
||||
Empty bool `json:"empty"`
|
||||
}
|
||||
|
||||
//NewViewTile creates a new ViewTile
|
||||
func NewViewTile() *ViewTile {
|
||||
return &ViewTile{}
|
||||
}
|
Loading…
Reference in New Issue
Block a user