-
Notifications
You must be signed in to change notification settings - Fork 2
Semester Project Report
#http://reminisce.me — a game to measure the memorability of autobiographical data
##Introduction The project reminisce.me is a game based on the user's Facebook data in the purpose to study the memorability of autobiographical data. It's a online game inspired from Tic Tac Toe; the user has to answer three questions about his Facebook activity in order to take a cell of the board. In my project I worked on the Statistical module of the game and particularly on the back-end. The module is an autonomous application that is requested by the Game Application to compute the new statistics after a new play or to retrieve them.
##Technology The statistical module is a Scala application with Akka toolkit and runtime. Akka emphasizes actor-based concurrency. The RESTFul part is implemented with Spray toolkit that allows to build REST/HTTP-based integration layers on top of Scala and Akka. The data are stored in a MongoDB database and the interface between the application and the database is achieved with ReactiveMongo, a driver Scala Driver for MongoDB. MongoDB is a NoSQL database in which data are stored in a JSON-like form call BSON (For Binary JSON)
Two main data entities: Game and Statistics. The first represents the summary of a play between two players. It contains the userIDs, the scores, the questions, etc. The second contains the statistics for a user.
Both of them are stored in the database in his entirely as a single document of the collection. The mongoDB database contains two collections:
- gameCollection: Contains all Game entities that have been received
- cacheCollection: Contains all Statistic entities that have have computed
{
_id: String,
player1: String,
player2: String,
player1Board: Board,
player2Board: Board,
status: String,
playerTurn: Int,
player1Scores: Int,
player2Scores: Int,
boardState: List[List[Score]],
player1AvailableMoves: List[Move],
player2AvailableMoves: List[Move],
wonBy: Int,
creationTime: Int
}
-
_idis the ID of the play -
player1andplayer2are the ID's of the two players -
player1Boardandplayer2Boardare the game board of the play -
statusindicates whether the game is ended or not -
playerTurnindicates which player can play -
player1Scoreandplayer2Scoreindicates the points obtained by the player -
player1AvailableMoveandplayer2AvailableMovekeep a list of available move for the user -
wonByindicate which player has won -
creationTime: creation tim of the game entity
More detailed informations about each subpart are available here
{
userID: String,
frequencies: FrequencyOfPlays
}
-
userIDis the ID of the user for whome the statistics was computed -
frequenciescontains the list of all statistics for each type of interval
The stastistics are computed on different intervals (time unit): day, week, month, year and overall
{
day: List[StatsOnInterval] = List(),
week: List[StatsOnInterval] = List(),
month: List[StatsOnInterval] = List(),
year: List[StatsOnInterval] = List(),
allTime: Option[List[StatsOnInterval]] = None
}
-
daylists the statistics computed on an daily interval -
weeklists the statistics computed on an weekly interval -
monthlists the statistics computed on an monthly interval -
yearlists the statistics computed on an yearly interval
{
ago: Int,
amount: Int,
won: Int,
lost: Int,
questionsBreakDown: List[QuestionsBreakDown],
gamesPlayedAgainst: List[GamesPlayedAgainst]
}
-
agois the number of unit time from now for whom the statistic was computed -
amountis the number of game played ba the user during the interval -
wonandlostare the number of won plays, respectively lost plays. -
questionsBreakDownlists all questions that the player has answered -
gamesPlayedAgainstlists all oponent against which the user has played
{
userID: String,
numberOfGames: Int,
won: Int,
lost: Int
}
-
userIDis the ID of the opponent -
numberOfGameis the number of plays against this opponent -
wonandlostare the number of won, respectively lost, plays against this opponent
{
questionsBreakDownKind: QuestionsBreakDownKind,
totalAmount: Int,
correct: Int,
percentCorrect: Double
}
-
questionsBreackDownKindis the type of the question -
totalAmountis the number of question of this type answered by the user -
correctis the number of corect answers -
percentCorrectis the percentage of correct answers
The module is a RESTful application that supports two requests:
-
GET: Statistic retrieving
- address: http://localhost:777/stats
- parameters
-
userIdthe id of the user -
frequencyto return -
allTimeif the summary of allTime should be included (true,false), if omitted defaults tofalse
-
Example
This request asks for statistics of 1userID for the last 30 days, 4 weeks, 3 months and 1 year without the overall summary
More detailed informations about the API are available here
-
POST: Game insertion
- address: http://localhost:7777/insertEntity
- header: Content-Type: application/json
- body (raw): JSON reprensentation of a Game entity
At the reception of the request, the parameters are parsed into a Timeline object which contains the number of time unit to return:
{
userID: String,
day: Int,
week: Int,
month: Int,
year: Int
}
The default values are: day = 30, week = 5, month = 12, year = 10
Each request received by the StatServer is dispatched to the relevant service. In the application there are three main services: One for inserting an entity in the database, one for computing the statistics and one for retrieving data from the database. All services deleguate the tasks to worker. All Services and Workers are Akka actors.
The figure below shows the request dispatching to the Insertion and Retrieving Service.
The insertion is managed by the Insertion Service. He can insert Game of Statistic entity.
The figure below shows the workflow of a Statistics entity insertion in blue and the Game entity Insertion in Red.

