Archive for May, 2008

A Hangman game for Mike

Sunday, May 18th, 2008

My buddy Mike Pence motivated me to write a Hangman game. It shows some Apotomo concepts in a playful (painful?) way.

A screenshot from the exciting Hangman game.

The complete game is encapsulated in a widget.

app/cells/hangman_cell.rb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
class HangmanCell < Apotomo::StatefulWidget  
 
  def transition_map
    { :hangman => [:_guess],
      :_guess => [:_guess, :hangman]
    }
  end  
 
  def hangman
    @word     = random_word
    @guessed  = []
    @hits     = []
    @try      = 0
    @total_tries = 6
    @tries_left = @total_tries - @try
 
    nil
  end
 
  def _guess
    @try+=1
    @tries_left = @total_tries - @try
 
    guess     = param(:guess)
    @guessed  << guess 
    @hits     << guess if @word.include?(guess)
 
    if @hits.uniq.size == @word.split("").uniq.size
      jump_to_state :_you_won
    end
 
    if @tries_left == 0 
      jump_to_state :_game_over 
    end
 
    state_view :hangman
  end
 
  def _you_won
  end
 
  def _game_over
  end
 
  def random_word
    "Apotomo"   # this game IS hard.
  end  
end

To actually play, we should plug the game widget into our application widget tree.

app/apotomo/application_widget_tree.rb

  some_pg << cell(:hangman, :hangman, 'hangman_game')

I set :hangman as the start state. The respective method initializes the game variables (line 9-18) and shows the according view.

app/cells/hangman/hangman.haml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
%h1
  Hangman goes Apotomo
 
(Tries left: 
= @tries_left
)
 
%h2
  - @guess_str = ""
  - @word.split("").each { |c| @guess_str+= (@guessed.include?(c) ? c : "-") }
  = @guess_str
 
= form_to_event(:state => :_guess)
%input{:type=>:text, :id=>"guess", :name=>"guess", :size => 3, :value => ""}
</form>
 
%p
  You already tried 
  = @guessed.split("").join(", ")

Whenever the form is submitted, the widgets’ state machine is set to the state :_guess (line 13), which is a valid state method in our hangman cell (line 20-37).

The _guess method just processes the guess from the player, decreases the number of tries and decides what to do next. Normally, this means it stays in the state :_guess and reuses the state view hangman (line 36).

However, if the player guessed the complete word the widget goes into the state :_you_won (line 29). Awesome.

A state diagram for the exciting hangman game.

BTW, if someone knows a good state chart drawing app, please contact me right away.

Cheers, Mike!

Observing a form

Sunday, May 18th, 2008

Apotomo introduces an event-driven approach for Rails applications. Some widgets fire events, other register handlers for these events. I’ll demonstrate this with an example, where one small widget observes a form and acts upon a certain event from this form.

Taking our simple form example, I add one line to the form processing state.

app/cells/simple_form_cell.rb

14
15
16
17
18
19
20
  def _process_form
    res = @user.update_attributes(param(:user))
    return state_view :form unless res
 
    trigger(:userCreated)
    state_view :user_created
  end

Easy: if the user can successfully be created, the form widget informs all other (interested!) widgets about this spectacular incident by triggering a generic :userCreated event (line 18).
That’s all for this form widget. It no longer is actively involved in the following steps.

The Observer
Another widget is interested in that user creation and registers an observer for that.

app/cells/form_observer_cell.rb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class FormObserverCell < Apotomo::StatefulWidget
  def transition_map
    { :lurk => [:_observe_form]
    }
  end
 
  def lurk
    "lurking..."
  end
 
  def _observe_form
    "I smell a userCreated event!"
  end
end

Normally the widgets lurks around in the state :lurk. When the :userCreated event is fired, it wants itself to go into the state :_observe_form. How to do that?
A common place to attach observers is the application widget tree. I love this word.

app/apotomo/application_widget_tree.rb

  playgrd << simple_form = cell(:simple_form, :form, 'simple_form')
 
  playgrd << cell(:form_observer, :lurk, 'form_observer')
  simple_form.watch(:userCreated, 'form_observer', :_observe_form)

I first add the simple_form widget to the page. Then the form_observer widget with start state :lurk.

The watch call attaches the observer to simple_form, instructing the event system to invoke :_observe_form on the widget form_observer whenever an userCreated event is triggered.
This sounds confusing but it makes sense. In GUI architectures, it is common to attach callbacks as event handlers - in our case, we assigned a state method of a widget as an event handler.

Counter

Saturday, May 17th, 2008

After being compared to seaside I had a look at this project. Looks great, it is a stateful component framework in Smalltalk. To demonstrate my concepts in Apotomo I had to steal the counter example from the page.

As usual, we implement the counter logic in a cell.

