Pieter Heyvaert Publications PhD Blog Contact More

How a chess app interacts with Solid

2019-03-30

Last year I started playing around with the Solid platform, introduced by Tim Berners-Lee. In order to get more familiar with the different concepts and technologies used, I created a proof-of-concept app: a browser chess game.

The main concept of the Solid platform is the use of Solid PODs, which provide personal online data storage. The idea of Solid is that a user has one or more PODs to store their data and different apps are able to interact with these PODs, but these apps can only read and write the data to which the user has granted them access. So, instead of having to store all data of all users on a single server per app, users now store and control (!) their own data, leading to a decentralized approach. The focus of this blog post is on the interaction between the app and the Solid PODs.

First, we list the different features of the chess app. Second, we elaborate on the three high-level components of the app. Third, we list the different steps that are taken when certain actions are done by a player. Finally, we discuss how the data generated by this app can be used by other apps.

Features

The app provides the following features:

  • authenticate a player
  • start a new chess game and invite your opponent
  • join a chess game you are invited to
  • do moves in a chess game
  • continue an existing chess game

High-level components

The app consists of three high-level components: the GUI, the chess game engine, and the interaction with the POD. The GUI uses the chessboard.js library, which offers the chessboard and the interaction of the players with the board, such as the moving of the pieces. It does not provide a chess engine, i.e., it will not check whether the moves are valid or not and keep track of which player's turn it is. To accomplish this we make use of the chess.js library. Although it provides the required information to play a chess game, the data is not presented as Linked Data, which is preferred by the Solid platform. Linked Data is a method of publishing structured data so that it can be interlinked and become more useful through semantic queries. Therefore, I created a "wrapper" library around chess.js called semantic-chess that outputs RDF, a technology used to materialize Linked Data, which can be used during the interaction of the app with the POD. The focus of this blog post is on this interaction.

Actions

Authentication

When the app is launched, one of the first actions a player does is logging in. For this we use the solid-auth-client library. It keeps track of the session and provides a fetch method that is used whenever we want to interact with the POD. Whenever a player is logged in the fetch method is able to store and read data from PODs to which the logged-in player has access.

login popup window

Figure 1: pop-up window that appears when clicking the "Log in" button in the app.

In the GUI there is a classic log-in button. When clicked, a pop-up appears that allows selecting your identify provider to which you want to authenticate (see Figure 1). You can provide your own HTML file to render the pop-up or you can use the default one provided by the library.
After successfully logging in, the pop-up is closed and the focus is back on the app.

The solid-auth-client library has tracked the fact that a player has logged in and now a session object is available. This session object contains, for example, the Web ID of the logged in player. A Web ID is an HTTP URI that denotes an agent on an HTTP-based network. In line with the Linked Data principles, when a Web ID is de-referenced, it resolves to a profile document that describes its referent, i.e., the player. Furthermore, the fetch method provided by the library will now act on behalf of the player.

Start new game

When a player starts a new game he needs to select an opponent, the app needs to store the game data on the player's POD, and invite the opponent.

Select opponent

Select opponent sequence diagram

Figure 2: sequence diagram of the steps taken when a player selects an opponent.