- A client send an
InsertEntitymessage to the Insertion Service - The Service forward the message to a worker
- The worker inserts the entity in the database and send back a
Inserted(ids)message which contains the list of userID contained in the game inserted. - The service send a
InsertionDonemessage to the client. - The service create two Computation Services and send a
ComputeStatistics(id)message to both of them. - The Computation Service compute the statistics and send it a
Donemessage to the Computation Service - The Insertion is complete, the Service and all his children are stopped
Since the Statistics entity contains five time unit different, it delegates the task to five managers in charge to compute the statistics for every time unit. A worker is charge to compute the statistic for one interval and send it back to the manager. The manager collects them and, as soon as all workers have finished, send them to the service. The service collects all lists from the manager, create the Statistic entity and send it to the client. It also create a Insertion Service and send the Statistics entity to cache it in the database.
- The client sends a
ComputeStatisticsWithTimline(userID, timeline)message to the Computation Service - The service creates five managers with the interval type as attribute. He forwards the same message to each manager. (One manager will compute daily statistics, another will compute weekly statistics, ...)
- The manager extracts the number of time unit he has to compute in the
Timelineobject and creates for each of them a worker which is in charge to compute the statistics for one time unit. Finally he sends aComputeStatsOnInterval(userID, from, to)message to every worker (fromandtoare the beginning and the end of the interval i.e a day or a week). - The worker needs to compute the five sub-part of the statistics entity (
amount,won,lost, ...). For that, he creates five sub-workers to which he send aComputeSubStat(userID, SubStatType, from, to)message. - The sub-worker send back the computed statistic.
- The worker collects all statistics from sub-workers and send them back to the manager via a
ResponseStatOnInterval(stat)message. - The manager collects all statistics from workers and send them back to the Computation Service via different kind of messages (
DailyStats,weeklyStats,monthlyStats,...). - The Computation Service instantiates an Insertion Service and send the Statistics entity to it in order to insert it in the database.
- The Insertion Service returns an
Donemessage - The Computation Service sends the Statistics entity (
StatResponse(userID, freq, date)) to the client.
As the Insertion Service, the Retrieving Service delegates the task to a worker which query the database and return the data. In case of Statistics entity retrieving, if the entity isn't up-to-date, it instantiates a Computation Service and requests a new Computation.
The figure below shows a classic workflow in blue and the Statistics retrieving workflow in red.
- The client sends a
RetrieveStats(userID, frequences, allTime)message. - The Retrieving Service parses frequencies into
Timelineobject, send aRetrieveLastStatistic(userID)message to a new worker. - The worker queries the entity and send it back via a
StatisticsRetrieved(stats)message or send aStatisticsNotFoundmessage if no Statistic could be retrieved. - If the Service received out-dated Statistics (older than one day) or if no Statistics has been found, he instantiates a Computation Service and starts the computation with a
ComputeStatisticsWithTimline(userID, timeline)message. - The Computation Service sends back the freshly computed Statistics
StatResponse. - Finally the Retrieving Service sends the Statistics entity (from the database or the computation) to the client via a
StatisticsRetrieved(stats)message.
The queries use ReactiveMongo driver which encode queries into a BSONDocument representation. In order to simplify the queries, some fields and values are modified during the BSON serialization:
-
player1Scoreandplayer2Score: player1 and player2 are replaced by the actual userID. They become i.e1userID_Scorecalled userScoreBelow. -
player1Boardandplayer2Board: Same as above. -
wonBy: [int]: replace the number of the user by his userID (wonBy: "1userID")
As mentioned above, during the computation each sub-worker receives an interval boundaries from and to.
Since 1userID_Score (called userScore below) are contained only in the game play by this user, the query selects only the documents where this field name exists.
val query = BSONDocument(
"status" -> "ended",
userScore -> BSONDocument(
"$exists" -> true),
"creationTime" -> BSONDocument(
"$gte" -> from.getMillis,
"$lt" -> to.getMillis)
)
This query returns a list of matched Game entities. The Amount statistic is the size of the list.
val queryWon = BSONDocument(
"status" -> "ended",
userScore -> BSONDocument(
"$exists" -> true),
"wonBy" -> userID,
"creationTime" -> BSONDocument(
"$gte" -> from.getMillis,
"$lt" -> to.getMillis)
)
val queryLost = BSONDocument(
"status" -> "ended",
userScore -> BSONDocument(
"$exists" -> true),
"wonBy" -> BSONDocument("$ne" -> userID),
"creationTime" -> BSONDocument(
"$gte" -> from.getMillis,
"$lt" -> to.getMillis)
)
Same as above, the queries return a list of matched Game entities.
All games played by the users in the interval are retrieved with a similar query that the one for the amount.
All aggregations are done in the userspace in Scala.
Same as QuestionsBreakDown
Master Semester project, Audrey Loeffel, june 2016, EPFL Supervised by Michele Catasta, with the help of Roger Küng


