clojure-gamedev

Fabim 2021-02-19T14:20:52.002800Z

Hey I’m developing a game in Clojure. Looking forward to exchanging ideas about best practices.

🎉 3
Jakub Zika 2021-02-22T08:12:08.006300Z

What are you using on FE? Re-frame etc. or do you paint in canvas?

Fabim 2021-02-22T11:12:39.006600Z

@jakub.zika-extern I normally use re-frame for my projects. For the game instead I use html divs and css animations. I would switch if I find a good what to handle the animations

💯 2
Jakub Zika 2021-02-22T11:16:17.006800Z

@merklefabian I am playing with Quil currently (wrapper over processing(js)), it is nice. You can create simple 2d/3d games with it. http://quil.info/api You can also provide atom to quil so it can be easily connected to RUM (and reagent and reframe - but i did not try it yet), so you can control your canvas by custom html elements… + I would recommend tips taken from: https://github.com/alexkehayias/chocolatier Tips Here are some tips for optimizing performance of game loops: • Use the Chrome dev tools to do a CPU profile and trace where time is being spent • Don’t use `partial` or `apply` as they are slow • Always specify each arrity of a function instead of using `(fn [& args] ...)` • Don’t use `multimethod`, use `cond` or `condp` and manually dispatch • Use `array` when you need to quickly append items to a collection (mutable) • Use `loop` instead of `for` or `into` with transients or arrays as the accumulator • Avoid boxing and unboxing i.e multiple maps/fors over a collection, use transducers, reducers or loops • Don’t make multiple calls to get the same data, put it in a `let` • Avoid heavily nested closures as the lookup tree becomes very long and slow • Favor eager operations over lazy operations i.e `reduce` instead of `for` • Don’t use `concat` or `mapcat` as they can be slow and generate lots of garbage • Don’t use `last` as it will need to traverse the whole sequence, use `nth` instead if you know how many elements are in the collection • Don’t use hashmaps as functions `({:a 1} :a)`, instead use `get` or keywords as functions • Always return the same type from a function (V8 can then optimize it)

Fabim 2021-02-22T11:27:24.007100Z

@jakub.zika-extern Thanks for the extensive tips. I saw chocolatier before but had the impression that it was discontinued. Quil looks interesting. Why do you say Don’t use multimethod` `

Jakub Zika 2021-02-22T16:21:20.008300Z

Chocolatier is discontinued, yeah. Multimethod can get slower than using condp . Your code can get ugly once you will start optimizing JS / Java in Clojure - or you can keep your game simple and it should be fast enough 🙂.

Fabim 2021-02-22T16:46:08.011300Z

Slower of call time or initialization? I’m using multimethod a lot to separate out the logic of different cards. Didn’t notice any huge speed difference.

Jakub Zika 2021-02-22T16:46:56.011500Z

I remember that i had got some better call time results with condp but dont take it as granted. It is long time ago…

Fabim 2021-02-22T16:48:07.012100Z

What are you using Quil for?

Jakub Zika 2021-02-22T16:51:11.012300Z

I did some fun stuff like walking in 3d environment with wasd and jumping etc. But nothing is finished yet. I am more interested in creating the engine and the toolset than the game itself to be honest.

Fabim 2021-02-26T19:34:08.017Z

@jakub.zika-extern I guess that means one day I will be using your engine then, ha

😁 1
oconn 2021-02-19T16:36:33.003200Z

What are you looking to use?

2021-02-19T17:42:56.003700Z

@merklefabian What libraries or tools are you using?

Fabim 2021-02-20T08:32:48.000100Z

Thanks. Not planing to query that data, just need to be able to persist it through code deploys and then load it into memory again.

Fabim 2021-02-20T14:10:27.005500Z

@oconn could you show me how you manage the web sockets and allocate a connection to a specific user/match? I’m reworking the networking code because I’m introducing authentication, login ect. Glad to have found someone who uses sockets as well ~

oconn 2021-02-20T17:31:48.005800Z

This is just the way we’re doing it - hope some of this helps; 1. There is an unauthenticated ws connection that gets upgraded upon authentication 2. On an upgrade connection event, a row is created in our DB that maintains the connection details for that user 3. When a game is created, references to all the users (who in turn have references to their current connection identifiers) are stored in the DB for that game 4. When an event occurs within that game that needs to be broadcasted to all the participants, it has access to all the connection-ids.

oconn 2021-02-20T17:34:23.006Z

When it comes to managing the connection pools, we don’t do that. We rely on a 3rd party service for that.

Fabim 2021-02-22T13:11:19.007400Z

@oconn Thanks for your answer. Would you share the code how you do 1. with me, please?

oconn 2021-02-22T13:34:26.007600Z

@merklefabian No problem. That workflow is going to be dependent on what you’re using for your websocket server & authentication / user pool. It may be more useful if you share architecture / code for your specific setup. With that said, I’m doing this; 1. Each time a player opens the browser, I check to see if they are authenticated. (Do they have a valid auth token) 2. If the player does have a valid auth token then jump right to an authenticated connection

(let [connection-url
      (cond-> ws-url
        (some? credentials)
        (str "?token=" credentials)

        (nil? credentials)
        (str "?token=anonymous"))

      set-new-connection
      #(let [ws (new js/WebSocket connection-url)]
         (doto ws
           (aset "onopen" (partial ws-on-open ws on-open))
           (aset "onclose" (partial ws-on-close ws on-close config))
           (aset "onmessage" (partial ws-on-message ws))
           (aset "onerror" (partial ws-on-error ws on-error))
           (aset "connection-identifier" connection-identifier))

         (reset! last-connection-attempt (time-now))
         (reset! conn ws))])