With this action, the player selects an opponent for a new game. In Figure 2, we describe the corresponding steps between the player, the player's app, the player's POD, and the PODs of his friends. The steps are:

  1. The app does a GET to the player's Web ID. If the Web ID of the player is

    https://player.solid.community/profile/card#me
    
    copy success

    then the corresponding curl command is

    curl https://player.solid.community/profile/card#me
    
    copy success
  2. The player's POD returns RDF describing the player. An example of such RDF is

    @prefix : <https://player.solid.community/profile/card#>.
    @prefix inbox: <https://player.solid.community/inbox/>.
    @prefix ldp: <http://www.w3.org/ns/ldp#>.
    @prefix foaf: <http://xmlns.com/foaf/0.1/>.
    @prefix c0: <https://opponent.solid.community/profile/card#>.
    
    :me ldp:inbox inbox:;
        foaf:knows c0:me;
        foaf:name "Player".
    
    copy success
  3. The app queries the RDF to determine the player's friends, via the predicate foaf:knows. An example of SPARQL query using this predicate is

    PREFIX foaf: <http://xmlns.com/foaf/0.1/>
    PREFIX player: <https://player.solid.community/profile/card#>
    
    SELECT ?friend 
    WHERE {
      player:me foaf:knows ?friend.
    } 
    
    copy success

    For every friend the following three steps are taken.

  4. The app does a GET to the friend's Web ID. If the Web ID of the player is

    https://opponent.solid.community/profile/card#me
    
    copy success

    then the corresponding curl command is

    curl https://opponent.solid.community/profile/card#me
    
    copy success
  5. The friend's POD returns RDF describing the friend. This RDF is similar to the RDF of step 2. An example of such RDF is

    @prefix : <https://opponent.solid.community/profile/card#>.
    @prefix inbox: <https://opponent.solid.community/inbox/>.
    @prefix ldp: <http://www.w3.org/ns/ldp#>.
    @prefix foaf: <http://xmlns.com/foaf/0.1/>.
    @prefix player: <https://player.solid.community/profile/card#>.
    
    :me ldp:inbox inbox:;
        foaf:knows player:me;
        foaf:name "Opponent".
    
    copy success
  6. The app queries the RDF to determine the friend's name, via the predicate foaf:name. An example of SPARQL query using this predicate for an friend with the Web ID

    https://opponent.solid.community/profile/card#me
    
    copy success

    is

    PREFIX foaf: <http://xmlns.com/foaf/0.1/>
    PREFIX opponent: <https://opponent.solid.community/profile/card#>
    
    SELECT ?name 
    WHERE {
      opponent:me foaf:name ?name.
    } 
    
    copy success
  7. The app shows a list of friends to the player.

  8. The player selects the desired friend as his opponent.

Remarks

  • SPARQL is used to query the RDF to find friends and their names, because it is the default query language for RDF. However, alternatives such as LDFlex and GraphQL-LD are also available.
  • The FOAF ontology, with prefix foaf, is one of several ontologies, such as Schema.org and vCard, that can be used to describe the basic information of a person. At the time of writing, when creating a default profile with Solid mostly FOAF is used. However, bear in mind that other Solid PODs might use (a combination of) other ontologies.
  • We only store the Web IDs of a player's friends on the player's POD, because additional information about a friend can be acquired by downloading data from the friend's POD.

Store game data on POD

Store game data sequence diagram

Figure 3: sequence diagram of the steps taken when storing game data on the POD.

With this action, the game data of a new game is stored on the player's POD. In Figure 3, we describe the corresponding steps between the player, the player's app, and the player's POD. The steps are:

  1. The player starts the game.

  2. The player's app generates RDF that describing this specific instance of a chess game. An example of such RDF is

    @prefix : <https://player.solid.community/public/chess.ttl#>.
    @prefix chess: <http://purl.org/NET/rdfchess/ontology/>.
    @prefix stor: <http://example.org/storage/>.
    @prefix schema: <http://schema.org/>.
    @prefix player: <https://player.solid.community/profile/card#>.
    @prefix opponent: <https://opponent.solid.community/profile/card#>.
    
    :game
      a chess:ChessGame;
      stor:storeIn :;
      chess:providesAgentRole :jo8deywv, :jo8deyww;
      chess:starts :jo8deywv;
      schema:name "Test game".
        
    :jo8deywv a chess:WhitePlayerRole;
      chess:performedBy player:me.
      
    :jo8deyww a chess:BlackPlayerRole;
      chess:performedBy opponent:me.
    
    copy success

    It includes

    • the unique URL of the game: uniquely identifies this specific chess game on the Web
    • the name of the game: displayed in the app
    • the Web IDs of the two players: uniquely identifies the players of this specific chess game on the Web
    • the color of each player
  3. The player's app does a PATCH with a SPARQL UPDATE query to the player's POD with the RDF. A PATCH is used because the file where we want to store the RDF, chosen by the player, might already contain data, which we do not want to overwrite. An example of such a PATCH is

    PREFIX : <https://player.solid.community/public/chess.ttl#>
    PREFIX chess: <http://purl.org/NET/rdfchess/ontology/>
    PREFIX stor: <http://example.org/storage/>
    PREFIX schema: <http://schema.org/>
    PREFIX player: <https://player.solid.community/profile/card#>
    PREFIX opponent: <https://opponent.solid.community/profile/card#>
       
    INSERT DATA {
      :game
        a chess:ChessGame;
        stor:storeIn <>;
        chess:providesAgentRole :jo8deywv, :jo8deyww;
        chess:starts :jo8deywv;
        schema:name "Test game".
            
      :jo8deywv a chess:WhitePlayerRole; 
        chess:performedBy player:me.
        
      :jo8deyww a chess:BlackPlayerRole; 
        chess:performedBy opponent:me.
    }
    
    copy success

