Skip to content

Commit

Permalink
ready for next gem release
Browse files Browse the repository at this point in the history
  • Loading branch information
catmando committed Mar 29, 2021
1 parent f44779f commit 04cf1de
Show file tree
Hide file tree
Showing 21 changed files with 277 additions and 89 deletions.
30 changes: 15 additions & 15 deletions docs/client-dsl/interlude-tic-tac-toe.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ At this point if you have been reading sequentially through these chapters you k

The board is represented by an array of 9 cells. Cell 0 is the top left square, and cell 8 is the bottom right.

Each cell will contain nil, an :X or an :O.
Each cell will contain nil, an `:X` or an `:O`.

### Displaying the Board

Expand All @@ -13,7 +13,7 @@ The `DisplayBoard` component displays a board. `DisplayBoard` accepts a `board`
A small helper function `draw_squares` draws an individual square which is displayed as a `BUTTON`. A click handler is attached which
will fire the `clicked_at` event with the appropriate cell id.

Notice that `DisplayBoard` has no internal state of its own. That is handled by the `Game` component.
Notice that `DisplayBoard` has no internal state of its own. That is handled by the `DisplayGame` component.

```ruby
class DisplayBoard < HyperComponent
Expand All @@ -37,7 +37,7 @@ end

### The Game State

The `Game` component has two state variables:
The `DisplayGame` component has two state variables:
+ `@history` which is an array of boards, each board being the array of cells.
+ `@step` which is the current step in the history (we begin at zero)

Expand All @@ -46,17 +46,17 @@ The `Game` component has two state variables:
These are initialized in the `before_mount` callback. Because Ruby will adjust the array size as needed
and return nil if an array value is not initialized, we can simply initialize the board to an empty array.

There are two reader methods that read the state:
There are three *reader* methods that read the state:

+ `player` returns the current player's token. The first player is always :X so even steps
are :X, and odd steps are :O.
+ `player` returns the current player's token. The first player is always `:X` so even steps
are `:X`, and odd steps are `:O`.
+ `current` returns the board at the current step.
+ `history` uses state_reader to encapsulate the history state.

Encapsulated access to state in reader methods like this is not necessary but is good practice

```ruby
class Game < HyperComponent
class DisplayGame < HyperComponent
before_mount do
@history = [[]]
@step = 0
Expand All @@ -79,7 +79,7 @@ end
We also have a `current_winner?` method that will return the winning player or nil based on the value of the current board:

```ruby
class Game < HyperComponent
class DisplayGame < HyperComponent
WINNING_COMBOS = [
[0, 1, 2],
[3, 4, 5],
Expand All @@ -93,9 +93,7 @@ class Game < HyperComponent

def current_winner?
WINNING_COMBOS.each do |a, b, c|
return current[a] if current[a] &&
current[a] == current[b] &&
current[a] == current[c]
return current[a] if current[a] && current[a] == current[b] && current[a] == current[c]
end
false
end
Expand All @@ -110,7 +108,9 @@ There are two mutator methods that change state:

The `handle_click!` mutator first checks to make sure that no one has already won at the current step, and that
no one has played in the cell that the user clicked on. If either of these conditions is true `handle_click!`
returns and nothing changes.
returns, no mutation is signaled and nothing changes.

> If we had wanted to return AND signal a state mutation we would use the Ruby `next` keyword instead of `return`.s
To update the board `handle_click!` duplicates the squares; adds the player's token to the cell; makes a new
history with the new squares on the end, and finally updates the value of `@step`.
Expand All @@ -120,7 +120,7 @@ code are aware that these will change state.

```ruby

class Game < HyperComponent
class DisplayGame < HyperComponent
mutator :handle_click! do |id|
board = history[@step]
return if current_winner? || board[id]
Expand All @@ -143,7 +143,7 @@ Now we have a couple of helper methods to build parts of the game display.
+ `status` provides the play state

