Become the leader if you meet the criteria
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.
Ce dépôt est archivé. Vous pouvez voir les fichiers et le cloner, mais vous ne pouvez pas pousser ni ouvrir de ticket/demande d'ajout.

217 lignes
6.1KB

  1. // NGnius 2020-02-27
  2. package main
  3. import (
  4. "bytes"
  5. "encoding/json"
  6. "flag"
  7. "fmt"
  8. "io/ioutil"
  9. "net/http"
  10. "os"
  11. "os/signal"
  12. "path/filepath"
  13. )
  14. const (
  15. defaultPassword = ""
  16. defaultEntryURL = "http://localhost:1337/record"
  17. defaultPort = "9000"
  18. defaultDir = "criterias"
  19. )
  20. var (
  21. // cli params
  22. password string
  23. entryURL string
  24. port string
  25. dir string
  26. // internal
  27. server *http.Server
  28. client *http.Client
  29. isClosing bool
  30. referenceCriteria map[string]Criteria
  31. )
  32. // json structs
  33. // NewEntryJSON a new entry to be saved to a leaderboard
  34. type NewEntryJSON struct { // from leadercraft-s
  35. Score int64
  36. PlayerID int64
  37. BoardID int64
  38. Password string
  39. }
  40. // Criteria a set of values that must be met
  41. type Criteria struct {
  42. Location [2][3]float64 // (min coords (x,y,z), max coords (x,y,z))
  43. Time int64 // time since start of game (seconds)
  44. GameID int64 // game/board id in leadercraft-s and in Gamecraft (workshop ID?)
  45. PlayerID int64 // player id in leadercraft-s and in Gamecraft (steam ID)
  46. Coefficient float64 // coefficient for calculating the score
  47. Complete bool // game has been completed
  48. Points int64 // points scored in game
  49. ScoreMode string // score calculation mode
  50. }
  51. func (c *Criteria) Meets(c2 *Criteria) bool {
  52. meets := false
  53. if c2.Location[0][0] != 0.0 && c2.Location[0][1] != 0.0 && c2.Location[0][2] != 0.0 &&
  54. c2.Location[1][0] != 0.0 && c2.Location[1][1] != 0.0 && c2.Location[1][2] != 0.0 {
  55. // criteria is location-based
  56. 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]
  57. 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]
  58. return meets
  59. }
  60. if c2.Complete {
  61. return c.Complete && (c.Points >= c2.Points)
  62. }
  63. return meets
  64. }
  65. func (c *Criteria) Score(c2 *Criteria) int64 {
  66. if c2.ScoreMode == "time" {
  67. time := float64(c.Time)
  68. if time < 1 {
  69. time = 1
  70. }
  71. return int64(c2.Coefficient / float64(c.Time))
  72. }
  73. if c2.ScoreMode == "points" {
  74. return int64(c2.Coefficient * float64(c.Points))
  75. }
  76. return 0
  77. }
  78. func init() {
  79. flag.StringVar(&password, "entry-pwd", defaultPassword, "Password for new entry POST requests")
  80. flag.StringVar(&entryURL, "url", defaultEntryURL, "URL for new entry POST requests")
  81. flag.StringVar(&port, "port", defaultPort, "Port to listen on")
  82. flag.StringVar(&dir, "dir", defaultDir, "Working directory")
  83. }
  84. func main() {
  85. flag.Parse()
  86. serverMux := http.NewServeMux()
  87. serverMux.HandleFunc("/criteria", criteriaHandler)
  88. signalChan := make(chan os.Signal)
  89. signal.Notify(signalChan, os.Interrupt)
  90. go func() {
  91. s := <-signalChan
  92. fmt.Println("Received terminate signal " + s.String())
  93. isClosing = true
  94. server.Close()
  95. }()
  96. server = &http.Server{
  97. Addr: ":" + port,
  98. Handler: serverMux,
  99. }
  100. client = &http.Client{}
  101. fmt.Println("Starting on " + server.Addr)
  102. err := server.ListenAndServe()
  103. if err != nil && !isClosing {
  104. fmt.Println(err)
  105. }
  106. }
  107. // criteria POST request handler
  108. // this also sends a new entry to leadercraft-s when the criteria is met
  109. func criteriaHandler(w http.ResponseWriter, r *http.Request) {
  110. if r.Method != "POST" {
  111. w.WriteHeader(405)
  112. return
  113. }
  114. authHeader := r.Header.Get("Authorization")
  115. w.Header().Add("Content-Type", "application/json")
  116. //w.Header().Add("Access-Control-Allow-Origin", "*")
  117. data, readErr := ioutil.ReadAll(r.Body)
  118. if readErr != nil {
  119. // body could not be read properly
  120. w.WriteHeader(500)
  121. return
  122. }
  123. reqCriteria := &Criteria{}
  124. unmarshErr := json.Unmarshal(data, reqCriteria)
  125. if unmarshErr != nil {
  126. // body could not be interpreted as json
  127. w.WriteHeader(400)
  128. return
  129. }
  130. if reqCriteria.GameID < 2 {
  131. //fmt.Println("404 -- GameID too low")
  132. w.WriteHeader(404)
  133. return
  134. }
  135. criteriaFilename := filepath.Join(dir, fmt.Sprintf("criteria-%d.json", reqCriteria.GameID))
  136. //fmt.Println(criteriaFilename)
  137. realCriteria := &Criteria{}
  138. realCriteriaF, openErr := os.Open(criteriaFilename)
  139. if openErr != nil {
  140. // file not found (or not accessible)
  141. //fmt.Println("404 -- criteria file not accessible: %s", openErr.Error())
  142. w.WriteHeader(404)
  143. return
  144. }
  145. realCritData, realCritReadErr := ioutil.ReadAll(realCriteriaF)
  146. if realCritReadErr != nil {
  147. // internal read error
  148. w.WriteHeader(500)
  149. return
  150. }
  151. unmarshCritErr := json.Unmarshal(realCritData, realCriteria)
  152. if unmarshCritErr != nil {
  153. // criteria file is invalid json
  154. //fmt.Printf("404 -- Invalid criteria file json: %s", unmarshCritErr.Error())
  155. w.WriteHeader(404)
  156. return
  157. }
  158. // TODO check if criteria matches
  159. if !reqCriteria.Meets(realCriteria) {
  160. // if criteria does not match, stop
  161. //fmt.Println("400 -- Criteria does not meet required criteria")
  162. w.WriteHeader(400)
  163. return
  164. }
  165. // if criteria matches, send new entry to leadercraft-s
  166. entry := NewEntryJSON{
  167. Score: reqCriteria.Score(realCriteria),
  168. PlayerID: reqCriteria.PlayerID,
  169. BoardID: realCriteria.GameID,
  170. Password: password,
  171. }
  172. echoData, marshErr := json.Marshal(entry)
  173. if marshErr != nil {
  174. //fmt.Println("500 -- Unable to marshal entry into JSON for leaderboard-s endpoint")
  175. w.WriteHeader(500)
  176. return
  177. }
  178. echoBody := bytes.NewReader(echoData)
  179. entryReq, reqErr := http.NewRequest("POST", entryURL, echoBody)
  180. if reqErr != nil {
  181. // malformed request parameters
  182. //fmt.Println("500 -- Malformed request detected during initialization")
  183. w.WriteHeader(500)
  184. return
  185. }
  186. entryReq.Header.Add("Authorization", authHeader)
  187. entryReq.Header.Add("Content-Type", "application/json")
  188. echoResp, postErr := client.Do(entryReq)
  189. if postErr != nil {
  190. // bad communication or malformed request
  191. //fmt.Println("500 -- Bad communication for leadercraft-s")
  192. w.WriteHeader(500)
  193. return
  194. }
  195. // echo new entry request response to original sender
  196. w.WriteHeader(echoResp.StatusCode)
  197. echoRespData, echoReadErr := ioutil.ReadAll(echoResp.Body)
  198. if echoReadErr != nil {
  199. // body read error (should never occur)
  200. //fmt.Println("!!! Error reading response body from leadercraft-s")
  201. return
  202. }
  203. w.Write(echoRespData)
  204. }