Invite opponent

Invite opponent sequence diagram

Figure 4: sequence diagram of the steps taken when an opponent is invited.

With this action, an invitation is sent to the opponent to join the newly created game. In Figure 4, we describe the corresponding steps between the player's app and the opponent's POD. The steps are:

  1. The player's app generates RDF describing the invitation. An example of such RDF is

    @prefix : <https://player.solid.community/public/chess.ttl#>.
    @prefix schema: <http://schema.org/>.
    @prefix player: <https://player.solid.community/profile/card#>.
    @prefix opponent: <https://opponent.solid.community/profile/card#>.
    
    :invitation a schema:InviteAction>;
      schema:event :game;
      schema:agent player:me;
      schema:recipient opponent:me.
    
    copy success
  2. The player's app does PATCH with a SPARQL UPDATE query to store the invitation on the player's POD. An example of such a PATCH is

    PREFIX schema: http://schema.org/>
    PREFIX : <https://player.solid.community/public/chess.ttl#>
    PREFIX player: <https://player.solid.community/profile/card#>
    PREFIX opponent: <https://opponent.solid.community/profile/card#>
    
    INSERT DATA {
      :invitation a schema:InviteAction;
        schema:event :game;
        schema:agent player:me;
        schema:recipient opponent:me.
    }
    
    copy success
  3. The player's app generates RDF for a notification of the invitation. An RDF example of such an notification is

    @prefix schema: <http://schema.org/> .
    @prefix : <https://player.solid.community/public/chess.ttl#>.
        
    :invitation a schema:InviteAction .
    
    copy success
  4. The player's app does a GET to the Web ID of the opponent. If the Web ID of the player is

    https://player.solid.community/profile/card#me
    
    copy success

    then the corresponding curl command is

    curl https://player.solid.community/profile/card#me
    
    copy success
  5. The opponent's POD returns RDF describing the opponent. An example of such RDF is

    @prefix : <https://opponent.solid.community/profile/card#>.
    @prefix inbox: <https://opponent.solid.community/inbox/>.
    @prefix ldp: <http://www.w3.org/ns/ldp#>.
    @prefix foaf: <http://xmlns.com/foaf/0.1/>.
    @prefix c0: <https://player.solid.community/profile/card#>.
    
    :me ldp:inbox inbox:;
        foaf:knows c0:me;
        foaf:name "Opponent".
    
    copy success
  6. The player's app queries the RDF to determine the opponent's inbox via the predicate ldp:inbox. An example of a SPARQL query using this predicate is

    PREFIX ldp: <http://www.w3.org/ns/ldp#>
    
    SELECT ?inbox
    WHERE {
      <https://opponent.solid.community/profile/card#> ldp:inbox ?inbox.
    }
    
    copy success
  7. The player's app does a POST to the inbox with the notification of the invitation. When we do a POST to an inbox a new file, containing the notification, is created in the inbox.

Join game

Join game sequence diagram

Figure 5: sequence diagram of the steps taken when an opponent joins a game.

