Yote

Code server side, use client side.

What is Yote

Yote is a web application server, a set of perl libraries and a set of javascript libraries.

It is designed for rapid development of web applications.

Yote Provides :

Yote Objects :


Get Yote on github : https://github.com/ewolf/Yote

Secret Collect

Let's make a trivial but real riddle game called 'Secret Collect'. In this game, you add riddles and try to answer the riddles of others.

You start the game by entering in 3 riddles. You answer others riddles to steal them away. If your riddles are stolen, you may enter to get to three. Your score is how many riddles you own.

The first step in creating this app is to sketch out a design and the elements needed.

Elements

Application

The application runs the game. It keeps track of players and questions. The players call methods on the application to play the game. Yote creates a singleton instance of the application mapped to its package name internally. This application will store a list of riddles that can be collected.

Players

Yote provides Account objects, which are the players in this case. Methods invoked on the application are invoked with Yote account objects, automatically passed to the called method. Accounts are created and accessed using the javascript calls $.yote.create_account and $.yote.login. Once logged in, the javascript in the browser keeps track of the login state and account identification. Each account has a container object per application. This is meant to store things like documents, games played in the app, messages sent and received or pictures stored. For this application, it stores a list of collected secrets.

Riddles

Riddles are the secrets to be collected in the game. They are basic yote objects containing a question and an encrypted version of the answer to prevent cheating.

Object Model

Application

Class : Yote::Sample::SecretCollect
Methods - Player data is automatically passed to the methods. Data

Player Data

Class : Yote::AccountRoot
Data

Riddle

Class : Yote::Obj
Data

Client Side Code

<html><head><title>Secret Collect</title>
<script src="/js/jquery-latest.js"></script>
<script src="/js/jquery.dumper.js"></script>
<script src="/js/jquery.base64.min.js"></script>
<script src="/js/json2.js"></script>
<script src="/js/yote.js"></script>
<script>
  $().ready(function(){ 
      var secret_collect_app = $.yote.fetch_app('Yote::Sample::SecretCollect');
      $('#button').click( function() {
          var result = hello_app.hello({ name:$('#txt').val() } );
          alert( result ); //get the message from running the hello method.
          alert( 'testfield is ' + hello_app.get_testfield() ); //get the value of testfield that is attached to the app
          var counter = hello_app.get_counter();                //get the counter object that is attached to the app
          alert( 'counter is at ' + counter.get_count() );      //get the value of the count field of the counter object attached to the app
      } );

  });
</script></head>
<body><h1>Secret Collect</h1>
</body></html>

Server Side Code

As long as the classes defined are in perl's classpath, they will be accessible. There is no need to start the yote server to load them. At the time of this writing if you make changes to the classes, yote must be restarted to see those changes.
package Yote::Sample::SecretCollect;

use base 'Yote::AppRoot';

use Crypt::Passwd;

sub add_riddle {

    my( $self,      # This singleton AppRoot object. 
                    # It lives in /apps/Yote::Sample::SecretCollect
                    # Calling 
                    #   var app = $.yote.fetch_app('Yote::Sample::SecretCollect');
                    #   on the client side will return only this instance.
        
        $data,      # The data structure sent by the client.
                    # This app is expecting app.add_riddle({question:'ques',answer:'ans'});
        $acct_root, # This is a container specific to the account calling add_riddle
                    # and the SecretCollect app. This is meant to store state data
                    # for the player that does not clash with state data they have
                    # for any other app.
        
        $acct       # The account object the user is logged in as. 
                    # It is created by calling 
                    #   $.yote.create_account( {} );
        ) = @_;

    #
    # Create a new riddle object and add it to the account root's riddle supply.
    # encrypt the riddle to hide its answer.
    #
    # The riddle methods 'set_question', 'set_secret_answer', 'set_owner'
    #    are automatically there and need no definition.
    # The account root
    #
    my $riddle = new Yote::Obj();
    $riddle->set_question( $data->{question} );
    $riddle->set_secret_answer( unix_std_crypt( $data->{answer}, 
                                                $data->{question} ) );
    $riddle->set_owner( $acct_root );
    $acct_root->add_to_my_riddles( $riddle );

    #
    # add the riddle to all riddles the app has
    #
    $self->add_to_riddles( $riddle );
    $self->set_riddle_count( 1 + $self->get_riddle_count() );

    return { msg => 'riddle added' };

} #add_riddle

