227 lines
6 KiB
Go
227 lines
6 KiB
Go
|
// NGnius 2020-02-27
|
||
|
|
||
|
package main
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"encoding/json"
|
||
|
"flag"
|
||
|
"fmt"
|
||
|
"io/ioutil"
|
||
|
"net/http"
|
||
|
"os"
|
||
|
"os/signal"
|
||
|
"path/filepath"
|
||
|
"strconv"
|
||
|
)
|
||
|
|
||
|
const (
|
||
|
defaultPassword = ""
|
||
|
defaultEntryURL = "http://localhost:1337/record"
|
||
|
defaultPort = "9000"
|
||
|
defaultDir = "criterias"
|
||
|
defaultCriteriaJson = "criteria.json"
|
||
|
)
|
||
|
|
||
|
var (
|
||
|
// cli params
|
||
|
password string
|
||
|
entryURL string
|
||
|
port string
|
||
|
dir string
|
||
|
// internal
|
||
|
handler http.Handler
|
||
|
server *http.Server
|
||
|
client *http.Client
|
||
|
isClosing bool
|
||
|
referenceCriteria map[string]Criteria
|
||
|
)
|
||
|
|
||
|
// json structs
|
||
|
|
||
|
// NewEntryJSON a new entry to be saved to a leaderboard
|
||
|
type NewEntryJSON struct { // from leadercraft-s
|
||
|
Score int64
|
||
|
PlayerID int64
|
||
|
BoardID int64
|
||
|
Password string
|
||
|
}
|
||
|
|
||
|
// Criteria a set of values that must be met
|
||
|
type Criteria struct {
|
||
|
Location [2][3]float64 // (min coords (x,y,z), max coords (x,y,z))
|
||
|
Time int64 // time since start of game (seconds)
|
||
|
GameID int64 // game/board id in leadercraft-s and in Gamecraft (workshop ID?)
|
||
|
PlayerID int64 // player id in leadercraft-s and in Gamecraft (steam ID)
|
||
|
Coefficient float64 // coefficient for calculating the score
|
||
|
Complete bool // game has been completed
|
||
|
Points int64 // points scored in game
|
||
|
ScoreMode string // score calculation mode
|
||
|
}
|
||
|
|
||
|
func (c *Criteria) Meets(c2 *Criteria) bool {
|
||
|
meets := false
|
||
|
if c2.Location[0][0] != 0.0 && c2.Location[0][1] != 0.0 && c2.Location[0][2] != 0.0 &&
|
||
|
c2.Location[1][0] != 0.0 && c2.Location[1][1] != 0.0 && c2.Location[1][2] != 0.0 {
|
||
|
// criteria is location-based
|
||
|
meets = c.Location[0][0] >= c2.Location[0][0] && c.Location[0][1] >= c2.Location[0][1] && c.Location[0][2] >= c2.Location[0][2]
|
||
|
meets = meets && c.Location[1][0] <= c2.Location[1][0] && c.Location[1][1] <= c2.Location[1][1] && c.Location[1][2] <= c2.Location[1][2]
|
||
|
return meets
|
||
|
}
|
||
|
if c2.Complete {
|
||
|
return c.Complete && (c.Points >= c2.Points)
|
||
|
}
|
||
|
return meets
|
||
|
}
|
||
|
|
||
|
func (c *Criteria) Score(c2 *Criteria) int64 {
|
||
|
if c2.ScoreMode == "time" {
|
||
|
return int64(c2.Coefficient / float64(c.Time))
|
||
|
}
|
||
|
if c2.ScoreMode == "points" {
|
||
|
return int64(c2.Coefficient * float64(c.Points))
|
||
|
}
|
||
|
return 0
|
||
|
}
|
||
|
|
||
|
func init() {
|
||
|
flag.StringVar(&password, "entry-pwd", defaultPassword, "Password for new entry POST requests")
|
||
|
flag.StringVar(&entryURL, "url", defaultEntryURL, "URL for new entry POST requests")
|
||
|
flag.StringVar(&port, "port", defaultPort, "Port to listen on")
|
||
|
flag.StringVar(&dir, "dir", defaultDir, "Working directory")
|
||
|
}
|
||
|
|
||
|
func main() {
|
||
|
flag.Parse()
|
||
|
serverMux := http.NewServeMux()
|
||
|
serverMux.HandleFunc("/criteria", criteriaHandler)
|
||
|
signalChan := make(chan os.Signal)
|
||
|
signal.Notify(signalChan, os.Interrupt)
|
||
|
go func() {
|
||
|
s := <-signalChan
|
||
|
fmt.Println("Received terminate signal " + s.String())
|
||
|
isClosing = true
|
||
|
server.Close()
|
||
|
}()
|
||
|
server = &http.Server{
|
||
|
Addr: ":" + port,
|
||
|
Handler: handler,
|
||
|
}
|
||
|
client = &http.Client{}
|
||
|
fmt.Println("Starting on " + server.Addr)
|
||
|
err := server.ListenAndServe()
|
||
|
if err != nil && !isClosing {
|
||
|
fmt.Println(err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// criteria POST request handler
|
||
|
// this also sends a new entry to leadercraft-s when the criteria is met
|
||
|
func criteriaHandler(w http.ResponseWriter, r *http.Request) {
|
||
|
// TODO
|
||
|
if r.Method != "POST" {
|
||
|
w.WriteHeader(405)
|
||
|
return
|
||
|
}
|
||
|
authHeader := r.Header.Get("Authorization")
|
||
|
w.Header().Add("Content-Type", "application/json")
|
||
|
//w.Header().Add("Access-Control-Allow-Origin", "*")
|
||
|
data, readErr := ioutil.ReadAll(r.Body)
|
||
|
if readErr != nil {
|
||
|
// body could not be read properly
|
||
|
w.WriteHeader(500)
|
||
|
return
|
||
|
}
|
||
|
reqCriteria := &Criteria{}
|
||
|
unmarshErr := json.Unmarshal(data, reqCriteria)
|
||
|
if unmarshErr != nil {
|
||
|
// body could not be interpreted as json
|
||
|
w.WriteHeader(400)
|
||
|
return
|
||
|
}
|
||
|
// TODO load criteria for game id
|
||
|
criteriaFilename := filepath.Join(dir, fmt.Sprintf("%d", reqCriteria.GameID)+".json")
|
||
|
realCriteria := &Criteria{}
|
||
|
realCriteriaF, openErr := os.Open(criteriaFilename)
|
||
|
if openErr != nil {
|
||
|
w.WriteHeader(404)
|
||
|
return
|
||
|
}
|
||
|
realCritData, realCritReadErr := ioutil.ReadAll(realCriteriaF)
|
||
|
if realCritReadErr != nil {
|
||
|
w.WriteHeader(404)
|
||
|
return
|
||
|
}
|
||
|
unmarshCritErr := json.Unmarshal(realCritData, realCriteria)
|
||
|
if unmarshCritErr != nil {
|
||
|
// criteria file is invalid json
|
||
|
w.WriteHeader(404)
|
||
|
return
|
||
|
}
|
||
|
if reqCriteria.GameID > 1 {
|
||
|
f, fileErr := os.Open(filepath.Join(dir, "criteria-"+strconv.Itoa(int(reqCriteria.GameID))+".json"))
|
||
|
if fileErr != nil {
|
||
|
// file not found
|
||
|
w.WriteHeader(404)
|
||
|
return
|
||
|
}
|
||
|
data, readErr = ioutil.ReadAll(f)
|
||
|
if readErr != nil {
|
||
|
// file could not be read properly (file doesn't exist?)
|
||
|
w.WriteHeader(404)
|
||
|
return
|
||
|
}
|
||
|
unmarshErr = json.Unmarshal(data, &realCriteria)
|
||
|
if unmarshErr != nil {
|
||
|
// data could not be interpreted as json
|
||
|
w.WriteHeader(500)
|
||
|
return
|
||
|
}
|
||
|
} else {
|
||
|
// Game ID cannot exist
|
||
|
w.WriteHeader(404)
|
||
|
return
|
||
|
}
|
||
|
// TODO check if criteria matches
|
||
|
if !realCriteria.Meets(reqCriteria) {
|
||
|
// if criteria does not match, stop
|
||
|
w.WriteHeader(400)
|
||
|
return
|
||
|
}
|
||
|
// if criteria matches, send new entry to leadercraft-s
|
||
|
entry := NewEntryJSON{
|
||
|
Score: realCriteria.Score(reqCriteria),
|
||
|
PlayerID: reqCriteria.PlayerID,
|
||
|
BoardID: realCriteria.GameID,
|
||
|
Password: password,
|
||
|
}
|
||
|
echoData, marshErr := json.Marshal(entry)
|
||
|
if marshErr != nil {
|
||
|
w.WriteHeader(500)
|
||
|
return
|
||
|
}
|
||
|
echoBody := bytes.NewReader(echoData)
|
||
|
entryReq, reqErr := http.NewRequest("POST", entryURL, echoBody)
|
||
|
if reqErr != nil {
|
||
|
// malformed request parameters
|
||
|
w.WriteHeader(500)
|
||
|
return
|
||
|
}
|
||
|
entryReq.Header.Add("Authorization", authHeader)
|
||
|
entryReq.Header.Add("Content-Type", "application/json")
|
||
|
echoResp, postErr := client.Do(entryReq)
|
||
|
if postErr != nil {
|
||
|
// bad communication or malformed request
|
||
|
w.WriteHeader(500)
|
||
|
return
|
||
|
}
|
||
|
// echo new entry request response to original sender
|
||
|
w.WriteHeader(echoResp.StatusCode)
|
||
|
echoRespData, echoReadErr := ioutil.ReadAll(echoResp.Body)
|
||
|
if echoReadErr != nil {
|
||
|
// body read error (should never occur)
|
||
|
return
|
||
|
}
|
||
|
w.Write(echoRespData)
|
||
|
}
|