Rock Paper Scissor Game through Actor Model in Scala

Karthik Vaidyanathan
Oct 20 · 12 min read

I had an opportunity to attend a wonderful course during my first year of Ph.D. at the GSSI. The course was offered by Prof. Rocco De Nicola. It was on The principles of Concurrent and Distributed Programming. One of the projects for the course was to develop a program to simulate a three-player distributed Rock/ Paper/Scissor Game. It’s a game we all would have played at some point in our lives.

For people who don’t know the game, please refer here. It is a hand game where each player simultaneously forms the shape of a rock, paper or scissor and based on the rules of the game, the points are assigned and the game goes on.

An easy way to do develop this game is to have 3 processes where each process represents a player. Every process will select their moves one by one and based on the rules the score can be computed. However one of the important constraints of the game is that the moves cannot be made in a sequential way rather it should happen at the same time. This can be seen as a classic example where concurrency plays an important role. One more additional challenge that was given for the project was that there should not be any central coordinator. For example, if there was a central coordinator then the three processes could send their moves concurrently to a central process which can then compute the score and communicate to each process. This means that the processes should be able to concurrently send their moves to each other as well as have a view of the total scores

Game Rules :

  1. Each player should be able to randomly select a move from the set of available moves {rock,paper,Scissor}
  2. The user should be able to specify the number of rounds for which the game has to be conducted.
  3. Every player shall be able to send and receive moves to and from other players
  4. Every player should have means by which it can send it’s move to the other two players
  5. Each player should have a view of the global score of other players such that at any instant, a player will have the score information of all the three and it should be consistent
  6. Only when the round has finished in each of the players, the next round can be started

The choice of language was given to the students and I selected Scala for my implementation. The reason was pretty simple, I wanted to try out the Actor model (Many languages like Erlang, Haskell. Java support Actor model) and also to get my hands on some Scala code.

Scala is one of the most used programming languages for implementing concurrent and distributed systems. This is more clear from the fact that one of the key big data processing frameworks like Apache Spark is completely written in Scala. Due to the functional as well as object-oriented behavior provided by Scala, it is also one of the most used programming languages in the world. There are several other features provided by Scala that makes it more popular for writing distributed and parallel applications [1].

Scala and AKKA

Even though Scala can uses threads to enable concurrency as similar to Java, Scala has also a powerful way of enabling concurrency using Actor models provided by the AKKA framework. Akka uses the Actor model for handling concurrency. In the actor-based system, everything is an actor and it resembles an object in the Object-oriented design. The actor model was created as a model for concurrency. Every actor has a state and behavior. Actors run on real threads and more than one actor can run on a single thread and this is handled by the framework.