sub can_start {

    my( $self, $data, $acct_root, $acct ) = @_;

    # need 3 riddles to start guessing
    return { r => @{ $acct_root->get_riddles( [] ) } > 0 };
}


sub random_riddle {

    my( $self, $data, $acct_root, $acct ) = @_;

    unless( $self->can_start( $data, $acct_root, $acct ) ) {
        return { err => 'Must have 3 riddles to guess others' };
    }

    my $riddle_count = $self->get_riddle_count();

    if( $riddle_count == 0 ) {
        return { err => 'there are no riddles to guess' };
    }

    #
    # Pick the riddle without having to load in the whole riddle array :
    #
    my $riddle_idx = int( rand( $riddle_count ) );
    my $riddle = $self->_xpath( "/riddles/$riddle_idx" );

    return { riddle => $riddle };

} #random_riddle



sub guess_riddle {

    my( $self, $data, $acct_root, $acct ) = @_;

    my $riddle = $data->{riddle};
    my $answer = $data->{answer};

    my $riddle_owner = $riddle->get_owner();

    #
    # Collect stats on the riddle. They can be accessed on the client side
    #   by calling riddle.get_guesses();
    # Don't bother incrementing for one's own riddle.
    #
    if( ! $riddle_owner->is( $acct_root ) ) {
        $riddle->set_guesses( 1 + $riddle->get_guesses() );
        $acct_root->set_guesses( 1 + $acct_root->get_guesses() );
    }

    if( $riddle->get_secret_answer() eq unix_std_crypt( $answer, $riddle->get_question() ) ) {
        #
        # A secret collect! Change ownership and update the stats.
        #
        if( ! $riddle_owner->is( $acct_root ) ) {
            $acct_root->set_collected_count( 1 + $acct_root->get_collected_count() );
            $riddle->set_collect_count( 1 + $riddle->get_collect_count() );

            $riddle_owner->remove_from_my_riddles( $riddle );
            $acct_root->add_to_my_riddles( $riddle );
            $riddle->set_owner( $acct_root );
        }
        return { msg => 'You collected this riddle' };		     
    } 
    else {
        return { msg => 'You got the wrong answer' };
    }
} #guess_riddle


1;

Client Side Code

<html><head><title>Hello World</title>
<script src="/js/jquery-latest.js"></script>
<script src="/js/jquery.dumper.js"></script>
<script src="/js/jquery.base64.min.js"></script>
<script src="/js/json2.js"></script>
<script src="/js/yote.js"></script>
<script>
  $().ready(function(){ 
      var hello_app = $.yote.fetch_app('Yote::Hello');
      $('#button').click( function() {
          var result = hello_app.hello({ name:$('#txt').val() } );
          alert( result ); //get the message from running the hello method.
          alert( 'testfield is ' + hello_app.get_testfield() ); //get the value of testfield that is attached to the app
          var counter = hello_app.get_counter();                //get the counter object that is attached to the app
          alert( 'counter is at ' + counter.get_count() );      //get the value of the count field of the counter object attached to the app
      } );

  });
</script></head>
<body><h1>Hello World</h1>
<input type=text id=txt><BR><button type=button id=button>Say Hi</button>
</body></html>
    

Server Side Code

package Yote::Hello;

use strict;

use Yote::Obj;

use base 'Yote::AppRoot';

sub init {
    my $self = shift;
    #when the hello is created for the first time, install a counter to track how many times it is called
    $self->set_counter( new Yote::Obj() );  
}

sub hello {
    my( $self, $data, $acct ) = @_;
    my $name = $data->{name};
    $self->set_testfield(int(rand(10)); # set this to a random value each time
    my $counter = $self->get_counter(); # this could be counted with a field, but I wanted to demo how easy it is to send objects across.
    $counter->set_count( $counter->get_count() + 1 ); #increment the value in the counter
    return { r => "hello there '$name'. I have said hello ".$counter->get_count()." times." };
}

1;
    

This page has been viewed times.