A simple number-guess-game example

Just to give you a feeling of Jucas I want to show you how to implement a simple number-guess game. The user has to guess a number between 0 and 9. The number 5 is the right one. The game also keeps track of the number of guesses taken by the user.

The example consists of two files: (1) a Java class which defines the JucasBean used as Model and Controller (NumberGuess.java) and (2) a freemarker template which renders the JucasBean. Note: this two files are everything you need to implement the example (except of the standard Jucas installation). Especially there are no extra xml-configurations or properties-files to be set etc.

A JucasBean handling the Model and Controller

The JucasBean processes the number inputed from the user and decides if the guess was right or wrong. The bean also keeps (in Session Scope) the number of guesses the user has taken. Note the Bean itself is held in Request scope and each user will have it's own bean instance.

package org.jucas.examples.numberguess;

import org.jucas.BasePeer;
import org.jucas.Scope;
import org.jucas.StorageValue;


public class GuessBean extends BasePeer{

private StorageValue guesses;//hold count of guesses in Session Scope
private String input = null;//property set through the request
private int inputInt = -1;//the guessed number

//pubilc no argument constructor
public GuessBean(){
   super();
}

//overwritten from JucasBean: called when the bean is first loaded by the BeanManager
public void initializeJucasOnly() throws Exception{
   //make the StorageValue for the guesscount in session and initialize it to null
   guesses = this.getBeanHelper().getStorageValue("guesses",Scope.SESSION);
}

//bean specific: property which will be set through an html form
public void setInputNet(String inputS){ 
   this.input = inputS;
   this.inputInt = -1;
   try{ inputInt = Integer.parseInt(inputS);}catch(NumberFormatException e){}
   this.guesses.set(this.guesses.getInt(0)+1); 
}

public String getInput(){return this.input;}

//bean specific: property if input is ok
public boolean isInputOk(){  
   if(inputInt < 0 || inputInt > 9) return false;
   return true;
}

//bean specific: if rigth guess
public boolean isRigthGuess(){
   return this.inputInt == 5;
}

//bean specific: the number of guesses
public int getGuesses(){
   return guesses.getInt(0);
}

//called through request to clear the guesses
public void clearGuessesNet(){  
   this.guesses.set(0);
}

} //end

The class extends BasePeer. A Peer (like BasePeer) is a JucasBean on which properties can be set and actions can be called during the request. The Bean also has a default constructor (public no arguments). This is important so that it can be loaded by Jucas.

Properties which can be set through request-parameters must end with Net and take a String value (they may be indexed and mapped). In the example the only such property is setInputNet(). This property will take the guess the user took. The method remembers the users guess (as an int and as a String - for the case of wrong input) and increases the counter of guesses.

Methods (also called actions) which may be called through a request must also end with Net and must either take no arguments or any number of arguments of String-Array (ie methodNet(String[] arg1,String[] arg2,String[] arg3) ). In this example the only method is clearGuessesNet(). It will be used if the counter of guesses should be reset.

The method initializeJucasOnly() is called after the JucasBean has been loaded by Jucas. It is used to do some initialization. The method uses the BeanHelper to create an instance of StorageValue. The BeanHelper is given to the JucasBean by Jucas when the Bean is loaded. The BeanHelper provides various services to the JucasBean. One service is the possibility to store values encapsulated in Request or Session scope through a StorageValue instance. Encapsulated means that no other JucasBean (also of the same class) can access or modify the values (some kind like a private field in Session or Request).

For the construction of the StorageValue a name is provided and the Scope (Scope.REQUEST or Scope.SESSION) is given. The name must only be unique within the same Bean. The guesses StorageValue is used to hold the count of guesses taken by the user.

All other methods and properties are normal JavaBean properties (nothing special).

The View template renders the JucasBean and defines the properties to be set

The view is a single freemarker template. Please note that while for this example all the rendering has been put in one template (for size reasons) the template could be split up in anyway.

FreeMarker is used because I just think freemarker is the better jsp (Dont't worry if you don't know freemarker you will understand it right away if you know Java ;-)). Freemarker can do everything needed for the view jsp does (it can use jsp tag-libs as well) and more. Jucas also ships with freemarker. However freemarker is only there in Jucas so that JucasBeans can rely on a template-machine to be there. It is not used or referenced by Jucas in any way and JucasBeans do not need to use it. The reason that we do not use JSP (as standard) is that freemarkers templates can also be loaded from different places than the war file in a standard way (this can be handy if you use a CMS or so).

<!-- get the GuessBean from Jucas -->
<#assign guessBean = jucas.getBean("jucas://org.jucas.examples.numberguess.GuessBean")>