With this action, the opponent joins the game for which he received an invitation. In Figure 5, we describe the corresponding steps between the player, the opponent, the player's app, the player's POD, the opponent's app, and the opponent's POD. The steps are:

  1. The opponent's app does a GET to the Web ID of the the logged-in user, which in this case is the opponent.

  2. The opponent's POD returns RDF describing the opponent. An example of such RDF is

    @prefix : <https://opponent.solid.community/profile/card#>.
    @prefix inbox: <https://opponent.solid.community/inbox/>.
    @prefix ldp: <http://www.w3.org/ns/ldp#>.
    @prefix foaf: <http://xmlns.com/foaf/0.1/>.
    @prefix player: <https://player.solid.community/profile/card#>.
    
    :me ldp:inbox inbox:;
        foaf:knows player:me;
        foaf:name "Opponent".
    
    copy success
  3. The opponent's app queries the RDF to determine the opponent's inbox via the predicate ldp:inbox. An example of a SPARQL query using this predicate is

    PREFIX ldp: <http://www.w3.org/ns/ldp#>
    PREFIX opponent: <https://opponent.solid.community/profile/card#>
    
    SELECT ?inbox
    WHERE {
      opponent:me ldp:inbox ?inbox.
    }
    
    copy success
  4. The opponent's app does a GET to the inbox, which contains links that identify the different notifications.

  5. The opponent's POD returns RDF describing the inbox. An example of such RDF is

    @prefix inbox: <https://opponent.solid.community/inbox/>.
    @prefix ldp: <http://www.w3.org/ns/ldp#>.
    @prefix terms: <http://purl.org/dc/terms/>.
    @prefix XML: <http://www.w3.org/2001/XMLSchema#>.
    @prefix st: <http://www.w3.org/ns/posix/stat#>.
    
    inbox:
        a ldp:BasicContainer, ldp:Container;
        terms:modified "2019-01-10T15:00:26Z"^^XML:dateTime;
        ldp:contains inbox:765a0510-14e8-11e9-a29e-5d8e3e616ac9, n0:;
        st:mtime 1547132426.207;
        st:size 4096.
        
    inbox:765a0510-14e8-11e9-a29e-5d8e3e616ac9
        a ldp:Resource;
        terms:modified "2019-01-10T15:00:26Z"^^XML:dateTime;
        st:mtime 1547132426.207;
        st:size 1369.
    
    copy success
  6. The opponent's app queries the RDF to determine the notifications, i.e., their links, via the class ldp:Resource. An example of a SPARQL query using this predicate is

    PREFIX ldp: <http://www.w3.org/ns/ldp#>
    
    SELECT ?notification
    WHERE {
      ?notification a ldp:Resource .
    }
    
    copy success

    The opponent's app iterates over all notifications in the inbox.

  7. The opponent's app does a GET to the link of the notification.

  8. The opponent's POD returns RDF describing the notification. An example of such RDF is

    @prefix schema: <http://schema.org/> .
    @prefix : <https://player.solid.community/public/chess.ttl#>.
        
    :invitation a schema:InviteAction .
    
    copy success
  9. The app queries the RDF to determine if the notification contains an invitation, via the class schema:InviteAction. An example of a corresponding SPARQL query is

    PREFIX schema: <http://schema.org/>
    
    SELECT ?invitation
    WHERE {
      ?invitation a schema:InviteAction .
    }
    
    copy success
  10. If the notification contains an invitation, the opponent's app does a GET to the link of the invitation.

  11. The player's POD returns RDF describing the invitation. An example of such RDF is

    @prefix : <https://player.solid.community/public/chess.ttl#>.
    @prefix schema: <http://schema.org/>.
    @prefix player: <https://player.solid.community/profile/card#>.
    @prefix opponent: <https://opponent.solid.community/profile/card#>.
    
    :invitation a schema:InviteAction;
      schema:event :game;
      schema:agent player:me;
      schema:recipient opponent:me.
    
    copy success
  12. The opponent's app queries the RDF to determine the link of the game and the Web ID of the opponent. A corresponding SPARQL query is

    PREFIX : <https://player.solid.community/public/chess.ttl#>
    PREFIX schema: <http://schema.org/>   
    
    SELECT ?game ?opponent
    WHERE {
      :invitation schema:event ?game;
        schema:agent ?opponent.
    }
    
    copy success
  13. The opponent's app shows the invitation to the opponent.

  14. The opponent accepts the invitation.

  15. The opponent's app generates RDF describing the response. An example of such RDF is

    @prefix response: <https://opponent.solid.community/public/chess.ttl#response>.
    @prefix invitation: <https://player.solid.community/public/chess.ttl#invitation>.
    @prefix schema: <http://schema.org/>.
    @prefix player: <https://player.solid.community/profile/card#>.
    @prefix opponent: <https://opponent.solid.community/profile/card#>.
    
    response: a schema:RsvpAction;
      schema:rsvpResponse schema:RsvpResponseYes;
      schema:agent opponent:me;
      schema:recipient player:me.
          
    invitation: schema:result response:.
    
    copy success
  16. The opponent's app does PATCH with a SPARQL UPDATE query to store the response on the opponent's POD. An example of such a PATCH is

    PREFIX response: <https://opponent.solid.community/public/chess.ttl#response>
    PREFIX invitation: <https://player.solid.community/public/chess.ttl#invitation>
    PREFIX schema: <http://schema.org/>
    PREFIX player: <https://player.solid.community/profile/card#>
    PREFIX opponent: <https://opponent.solid.community/profile/card#>
    
    INSERT DATA {
      response: a schema:RsvpAction;
        schema:rsvpResponse schema:RsvpResponseYes;
        schema:agent opponent:me;
        schema:recipient player:me.
                
      invitation: schema:result response:.
    }
    
    copy success
  17. The player's app generates RDF for a notification of the response. An RDF example of such an notification is

    @prefix response: <https://opponent.solid.community/public/chess.ttl#response>.
    @prefix schema: <http://schema.org/>.
    
    response: a schema:RsvpAction.
    
    copy success
  18. The opponent's app does a PATCH to the opponent's POD to store the relevant game data. This is mostly data to know later that the opponent is participating in the game. Details about the game are not stored on the opponent's POD, because they are stored on the player's POD and are retrievable by doing a GET to the link of the game.

  19. The opponent's app does a GET to the Web ID of the player. This Web ID is available through the invitation.

  20. The player's POD returns RDF describing the player. An example of such RDF is

    @prefix : <https://player.solid.community/profile/card#>.
    @prefix inbox: <https://player.solid.community/inbox/>.
    @prefix ldp: <http://www.w3.org/ns/ldp#>.
    @prefix foaf: <http://xmlns.com/foaf/0.1/>.
    @prefix opponent: <https://opponent.solid.community/profile/card#>.
    
    :me ldp:inbox inbox:;
        foaf:knows opponent:me;
        foaf:name "Player".
    
    copy success
  21. The app queries the RDF to determine the player's inbox via the predicate ldp:inbox.

  22. The app does a POST to the player's inbox with notification of the response.

  23. The player's app does a GET to the notification. For clarity we skip the iteration over the different notifications, because this is the same as earlier.

  24. The player's POD returns RDF describing the notification. An example of such RDF is

    @prefix response: <https://opponent.solid.community/public/chess.ttl#response>.
    @prefix schema: <http://schema.org/>.
    
    response: a schema:RsvpAction.
    
    copy success
  25. The player's app queries the RDF to determine if the notification contains a response, via the class schema:RsvpAction.

  26. If the notification contains a response, the app does a GET to the link of the response.

  27. The opponent's POD returns RDF describing the response. An example of such RDF is

    @prefix response: <https://opponent.solid.community/public/chess.ttl#response>.
    @prefix invitation: <https://player.solid.community/public/chess.ttl#invitation>.
    @prefix schema: <http://schema.org/>.
    @prefix player: <https://player.solid.community/profile/card#>.
    @prefix opponent: <https://opponent.solid.community/profile/card#>.
        
    response: a schema:RsvpAction;
      schema:rsvpResponse schema:RsvpResponseYes;
      schema:agent opponent:me;
      schema:recipient player:me.
            
    invitation: schema:result response:.
    
    copy success
  28. The player's app queries the response to determine the corresponding invitation, from which the corresponding game can be determined. An example of a corresponding SPARQL query is

    PREFIX schema: <http://schema.org/>
    
    SELECT ?invitation 
    WHERE {
      response: schema:result ?invitation. 
    }
    
    copy success
  29. The player's app shows the response to the player.

