leadercraft-c/main.go

217 lines
6.1 KiB
Go
Raw Normal View History

2020-03-02 20:28:30 -05:00
// NGnius 2020-02-27
package main
import (
"bytes"
"encoding/json"
"flag"
"fmt"
"io/ioutil"
"net/http"
"os"
"os/signal"
"path/filepath"
)
const (
2020-03-06 14:32:33 -05:00
defaultPassword = ""
defaultEntryURL = "http://localhost:1337/record"
defaultPort = "9000"
defaultDir = "criterias"
2020-03-02 20:28:30 -05:00
)
var (
// cli params
password string
entryURL string
port string
dir string
// internal
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" {
2020-08-10 20:48:41 -04:00
time := float64(c.Time)
if time < 1 {
time = 1
}
2020-03-02 20:28:30 -05:00
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,
2020-03-06 14:32:33 -05:00
Handler: serverMux,
2020-03-02 20:28:30 -05:00
}
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) {
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
}
2020-03-06 14:32:33 -05:00
if reqCriteria.GameID < 2 {
//fmt.Println("404 -- GameID too low")
w.WriteHeader(404)
return
}
criteriaFilename := filepath.Join(dir, fmt.Sprintf("criteria-%d.json", reqCriteria.GameID))
//fmt.Println(criteriaFilename)
2020-03-02 20:28:30 -05:00
realCriteria := &Criteria{}
realCriteriaF, openErr := os.Open(criteriaFilename)
if openErr != nil {
2020-03-06 14:32:33 -05:00
// file not found (or not accessible)
//fmt.Println("404 -- criteria file not accessible: %s", openErr.Error())
2020-03-02 20:28:30 -05:00
w.WriteHeader(404)
return
}
realCritData, realCritReadErr := ioutil.ReadAll(realCriteriaF)
if realCritReadErr != nil {
2020-03-06 14:32:33 -05:00
// internal read error
w.WriteHeader(500)
2020-03-02 20:28:30 -05:00
return
}
unmarshCritErr := json.Unmarshal(realCritData, realCriteria)
if unmarshCritErr != nil {
// criteria file is invalid json
2020-03-06 14:32:33 -05:00
//fmt.Printf("404 -- Invalid criteria file json: %s", unmarshCritErr.Error())
2020-03-02 20:28:30 -05:00
w.WriteHeader(404)
return
}
// TODO check if criteria matches
2020-03-06 14:32:33 -05:00
if !reqCriteria.Meets(realCriteria) {
2020-03-02 20:28:30 -05:00
// if criteria does not match, stop
2020-03-06 14:32:33 -05:00
//fmt.Println("400 -- Criteria does not meet required criteria")
2020-03-02 20:28:30 -05:00
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 {
2020-03-06 14:32:33 -05:00
//fmt.Println("500 -- Unable to marshal entry into JSON for leaderboard-s endpoint")
2020-03-02 20:28:30 -05:00
w.WriteHeader(500)
return
}
echoBody := bytes.NewReader(echoData)
entryReq, reqErr := http.NewRequest("POST", entryURL, echoBody)
if reqErr != nil {
// malformed request parameters
2020-03-06 14:32:33 -05:00
//fmt.Println("500 -- Malformed request detected during initialization")
2020-03-02 20:28:30 -05:00
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
2020-03-06 14:32:33 -05:00
//fmt.Println("500 -- Bad communication for leadercraft-s")
2020-03-02 20:28:30 -05:00
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)
2020-03-06 14:32:33 -05:00
//fmt.Println("!!! Error reading response body from leadercraft-s")
2020-03-02 20:28:30 -05:00
return
}
w.Write(echoRespData)
}