<html>
<head>
<meta http-equiv="expires" content="0">
<title>Jucas Sample - Number Guess Java</title>
</head>
<body bgcolor="#FFFFFF">

<h1>Welcome to the Number Game </h1>

<!-- explain the game if it is the frist guess-->
<#if guessBean.guesses == 0>
  Please guess a number between 0 and 9 (including 0 and 9)
  <#else>
  <p>This was your ${guessBean.getGuesses()} guess. </p>
  <#if guessBean.inputOk>
      <#if guessBean.rigthGuess>
        Congratulation!!! you guessed right    
      <#else>   
         Unfortunately you guessed wrong
      </#if>
  <#else>
      You entered: ${guessBean.inputNet} which is not allowed.    
      Please enter a value between 0 and 9.
  </#if>
</#if>

<!-- the input form is a normal form with normal semantics.
     Like normal the action also indicates which page to show next (we say ourself).
     ActionCode is used to map the form input field to the input(Net) property of GuessBean -->
<form action="guess.ftl" method="post">
 
   <#assign actionCode = jucas.actionCode >
   
   <input type="text" name="${actionCode.addProperty("inputField",guessBean,"input")}">
   
   ${actionCode.hiddenField} 
   
   <input type="submit" value="guess again">
</form>

<!-- use the method jucasAction_clearGuesses to give the user a change to clear his guesses up to
     now. We will send this in a link. Again we use an actionCode to instruct jucas
     to call the method clearGueses(Net) on the GuessBean -->
     
<#assign actionCode = jucas.actionCode>
${actionCode.getAction(guessBean).setName("clearGuesses")}

<p>
<a href="guess.ftl?${actionCode.requestPara}">
  Set back count 
</a> 
</p>

</body>
</html>

The first thing we do is to get a GuessBean instance from Jucas. Jucas puts an org.jucas.ViewHelper in the HttpServletRequest attribute 'jucas'. This ViewHelper (in here referenced by jucas) is used to get the GuessBean. (Freemarker accesses directly the HttpServletRequest attributes so we do not have to get the ViewHelper excplicitly from there.)

The method ViewHelper.getBean() takes as an argument the 'jucas-name' of the GuessBean. The 'jucas-name' of a JucasBean is an URI where the scheme is 'jucas' (for services the scheme is 'service'). The host of the URI denotes the class-name of the JucasBean.

The (optional) path and the query of the URI denote the instance of the JucasBean. So for example through the 'bean-names' "jucas://org.jucas.examples.numberguess.GuessBean" and "jucas://org.jucas.examples.numberguess.GuessBean/second/bean" two different instance of GuessBean (with also different states in request and session)would be gotten. This could be for example used to give the user a chance to place two guesses on the same page or run different guess-games on different pages. (Actually it is used for ? yet to come - default components like trees etc which are used multiple times in a web-app).

Finally the (optional) fragment part of the URI (after the #) is interpreted as a BeanUtils path on the properties of the JucasBean loaded. This way through the jucas-name (URI) also other objects but JucasBeans can be referenced.

For our example we just load a GuessBean instance. Than we use the properties of the GuessBean like of a normal JavaBean to render the ouput (We have a lot of if else here because we handle for demonstration all states of the bean in one template. Normally you would split this up and include the other templates. The other templates would than also get the GuessBean from the ViewHelper).

Than comes the form where the user can enter his guess. Note the form tag is a normal html form tag, where also the action specifies which page is to be shown next (Jucas does not care about that).

Next we get an org.jucas.ActionCode instance from the ViewHelper. The ActionCode is used to map request-parameters (form-input-fields or url query-parameters) to properties which should be set and to methods and their parameters (also called actions) which should be called on Peers (remember Peers are components or generally objects on which properties can be set and/or methods can be executed).

We make a normal html-input-field and assign it the name ?inputField?. The ActionCode.addProperty() method takes as first argument the name of the input-field (this is also returned by the method and therefore set as the name of the input-field in the html-code). The second argument is the Peer on which the property should be set and the third argument is the property of the Peer without the Net suffix which should be set.

Finally and this is important the ActionCode must be transfered as html-hidden field (or anyway as request-parameter) so that jucas knows how to map the request-parameters. ActionCode.getHiddenField() prints an html-hidden-field for this.

Finally we want to provide a Link through which the user can set back the guess-count. Again we need an action-code (a new one is needed for each form or url on the html-page). But now we set the action clearGuesses. Again the Net suffix is committed. Finally we print the action-code again but this time we use ActionCode.getRequestPara(). This prints the action-code as an URL query (action-code = the code - url-encoded etc).

That's about it. The example is included in the distribution and can be run through the jucas.war web-app.