Actor model of Akka (source : https://www.toptal.com)

In Scala actor system every actor interacts and shares information without any sequential behavior and the communication happens through message passing. Some of the important features offered by the Akka model are as follows :

Creation of parallel process

The creation of a parallel process is enabled by the Akka framework which helps in creating actors that are the smallest unit. As many as 2.7Million actors can be created and used for every GB of RAM which makes the whole system highly scalable, Actors can be created and terminated in run-time thereby making the whole system more scalable.

Communication

The basic communication means offered by the Akka model is message passing. In Akka, actors are very lightweight concurrent entities that communicate asynchronously by sending messages between each other. As with Erlang, Scala uses pattern matching to figure out what kind of messages it receives. Actors can just be accessed by sending the message, it does not provide means to directly interact with any of the methods/fields. An actor has a mailbox, whenever a message is sent, the messages are immutable and they go to the mailbox of the actor. On the receipt of a message, the actor takes the necessary step as defined and moves to the next message.

Synchronization

One of the important problems that exist in concurrent programming is handling synchronization, In the case of the Akka framework, a layer exists between the underlying system and the actor model such that the only task of the actors is to process messages. The complete underlying complexity of handling race conditions, synchronization issues, the creation of threads, dispatching of messages is handled by the framework. Every actor has a state and behavior. Every actor runs its lightweight thread and the messages are processed one at a time. In this way, the state of each actor is maintained reliably and the developer needs not to take care of the issues of race conditions and synchronization issues.

Mutual Exclusion

As explained above, every actor maintains its state and irrespective of the number of messages that an actor receives in the mailbox, the messages are processed one by one and only one message is processed at a time which ensures that the possibility of locks does not arise. One message may update a state and following this, the next message may again change the state but at one instant of time only one update in the state happens which guarantees mutual exclusion

Apart from the above-mentioned advantages, some more advantages offered by Akka are as follows :

  1. Encapsulation is preserved as execution is not transferred, only the messages are passed between actors
  2. The complete concept of Locks does not exist as the internal state of the actor can be modified only by using a message. This also means that senders are not blocked and millions of actors can be easily scheduled
  3. State of actors are not shared and it is local. The changes/data is propagated as messages between actors by keeping the local state and data preserved within the actors
  4. Actors also handle error situations in a very clean manner. On the event of an error in an actor, the error is propagated as the message to the calling actor and in the event of a failure of an actor itself, the parent actor gets notified and all child actors can be stopped recursively

Implementation Method

I have been a big fan of the IntelliJ IDE’s and I thought it will be the best choice to implement this program. They also have very good support for Scala Projects. So the basic ideas for implementation were to convert each player into an individual actor. So a class for each of the players was created and each of these classes extended the actor class.

Listed below are the objects and the corresponding implementation details :

  1. ConfigReader Object :

The first part was to create an object, ConfigReader, which is responsible for reading the input configurations of the program which can be customized such as the number of rounds, precedence of moves and other parameters which can be fed to the program from outside so that the hard coding part can be avoided. For this, settings.conf file was created and the parameter rounds were set. Then this file was then used in ConfigReader object to read the parameter value using the ConfigFactory library offered by Scala. The variable of ConfigObject is kept public so that any calling function can use this to determine the number of rounds for which the game has to run. Following this, I added a function, generateMove to generate a random number between 1 and 3 and use this to return a move to the calling function. Another public variable offered by this object moves which contain the possible moves in a list in the order rock, paper, and scissor respectively.

Config Reader Object

2. RuleEngine Object :

This object is responsible for handling gaming logic such as the computation of scores. This is kept as a separate object to ensure the fact that any future changes in the rules can easily be accommodated just by making changes in this object rather than modifying the whole code. It contains only one method, getscore which takes as input, the move played by each of the player in the order player1, player2 and player3 and returns a scoreList which contains the scores of the individual players in the order player1, player2 and player3. To compute the scores, this method uses the game rule which is modeled in a precedence list. This says which move dominates the other/other two. The object also uses a variable ruleMap which uses a map data structure to store the winner of possible pair combinations of moves. From the given condition it is clear that if each player plays a unique move then no one gets any point. This condition is considered as the default condition and the scoreList is initially filled with 0. Whenever the function receives the moves from each player, the condition checks are made to find which two moves are same and which two moves are different, The two different moves identified are passed to the ruleMap to identify the winner among the move and based on the fact that if the moves played by 2 similar ones are higher or lower than the third one, the scores are computed.
For example: if the moves played are in the order rock, paper and paper for player 1, player 2 and player 3 respectively, then player 2 and player 3 receive 1 point each as paper has higher precedence than rock. If on the other hand the moves played are rock, rock, and paper then player 3 will receive 2 points. This distinction is made by the condition checks and the score is computed and the scoreList is returned

Snapshot of the RuleEngineObject

3. Player Object :

There are three companion objects for each of the player class which in turn models each of the players. These objects contain the declarations of one case class which is playMove and a case object, play. playMove takes as arguments two strings corresponding to the move and player id. The purpose of the first case class is to receive the moves played by other players and that of the second case class is to send the move played by the player to the other players. In this way, there is no need for a central coordinator rather every player can have means to locally compute the scores of every player.

Player Object

Player Class :

There are three player classes corresponding to each of the players. Each of them extend the actor class which mandates the fact that the receive method needs to be implemented which is responsible for performing an action on the receipt of a message sent by an actor. For each of these players, we maintain 3 variables which are local to each of them, they are scoreMap which uses mutable map to store the score of each of the players, moveMap which again uses mutable map to store the moves played by each of the player and moveQueue which uses a mutable queue to store the moves received for that particular player. The scoreMap is initialized to 0 for each of the player which denotes the fact that at the beginning, each player has 0 points. We also maintain a list of type int, scoreList which is responsible for storing the scores obtained from the score function from RuleEngine Object. We also maintain an integer variable, roundNumber to keep a track of the number of the current round. The next step was implementing the receive method. The purpose of receive function is to define what happens when the actor is invoked using the case classes that has been declared in the companion object. There are two case classes that needs to be defined on this regard :

PlayMove (playerMove, playerId):

This class is basically for handling the messages sent by other players. Since the game is a 3 player game, once a player selects its move and receives moves from other 2 players, it can compute the score. On invocation, it takes the move and identifies the player from the playerId and this is then pushed into the moveMap as <player,Move> with the corresponding move for each player. Once the size of the moveMap of the player reaches 2, it pops out its own move from the moveQueue and then takes the move from the other 2 players and these three scores in the order of player1, player2 and player3 are passed as parameters to the getScorefunction of the RuleEngineobject to obtain the scoreList in order. The scoreList is then used to update the scoreMap which ensures that each player has a local view of the scores of the other players and then the moveMap is cleared thus enabling the next set of round to be processed and this goes on until all the messages are processed. The case class functionality offered by Scala enables all these message passing to happen in a seamless and efficient manner.

PlayMove case

Play :

This is a case object and hence it does not take any parameters. It basically calls the generateMove to randomly generate a move for itself. This is then inserted into the moveQueue to preserve the order in which the moves are generated and then it sends these moves to the other 2 players through the message passing functionality offered by Scala Akka and by calling the playMove function with its move along with the corresponding playerId of the player.

Play case

4. GameDist Object :

This object contains implements the main method and it is responsible for executing the application. It imports all the 3 player classes and then implements the main method. The method first calls the readConfig method of the ConfigReader object to read the configurationfile. It then reads the number of rounds from the file. This then initializes the Actor system by creating a variable named system of type Actor System with the name ” Game”. This becomes the root actor and then three variables corresponding to each of the players are created which references each of the actors and all are created as the root of the main actor, system. Following this, a variable named randomNumber is defined to create random numbers to model the scenario of players randomly selecting a move. The possible set of moves is read from the ConfigReader object as a list variable, possibleMoves and then a loop is created to keep running the game till the number of moves specified in the settings.conf file is completed. Every player makes a call to the Play case of their respective actors and the game continues until the total rounds are completed. This is completely done in a parallel manner.

GameDist Object

Now all you need is to run and enjoy the game !!. So that’s pretty much of it. I hope this post helps in understanding how to create a small program based on the Actor model in Scala using the AKKA framework. Future directions of the work can be, in the offering of AKKa model as a web-service to model the real-world scenario and then a custom UI can be built to make the program more interactive. Also, the program can be extended to an n player game with n>3.

The complete source code with instructions can be found here

PS: I am not an expert in Scala so I may not have written the code as per Scala programming standards. The idea here is to convey the concept of the Actor model and how to use it

Hope you all liked this post. For further questions, feel free to drop me an e-mail at karthikv1392@gmail.com

References

[1] Martin Odersky, Philippe Altherr, Vincent Cremet, Iulian Dragos Gilles Dubochet, Burak Emir, Sean McDirmid, Stephane Micheloud, Nikolay Mihaylov, Michel Schinz, Erik Stenman, Lex Spoon, Matthias Zenger,’An Overview of the Scala Programming Language’, Technical Report LAMP-REPORT-2006–001

[2] MateiZaharia, MosharafChowdhury, MichaelJ.Franklin, ScottShenker, IonStoica’Spark: Cluster Computing with Working Sets’, University of California, Berkeley

Karthik Vaidyanathan

Written by

Computer Science PhD Student at Gran Sasso Science Institute, Italy

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade