123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282 |
- import "./templates.ecal" as tmpl
- import "./const.ecal" as const
- import "./helper.ecal" as hlp
- import "./engine.ecal" as engine
- /*
- GameState holds the runtime state of all active games.
- */
- GameState := {}
- /*
- Websocket holds all active websocket connections.
- */
- Websocket := {}
- /*
- Get details of a game world.
- Endpoint: /db/ecal/game
- */
- sink GetGameWorld
- kindmatch ["db.web.ecal"]
- statematch {"path" : "game", "method" : "GET"}
- priority 10
- {
- let gameWorld
- try {
- let gameName := event.state.query.gameName[0]
- if gameName != "main" {
- raise(const.Errors.EntityNotFound, "Game world {{gameName}} not found")
- }
- gameWorld := tmpl.newGameWorld(gameName)
- mutex GameStateMutex {
- if GameState[gameName] == null {
- db.storeNode(gameName, gameWorld)
- sprites := []
- for i in range(1, 8) {
- posX := math.floor(rand() * gameWorld.screenWidth - 100) + 100
- posY := math.floor(rand() * gameWorld.screenHeight - 100) + 100
- size := math.floor(rand() * 30) + 20
- rot := rand() * math.Pi * 2
- sprites := add(sprites, tmpl.newAsteroid("asteroid-{{i}}", posX, posY, size, rot, 0.005))
- }
- GameState[gameName] := {
- "players" : {},
- "sprites" : sprites,
- "stats" : {},
- "world" : gameWorld
- }
- }
- }
- } except e {
- error(e)
- db.raiseWebEventHandled({"status" : const.ErrorCodes[e.type], "body" : {"error" : e.type}})
- } otherwise {
- db.raiseWebEventHandled({"cstatusode" : 200, "body" : {"result" : "success", "gameworld" : gameWorld}})
- }
- }
- /*
- Register a new websocket connection
- Endpoint: wss://<host>/db/sock/gamestate
- */
- sink WebSocketRegister
- kindmatch ["db.web.sock"]
- statematch {"path" : "gamestate", "method" : "GET"}
- priority 0
- {
- let gameName := event.state.query.gameName[0]
- let playerName := event.state.query.playerName[0]
- let commID := event.state.commID
- mutex WebsocketMutex {
- Websocket[commID] := {"gamename" : gameName}
- }
- log("Register websocket for player: ", playerName, " in game: ", gameName, " commID: ", commID)
- }
- /*
- Register a new player.
- Endpoint: /db/ecal/player
- */
- sink RegisterNewPlayer
- kindmatch ["db.web.ecal"]
- statematch {"path" : "player", "method" : "POST"}
- priority 10
- {
- let sprite
- let gameWorld
- try {
- let playerName := event.state.bodyJSON.player
- let gameName := event.state.bodyJSON.gameName
- let gameWorld := GameState[gameName].world
- mutex GameStateMutex {
- if GameState[gameName].players[playerName] == null {
- posY := math.floor(rand() * gameWorld.screenHeight - 100) + 100
- GameState[gameName].players[playerName] := tmpl.newPlayer(event.state.bodyJSON.player, 20, posY)
- /*
- Reset any player score
- */
- addEvent("changescore", "main.gamescore", {
- "id" : playerName,
- "part" : gameName,
- "changeFunc" : func (s) {
- s.score := 0
- }
- })
- log("Registered player: ", playerName, " for game:", gameName)
- }
- }
- } except e {
- error(e)
- db.raiseWebEventHandled({"status" : const.ErrorCodes["InternalError"], "body" : {"error" : const.Errors.InternalError}})
- } otherwise {
- db.raiseWebEventHandled({"status" : 200, "body" : {
- "result" : "success",
- "sprite" : sprite,
- "gameworld" : gameWorld
- }})
- }
- }
- /*
- Handle player input - send over an established websocket connection.
- */
- sink WebSocketHandler
- kindmatch ["db.web.sock.data"]
- statematch {"path" : "gamestate", "method" : "GET"}
- priority 0
- {
- try {
- let playerName := event.state.data.player
- let gameName := event.state.data.gameName
- let action := event.state.data.action
- let state := event.state.data.state
- mutex GameStateMutex {
- if GameState[gameName].players[playerName] != null {
- for [k, v] in state {
- GameState[gameName].players[playerName][k] := v
- }
- if not action in ["move", "stop move"] and GameState[gameName].players[playerName].action == null {
- GameState[gameName].players[playerName].action := action
- }
- } else {
- log("Someone didn't know they were gone: ", playerName)
- addEventAndWait("StateUpdate", "db.web.sock.msg", {"commID" : event.state.commID, "payload" : {"toRemovePlayerIds" : [playerName]}})
- }
- }
- } except e {
- error(e)
- }
- }
- /*
- GameScore sink.
- */
- sink MainGameScore
- kindmatch ["main.gamescore"]
- priority 100
- {
- try {
- let scoreObj := db.fetchNode(event.state.part, event.state.id, const.ObjectKinds.ScoreObject)
- if scoreObj == null {
- scoreObj := {
- "key" : event.state.id,
- "kind" : const.ObjectKinds.ScoreObject,
- "score" : 0
- }
- }
- event.state.changeFunc(scoreObj)
- db.storeNode(event.state.part, scoreObj)
- } except e {
- error("GameScore:", e)
- }
- }
- /*
- Object for main game engine.
- */
- MainGameEngine := new(engine.GameEngine, "main", tmpl.DefaultGameWorld, GameState, Websocket)
- /*
- GameLoop sink.
- */
- sink MainGameLoop
- kindmatch ["main.gameloop"]
- priority 100
- {
- try {
- MainGameEngine.moveLoop()
- } except e {
- error("Game loop:", e)
- }
- }
- /*
- Period game events loop.
- */
- sink PeriodicGameEvents
- kindmatch ["main.periodicgameevents"]
- priority 100
- {
- try {
- mutex GameStateMutex {
- for [gameName, state] in GameState {
- let gameWorld := state.world
- if len(GameState[gameName].sprites) < 10 {
- log("Adding more asteroids", gameWorld.screenWidth)
- sprites := GameState[gameName].sprites
- for i in range(1, 4) {
- posX := math.floor(rand() * gameWorld.screenWidth - 100) + 100
- posY := gameWorld.screenHeight - 100
- size := math.floor(rand() * 40) + 20
- rot := rand() * math.Pi * 2
- sprites := add(sprites, tmpl.newAsteroid("asteroid-{{now()}}-{{i}}", posX, posY, size, rot, 0.005))
- }
- GameState[gameName].sprites := sprites
- }
- }
- }
- } except e {
- error("Periodic events loop:", e)
- }
- }
- /*
- Trigger the main game loop in a set interval (microseconds). The interval here
- must always be greater than the total time of the move loop (see the time_total_move
- stat recorded in engine.ecal).
- 35000 - 35 milli seconds - smooth animation calculated in the backend, frontend only needs to display
- */
- setPulseTrigger(35000, "Main Game Loop", "main.gameloop")
- /*
- Trigger periodic events in the game
- 1000000 - 1 second
- */
- setPulseTrigger(1000000, "Periodic Game Events", "main.periodicgameevents")
|