Do move

Do move sequence diagram

Figure 6: sequence diagram of the steps taken when a player does a move.

With this action, the player does a new move, which is shown to the opponent. In Figure 6, we describe the corresponding steps between the player, the player's app, the player's POD, the opponent's app, and the opponent's POD. The steps are:

  1. The player does a move.

  2. The player's app generates RDF that describes this move. An example of such RDF is

    @prefix : <https://player.solid.community/public/chess.ttl#>.
    @prefix schema: <http://schema.org/>.
    @prefix chess: <http://purl.org/NET/rdfchess/ontology/>.
    @prefix opponent: <https://opponent.solid.community/public/chess.ttl#>.
    
    :game chess:hasHalfMove :move2.
    
    :move2 a chess:HalfMove;
      schema:subEvent :game;
      chess:hasSANRecord "e3"^^xsd:string;
      chess:resultingPosition "rnbqkbnr/pppp1ppp/4p3/8/8/4P3/PPPP1PPP/RNBQKBNR w KQkq -".
    
    opponent:move1 chess:nextHalfMove :move2.
    
    copy success

    where

    https://player.solid.community/public/chess.ttl#move2
    
    copy success

    is the link of the new move and

    https://opponent.solid.community/public/chess.ttl#move1
    
    copy success

    is the link of the previous move.

  3. The player's app does a PATCH to the player's POD to store the move.

  4. The player's app generates a notification for the move. An example of such RDF is

    @prefix : <https://player.solid.community/public/chess.ttl#>.
    @prefix opponent: <https://opponent.solid.community/public/chess.ttl#>.
        
    opponent:move1 chess:nextHalfMove :move2.
    
    copy success
  5. The player's app does a POST to the opponent's POD with this notification.

  6. The opponent's app does a GET to the link of the notification.

  7. The opponent's POD returns RDF describing the move. An example of such RDF is

    @prefix : <https://player.solid.community/public/chess.ttl#>.
    @prefix opponent: <https://opponent.solid.community/public/chess.ttl#>.
    
    opponent:move1 chess:nextHalfMove :move2.
    
    copy success
  8. The opponent's app queries the RDF to determine if the notification contains a move.

  9. The opponent's app does a GET to the link of the move.

  10. The player's POD returns RDF describing the move. An example of such RDF is

    @prefix : <https://player.solid.community/public/chess.ttl#>.
    @prefix schema: <http://schema.org/>.
    @prefix chess: <http://purl.org/NET/rdfchess/ontology/>.
            
    :move2 a chess:HalfMove;
      schema:subEvent :game;
      chess:hasSANRecord "e3"^^xsd:string;
      chess:resultingPosition "rnbqkbnr/pppp1ppp/4p3/8/8/4P3/PPPP1PPP/RNBQKBNR w KQkq -".
    
    copy success
  11. The opponent's app queries the RDF to determine the details of the move. A corresponding SPARQL query is

    PREFIX : <https://player.solid.community/public/chess.ttl#>
    PREFIX schema: <http://schema.org/>
    PREFIX chess: <http://purl.org/NET/rdfchess/ontology/>
        
    SELECT ?game ?san
    WHERE {
      :move2 schema:subEvent ?game;
        chess:hasSANRecord ?san.
    }
    
    copy success
  12. The opponent's app shows the new move to the opponent.

  13. The opponent's app stores the link of the new move on the opponent's POD. This is done to reconstruct the chess game based on the sequence of individual moves.