3. On the server, before the authenticated connection is created, the credentials are checked against the user pool to identify the player. 4. The player can now send / receive messages over the websocket. You would adjust the onopen onclose onmessage onerror handlers to work with your app. Not sure what you’re using for your websocket server, but I’ve had luck using https://github.com/ptaoussanis/sente and http-kit (one of its supported web servers) before.

Fabim 2021-02-26T18:08:02.016800Z

@oconn thanks for your answer and the snippet. My current setup is core.async channels for the connections, a http-kit server, chord.http to wrap the websocket handler. I will be switching from compojure to reitit since I need to rework the logic to enable authentication (using buddy). With 3. you mean the server opens a new connection to the client or do you use the incoming one from the client?

oconn 2021-02-26T22:37:08.017200Z

Always use the incoming connection from the client. Because of what I’m using to manage connection pooling, websocket connections make a request to the ws endpoint and if they don’t provide authentication details (in my case this is a short lived token) they get an anonymous connection with limited privileges. If they do provide the token then I associate the connection information with that user. This is probably going to be much different for you (and possibly simpler in some ways) because you’re in control of everything (user pool, http server, authentication libs). You could probably use one secure connection and associate that connection with a user after they have authenticated, or the server has determined that they are authenticated.

Fabim 2021-02-19T17:51:15.004300Z

I’m using clojure/reitit/integrant on the backend und use sockets to connect to a clojurescript/html frontend

Fabim 2021-02-19T17:51:38.004500Z

I’m using clojure/reitit/integrant on the backend und use sockets to connect to a clojurescript/html frontend

Fabim 2021-02-19T17:52:02.004700Z

So not really libraries I guess

2021-02-19T17:52:05.004900Z

What do you use for the rendering?

Fabim 2021-02-19T17:52:35.005100Z

I just render html divs

👍 1
2021-02-19T17:52:47.005300Z

oh, I see

Fabim 2021-02-19T17:53:20.005600Z

I tried many other solutions but they all seemed a lot of overhead

Fabim 2021-02-19T18:00:10.006100Z

yes I have seen it. Don’t quite understand why datascript would be needed.

2021-02-19T18:00:42.006300Z

It is not required, but it was used because Datascript was made by the author of the video.

Fabim 2021-02-19T18:00:58.006500Z

haha

2021-02-19T18:01:26.006700Z

You may also give ThreeJS a try if you want something more graphical.

Fabim 2021-02-19T18:01:41.006900Z

yes I watch all his videos. too bad that he didn’t continue the series

2021-02-19T18:02:00.007100Z

https://github.com/DougHamil/threeagent

Fabim 2021-02-19T18:02:56.007400Z

Are you using it?

2021-02-19T18:03:14.007600Z

I used ThreeJS from JS in the past.

Fabim 2021-02-19T18:05:00.007800Z

I’m hesitant on relaying on a library. I tried Oakes play-cljc before. It was nice but overkill for a boardgame like I’m doing.

Fabim 2021-02-19T18:05:06.008Z

what are you using now

2021-02-19T18:06:56.008200Z

I am not making games at the moment. I used to use Unity.

2021-02-19T18:08:11.008400Z

You might be right to keep it simple.

oconn 2021-02-19T18:11:25.008600Z

Nice - yeah i’ve worked on projects in the past that rely on the DOM for rendering. Currently I’m working on a project that uses Phaser 3. I’m also using websockets + transit to pass data. For application state management I’m using Datascript, but for game state management I’m leaning on phaser 3. Keeping it simple is always a good idea in my book :simple_smile:

👍 1
2021-02-19T18:13:09.008800Z

There is a nice library which I recommend for writing reactive games: https://github.com/oakes/odoyle-rules

oconn 2021-02-19T18:17:21.009100Z

^ This looks pretty cool, thanks for sharing

Fabim 2021-02-19T18:50:50.010300Z

@vincent.cantin Are you using it in production?

2021-02-19T18:51:06.010500Z

Not yet

Fabim 2021-02-19T18:51:43.011Z

What will you build

2021-02-19T18:52:03.011300Z

at the moment, what I build is not related to games

Fabim 2021-02-19T19:02:44.015300Z

@oconn how do you persist your games on the backend

Fabim 2021-02-19T19:02:55.015500Z

I’m looking for the best way to persist my game state on the backend. Leaning towards JSON in a Postgres. Does anyone have suggestions

oconn 2021-02-19T20:22:13.015600Z

AWS DynamoDB

Fabim 2021-02-19T20:33:47.015800Z

@oconn as JSON or single fields? I’m currently on heroku but don’t like that they mainly offer SQL

oconn 2021-02-19T20:34:53.016Z

You can nest json in dyanmo rows, but I haven’t had a need for that yet.

Fabim 2021-02-19T20:35:37.016200Z

so you save the whole state in as one string

oconn 2021-02-19T20:43:22.016400Z

if you’re working exclusively with JSON it may be worth looking at a DB that has better support for it. > so you save the whole state in as one string In my setup - game state can be hydrated from multiple rows in the DB, but is mostly held in memory.

Fabim 2021-02-19T23:00:07.020Z

My state currently is a clojure hash-map held in memory as well. JSON in Postgres would be one option. Is there code published somewhere how you hydrate the state from multiple rows?

2021-02-19T23:20:16.021200Z

The absolute best way depends on context. JSON in a Postgres JSONB column is underrated and is a fine way to get started — it’s a better Mongo than Mongo — and will take you quite far, but may not be the “best” way in general.

👍 3