app/cells/counter_cell.rb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class CounterCell < Apotomo::StatefulWidget
 
  def transition_map
    { :_increase => [:_increase, :_decrease],
      :_decrease => [:_increase, :_decrease],
    }
  end
 
  def count
    @count = 0
    jump_to_state :_increase
  end
 
  def _increase
    @count += 1  
    state_view :_count
  end
 
  def _decrease
    @count -= 1  
    state_view :_count
  end
end

The widget is plugged into an arbitrary part of the GUI.

app/apotomo/application_widget_tree.rb

some_pg << cell(:counter, :count, 'my_counter')

Start state is :count, which initializes the counter and jumps to the state :_increment (line 10-11). Here we actually increment our counter and render the view _count (line 16). Let’s look at the view.

app/cells/counter/_count.html.erb

<h1><%= @count %></h1>
 
<%= link_to_event("--", :state => :_decrease) %>
<%= link_to_event("++", :state => :_increase) %>

Simple stuff. We create two links which reference to the current widget my_counter and push the widget state machine into the respective state. Looking at the transition map (line 4-5) we see that the requested states are permitted.

Counter state diagram

After all this coding, I have to admit that the comparison with seaside made me somewhat proud.

A “Toggle-Friendship” widget on a user profile page

Thursday, May 8th, 2008

We take an imaginary forum application. When viewing another user’s profile page, there is often an “Add buddy” button, where you can add users to your friends list. Enough talk, let’s code that.

app/cells/buddy_cell.rb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class BuddyCell < Apotomo::StatefulWidget
 
  def transition_map
    {:toggle => [:_chumup, :_breakup]}
  end
 
  def toggle
    @user    = current_user
    @viewed  = param(:drinker)
 
    if @user.friends?(@viewed)
      return state_view :break
    end
 
    state_view :chum
  end
 
  def _chumup
    @viewed  = param(:drinker)  # i forgot we're stateful!
    @user.add_friend(@viewed)
 
    jump_to_state :toggle
  end
 
  def _breakup
    @user.remove_friend(@viewed)
    jump_to_state :toggle
  end
end

The widget is inserted in the tree as follows:

app/apotomo/application_widget_tree.rb

1
2
3
profile = cell(:user, :profile, 'user_profile')
  profile << main_col = section('main_col')
    main_col << cell(:buddy, :toggle, 'buddy_toggle')

I add the friendship widget with id buddy_toggle to a section, which itself is a child of the user_profile widget, that we soon will inspect.
Why the section? Well, maybe I like to split my profile into different parts. Sections are great for that.

Since we want to display a button either for chumming up or for breaking up the friendship, toggle is a simple decider state, rendering the respective button view (line 12 and 15).
The chumup view could look like:

app/cells/buddy_cell/chum.rhtml

<%= link_to_widget("Become friend", false, 
  :state => :_chumup ) %> with <%= @viewed.username %>!

Very simple. When the link is clicked, it advises our current widget buddy_toggle to go into the state _chumup. This is allowed by the state machine, since it is a valid transition (line 4). Further on, the state _chumup will execute its state method, add our new friend and jump back to the start state toggle (line 22).
The same applies for the other way round, the breakup.

So where do @user and @viewed come from (line 8 and 9)? Let’s forget about the currently logged in user and assume it is provided by some library call. More interesting is, how comes the currently viewed user (”drinker”) into our widget: Normally you would find out who’s viewed by inspecting the request parameters for something like “&user_id=9″. This is fine, but Apotomo provides a cleaner way to do this.

The call to param (line 9) will travel up the widget tree, asking the ascending widgets for :drinker. Since we await an object of type User, it would be nice, if someone up there could provide this object. Fortunately, the user_profile widget does this.


The User profile

app/cells/buddy_cell.rb

1
2
3
4
5
6
7
class BuddyCell < Apotomo::StatefulWidget
  def profile
    @drinker = User.find(param(:drinker_id))
    set_child_param(nil, :drinker, @drinker)
    nil
  end
end

The profile method provides the view for - guess what - the profile page of the viewed user, or “drinker”, as we call him here. The actual user object is retrieved by asking for the requested drinker id, which in turn is found out by querying param (line 3). This will end up in the root widget looking at the request parameters and returning the value.

Ok, when the profile page is opened, the user_profile widget saves the currently viewed user. How can the descending widgets like buddy_toggle access it? Well, they kindly ask for it.
By calling set_child_param (line 4), the profile widget is now the official provider for the :drinker object. Since we do not set a concrete widget id (the first argument is nil), this widget will provide the :drinker object for any descending widget. Wow.

Please note line 19 in BuddyCell. Here we ask for the object again, although it is already available as instance var in the state. It will retrieve the correct object, since user_profile provides it.