Become the leader if you meet the criteria
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
This repo is archived. You can view files and clone it, but cannot push or open issues/pull-requests.

227 line
6.0KB

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