Background
A few months back, I chanced upon this nifty little library named scala-graph. This library provides some basic graph functionality and its APIs fit very well with standard Scala collection APIs. The graphs that it help create are in-memory data structures. It provides quite a number of ways and means to define, populate, manipulate and access directed and undirected graphs, along with edges which can be labeled and can have weights.When I began to play around with it, I realized that a lot could be done. However, the question I had was, of so many features, which were to be picked up? I began to look for a small application which could be modelled as a graph rather intuitively. Finally, I chose to implement a Game Lobby using scala-graph.
So, what is a lobby?
In the world of multiplayer games, there exists a concept of a Lobby. A Lobby is where clients come in after they log in. A lobby provides live information about many aspects of the ongoing games, such as- The tables (or rooms in some literature) that are laid out
- The characteristics of the tables (viz., stake value, duration, private or public)
- The players who are actively playing on them
- What are the current states of the games being played on them (viz, current round, rounds remaining)
- (in case of tournaments) which stage is a tournament right now
- Who are winners of recently concluded games / tournaments
Typically, a separate Game Server holds (contains, if you like) the data structures that represent tables, players, tournaments etc. Game Server is responsible for accepting moves by the players and applying rules of the game. It also is responsible for update counters, scoresheets and for persisting data. Importantly, it forwards (streams, to be more accurate) pieces of information to the Lobby continually. For the world to know what is happening across all the tables and tournaments, Lobby is the point to go to, and not the Game Server.
It follows, therefore, that a Lobby's contents are updated (WRITE op) by entities like Game Server and accessed (READ op) by other entities like Client applications, Admin applications and others such.
Who all reside in the Lobby?
There could be many, but in this small application, the residents are:- Player
- GameTable
- GameType (several tables can be laid out where only one type of game is played)
- ScoreSheet (of a particular table)
- TopScorers (of a particular game-type)
For example, in the world of OO, we can model the relationship between a table and the players on it as a 'HasA' relationship.
GameTable HasA Player(s)In java, if you prefer:
class GameTable {In the world of graph, however, it is terse yet, illustrative:
private ArrayList<Player> players = new ArrayList<Player>()
.......
}
scala-graph allows us to capture these two relationships as (Figure:1)
(Table ~+> Player)("SeatOccupiedBy")and
(Player ~+> Table) ("IsPlayingAt")~+> is one of the many operators defined by scala-graph. It is a rather cute and handy synonym for a class named LDiEdge (Labeled Directed Edge).
It rather follows that when multiple players are playing on a table, a Table node will be 'related to' multiple Player nodes, but through the same relationship (Figure: 2). The edges are labelled with 'String' type values here; these are the names we give to particular relationships.
More specifically, in this case:
We created a collection of two relationships because it is easier (and faster, according to a discussion in the googlegroups) to add to the graph, a bunch of relationships in a single go.
A lobby is defined as a graph of LobbyNode and LDiEdge:
var lobby = Graph[LobbyNode,LDiEdge]()Then, labelled triples are added in a bunch:
Graph - immutable or mutable?
scala-graph allows a graph to be created in either of the ways. If the number of nodes and edges is too huge, it makes sense to reconsider the decision to stick to a pure immutable approach. It is quite likely, that a useful graph will undergo a lot of additions, removals and rearrangements of its contents during its lifetime. Prudence matters in such situations and final decision lies with the application developer.
In this case, I have used an immutable graph but its reference is modifiable (var). As and when, I add new nodes and relationships to the graph, effectively a new graph is created and the reference points to the new (modified) graph. scala-graph's programming guide provides a good background of this topic.
Holding a lobby in a var makes sense because a lobby has dynamic characteristics. As long as the game server is available, players will keep (hopefully :-) ) visiting the site, watch games being played, join tables, leave tables, score high (or low) etc. Each of these events will cause a modification of the contents of lobby. It is indeed expected to be continually changing.
Retrieving useful information from lobby
Having the lobby filled with the nodes and edges in the manner described earlier (also take a look at the picture), we can then begin to access the information that we want, in a very functional way. To find out the tables on which a player is playing (smart that s/he is, s/he may be playing on multiple tables simultaneously), we do:
- (line 2) We retrieve the Player node from lobby using get() operation, which works on exactly the same principle of depending on equality and hash of objects. Of course, the usual guard actions like getOrElse() applies here too, but then it is a demonstrative application and truth be told, I have been a bit lazy recently!
- (line 2) We collect only those edges which are going out from this Player node. This is the key and is perhaps obvious from the pictures above. We then take a view of this collection because we want to be frugal with creation of intermediate data structures.
- (line 3) Somewhat intuitively perhaps, we filter only those outgoing edges, which bear the label "IsPlayingAt".
- (line 4) Then, we pick the other ends (targets) of the filtered edges. To be more precise, we pick the nodes which are connected to the Player node through the "IsPlayingAt" labelled edges, which are of type, Tables (refer to the code snippets earlier).
- (line 5) Convert the Node from an inner type (of scala-graph) to a type which is accessible from outside the graph (this is an important concept, more details here)
- (line 6) Finally, we pick only those nodes which are of type GameTable. This is functionally equivalent to asInstanceOf[GameTable] and is required because of shortcoming of the current version.
- We get a list of the tables on which the given Player is playing.
Complete code is available here. The following classes provide a good view of what I have attempted:
- src/main/scala/org/nirmalya/projects/gameLobby/contents/LobbyFacade.scala
- src/test/scala/org/nirmalya/LobbyTest.scala
Please leave your comments and critiques, if any. Help me to write better Scala code! :-)
Many thanks, Peter Empen (of scala-graph) for patient answers to my newbie questions on googlegroups.
titanium arts
ReplyDeleteTATONIC ART CUSTOMING · TATONIC ford escape titanium ROCKING gri-go.com T-TATONIC ROCKING T-TATONIC ROCKING 1xbet korean T-TATONIC. This worrione.com unique and original design is crafted with the https://deccasino.com/review/merit-casino/ use of sustainable