```ruby
class Game < HyperComponent
class DisplayGame < HyperComponent
def moves
return unless history.length > 1

Expand All @@ -166,7 +166,7 @@ end
And finally our render method which displays the Board and the game info:

```ruby
class Game < HyperComponent
class DisplayGame < HyperComponent
render(DIV, class: :game) do
DIV(class: :game_board) do
DisplayBoard(board: current)
Expand Down
1 change: 1 addition & 0 deletions docs/client-dsl/notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,7 @@ Hyperstack.cancel_import 'hyperstack/component/auto-import'
```

### The Enter Event
The :enter event is short for catching :key_down and then checking for a key code of 13.
```ruby
class YouSaid < HyperComponent
state_accessor :value
Expand Down
3 changes: 3 additions & 0 deletions docs/client-dsl/state.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,9 @@ is common enough that Hyperstack provides two ways to shorten this code. The fi
In other words `mutator` defines a method that is wrapped in a call to `mutate`. It also has
the advantage of clearly declaring that this method will be mutating the components state.

> Important note: If you do an early exit from the mutator using a `return` or `break` no mutation
will occur. If you want to do an early exit then use the `next` keyword.

### The `state_accessor`, `state_reader` and `state_writer` Methods

Often all a mutator method will do is assign a new value to a state. For this case Hyperstack provides
Expand Down
98 changes: 98 additions & 0 deletions docs/hyper-state/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,101 @@
<img align="left" width="100" height="100" style="margin-right: 20px" src="https://github.com/hyperstack-org/hyperstack/blob/edge/docs/wip.png?raw=true" /> The `Hyperstack::State::Observable` module allows you to build classes that share their state with Hyperstack Components, and have those components update when objects in those classes change state.

## This Page Under Construction

The `Hyperstack::State::Observable` module allows you to build classes that share their state with Hyperstack Components, and have those components update when objects in those classes change state.

### Revisiting the Tic Tac Toe Game

The easiest way to understand how to use Hyperstate is by example. If you you did not see the Tic Tac Toe example, then please review it now, as we are going to use this to demonstrate how the `Hyperstack::State::Observable` can be used in any Ruby class, to make that class work as a **Store** for your Hyperstack components.

Here is the revised Tic Tac Toe game using a *Store* to hold the game data.

```ruby
class Game
include Hyperstack::State::Observable

receives Hyperstack::Application::Boot do
@history = [[]]
@step = 0
end

class << self
observer :player do
@step.even? ? :X : :O
end

observer :current do
@history[@step]
end

state_reader :history

WINNING_COMBOS = [[0, 1, 2], [3, 4, 5], [6, 7, 8], [0, 3, 6], [1, 4, 7], [2, 5, 8], [0, 4, 8], [2, 4, 6]]

def current_winner?
WINNING_COMBOS.each do |a, b, c|
return current[a] if current[a] && current[a] == current[b] && current[a] == current[c]
end
false
end

mutator :handle_click! do |id|
board = history[@step]
return if current_winner? || board[id]

board = board.dup
board[id] = player
@history = history[0..@step] + [board]
@step += 1
end

mutator(:jump_to!) { |step| @step = step }
end
end

class DisplayBoard < HyperComponent
param :board

def draw_square(id)
BUTTON(class: :square, id: id) { board[id] }
.on(:click) { Game.handle_click!(id) }
end

render(DIV) do
(0..6).step(3) do |row|
DIV(class: :board_row) do
(row..row + 2).each { |id| draw_square(id) }
end
end
end
end

class DisplayGame < HyperComponent
def moves
return unless Game.history.length > 1

Game.history.length.times do |move|
LI(key: move) { move.zero? ? "Go to game start" : "Go to move ##{move}" }
.on(:click) { Game.jump_to!(move) }
end
end

def status
if (winner = Game.current_winner?)
"Winner: #{winner}"
else
"Next player: #{Game.player}"
end
end

render(DIV, class: :game) do
DIV(class: :game_board) do
DisplayBoard(board: Game.current)
end
DIV(class: :game_info) do
DIV { status }
OL { moves }
end
end
end
```
2 changes: 1 addition & 1 deletion docs/rails-installation/prerequisites.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ If you don't have an existing Rails app you can create a new Rails app
with the following command line:

```
bundle exec rails new NameOfYourApp -T
rails new NameOfYourApp -T
```

To avoid much pain do not name your app `Application` as this will conflict with all sorts of
Expand Down
Loading

0 comments on commit 04cf1de

Please sign in to comment.