Continue game

Continue game sequence diagram

Figure 7: sequence diagram of the steps taken when a player continues a game.

With this action, the player continues a game that he started before. Chess does not have to be played real time, i.e., it is possible that a player does a move in the morning, but that the opponent does the next move only in the evening. Therefore, players should be able to continue a game at any point in time. In Figure 7, we describe the corresponding steps between the player, the player's app, the player's POD, and the opponent's POD. The steps are:

  1. The player's app does a GET to the player's Web ID.

  2. The player's POD returns RDF describing the player. An example of such RDF is

    @prefix : <https://player.solid.community/profile/card#>.
    @prefix inbox: <https://player.solid.community/inbox/>.
    @prefix ldp: <http://www.w3.org/ns/ldp#>.
    @prefix foaf: <http://xmlns.com/foaf/0.1/>.
    @prefix c0: <https://opponent.solid.community/profile/card#>.
    
    :me ldp:inbox inbox:;
        foaf:knows c0:me;
        foaf:name "Player".
    
    copy success
  3. The player's app queries the RDF to determine the chess games in which the player participates. The app iterates over all games.

  4. The player's app does a GET to the link of each game.

  5. The player's POD returns RDF describing the game. An example of such RDF is

    @prefix : <https://player.solid.community/public/chess.ttl#>.
    @prefix chess: <http://purl.org/NET/rdfchess/ontology/>.
    @prefix stor: <http://example.org/storage/>.
    @prefix schema: <http://schema.org/>.
    @prefix player: <https://player.solid.community/profile/card#>.
    @prefix opponent: <https://opponent.solid.community/profile/card#>.
    
    :game
     a chess:ChessGame;
     stor:storeIn :;
     chess:providesAgentRole :jo8deywv, :jo8deyww;
     chess:starts :jo8deywv;
     schema:name "Test game".
       
    :jo8deywv a chess:WhitePlayerRole;
     chess:performedBy player:me.
     
    :jo8deyww a chess:BlackPlayerRole; 
     chess:performedBy opponent:me.
    
    copy success
  6. The player's app queries the RDF to determine the name of the game and the opponent's Web ID. An example of a corresponding SPARQL query is

    PREFIX : <https://player.solid.community/public/chess.ttl#>
    PREFIX chess: <http://purl.org/NET/rdfchess/ontology/>
    PREFIX schema: <http://schema.org>
    PREFIX player: <https://player.solid.community/profile/card#>
    
    SELECT ?name ?opponent
    WHERE {
      :game schema:name ?name;
        chess:providesAgentRole ?role.
        
      ?role chess:performedBy ?opponent.
        
      MINUS {?role chess:performedBy player:me}
    }
    
    copy success
  7. The player's app does a GET to the opponent's Web ID.

  8. The opponent's POD returns RDF describing the opponent. An example of such RDF is

    @prefix : <https://opponent.solid.community/profile/card#>.
    @prefix inbox: <https://opponent.solid.community/inbox/>.
    @prefix ldp: <http://www.w3.org/ns/ldp#>.
    @prefix foaf: <http://xmlns.com/foaf/0.1/>.
    @prefix player: <https://player.solid.community/profile/card#>.
    
    :me ldp:inbox inbox:;
      foaf:knows player:me;
      foaf:name "Opponent".
    
    copy success
  9. The player's app queries the RDF to determine the name of the player via the predicate foaf:name. An example of SPARQL query using this predicate for an friend with the Web ID

    https://opponent.solid.community/profile/card#me
    
    copy success

    is

    PREFIX foaf: <http://xmlns.com/foaf/0.1/>
    PREFIX opponent: <https://opponent.solid.community/profile/card#>
    
    SELECT ?name 
    WHERE {
      opponent:me foaf:name ?name.
    } 
    
    copy success
  10. The player selects the game he wants to continue.

  11. The player's app iterates over all moves of the selected game.

  12. If the move is from the player then a GET to the link of the move by the player's app goes to the player's POD.

  13. The player's POD returns RDF describing the move. An example of such RDF is

    @prefix : <https://player.solid.community/public/chess.ttl#>.
    @prefix chess: <http://purl.org/NET/rdfchess/ontology/>.
    @prefix schema: <http://schema.org>.
    
    :move2 a chess:HalfMove;
      schema:subEvent :game;
      chess:hasSANRecord "e3"^^xsd:string;
      chess:resultingPosition "rnbqkbnr/pppp1ppp/4p3/8/8/4P3/PPPP1PPP/RNBQKBNR w KQkq -".
    
    copy success
  14. If the move is from the opponent then a GET to the link of the move by the player's app goes to the opponent's POD.

  15. The opponent's POD returns RDF describing the move. An example of such RDF is

    @prefix : <https://opponent.solid.community/public/chess.ttl#>.
    @prefix player: <https://opponent.solid.community/public/chess.ttl#>.
    @prefix chess: <http://purl.org/NET/rdfchess/ontology/>.
    @prefix schema: <http://schema.org>.
    
    :move1 a chess:HalfMove;
      schema:subEvent :game;
      chess:hasSANRecord "e6"^^xsd:string;
      chess:resultingPosition "rnbqkbnr/pppp1ppp/4p3/8/8/8/PPPPPPPP/RNBQKBNR w KQkq -";
      chess:nextHalfMove player:move2.
    
    copy success
  16. The player's app adds the move to the instance of the game.

  17. The player's app shows the game to the player.

Beyond this app

The data generated by this app is not tied to this specific app, because the data is materialized as RDF and follows the Linked Data principles. Furthermore, it is stored in the PODs of the players, which the players themselves control. As a result, the data can be used by other apps, completely independent of the app described in this blog post. Examples of such other apps are:

  • other chess apps which can continue an existing game
  • leaderboard apps that list the best players
  • artificial intelligence apps that analyze the moves of a player with the goal to provide suggestions to the player as how to improve their skills
----

If you have any questions or remarks, don’t hesitate to contact me via email or via Twitter.