Skip to content

Instantly share code, notes, and snippets.

@DanCouper
Last active September 21, 2017 15:55
Show Gist options
  • Save DanCouper/0fc4b3fb335247447ed7cfc7db9ede07 to your computer and use it in GitHub Desktop.
Save DanCouper/0fc4b3fb335247447ed7cfc7db9ede07 to your computer and use it in GitHub Desktop.
/* ---------------------------
* Helpers
* --------------------------- */
/* `ReasonReact.stringToElement` is a little verbose, so alias it.
* NOTE this particular vagary get annoying quickly. */
let stringToEl = ReasonReact.stringToElement;
/* ReasonReact doesn't play nice with React Devtools. This is a pain
* point; for development, manually printing out the state turns out
* to be helpful: */
let intListToReactString intList =>
stringToEl (Helpers.intListToString intList);
/* ---------------------------
* SequenceDisplay component
*
* This, on mount, diplays a sequence of list items.
* NOTE: Initially, I tried to make this do too much - I included
* the controls within the component, and tried to hook into
* `didUpdate`, switching internal state. After a few failed
* attempts, I used something that's basically identical to
* the example in the guide.
* TODO: replace `setinterval` with a `requestAnimationFrame`
* based solution. Slight wierdness - Bs_WebIncubator has a
* binding for `requestAnimationFrame`, but not for
* `cancelAnimationFrame`.
* TODO: this has to notify the parent that it's finished.
* Need a callback passed as a prop.
* REVIEW: The state _could_ be dispensed with entirely, need to
* investigate. Because CSS props can be passed, a keyframe
* animation could be generated within the component based on
* passed args: the sequence is simple, and the timings linear.
* The end event could occur on an `onAnimationEnd` or
* `onTransitionEnd` callback. This idea has legs.
* --------------------------- */
type action =
| Tick;
type state = {
displaySeq: list int,
timerId: ref (option Js.Global.intervalId)
};
let sequence state sequenceCompleteNotifier =>
switch state.displaySeq {
| [] =>
ReasonReact.UpdateWithSideEffects
{...state, timerId: ref None} sequenceCompleteNotifier
| [x, ...xs] => ReasonReact.Update {...state, displaySeq: xs}
};
let component = ReasonReact.reducerComponent "SequenceDisplay";
let make ::displaySeq ::timeoutDelay=1000 ::displayEndNotifier _children => {
...component,
initialState: fun () => {displaySeq, timerId: ref None},
reducer: fun action state =>
switch action {
| Tick => sequence state displayEndNotifier
},
didMount: fun self => {
self.state.timerId :=
Some (Js.Global.setInterval (self.reduce (fun _ => Tick)) timeoutDelay);
ReasonReact.NoUpdate
},
render: fun {state} => <p> (intListToReactString state.displaySeq) </p>
};
let component = ReasonReact.statelessComponent "SequenceInput";
let make ::inputs _children => {
...component,
render: fun self =>
<figure>
(
ReasonReact.arrayToElement (
Array.of_list (
List.map
(
fun {keyValue} =>
<button
value=(string_of_int keyValue)
key=(string_of_int keyValue)>
(ReasonReact.stringToElement (string_of_int keyValue))
</button>
)
inputs
)
)
)
</figure>
};
/* ---------------------------
* Helpers
* --------------------------- */
/* `ReasonReact.stringToElement` is a little verbose, so alias it.
* NOTE this particular vagary get annoying quickly. */
let stringToEl = ReasonReact.stringToElement;
/* ---------------------------
* Simon component
*
* The core component, eventually should just have job of shifting between game states.
* --------------------------- */
type gameState =
| Waiting
| Ready
| Displaying
| Displayed
| Playing;
let gameStateToReactStr gameState =>
switch gameState {
| Waiting => stringToEl "Game state: Waiting"
| Ready => stringToEl "Game state: Ready"
| Displaying => stringToEl "Game state: Displaying"
| Displayed => stringToEl "Game state: Displayed"
| Playing => stringToEl "Game state: Playing"
};
type action =
| Start
| Display
| DisplayComplete
| Play
| Check int;
type state = {gameState, seq: list int};
let component = ReasonReact.reducerComponent "Simon";
let make ::rounds ::keys _children => {
...component,
initialState: fun () => {gameState: Waiting, seq: []},
reducer: fun action state =>
switch action {
| Start => ReasonReact.Update {...state, gameState: Ready}
| Display => ReasonReact.Update {...state, gameState: Displaying}
| DisplayComplete => ReasonReact.Update {...state, gameState: Displayed}
| Play => ReasonReact.Update {...state, gameState: Playing}
| Check i => ReasonReact.NoUpdate
},
render: fun self =>
<figure>
<header>
/* DEBUG AREA */
<p> (gameStateToReactStr self.state.gameState) </p>
</header>
(
switch self.state.gameState {
| Displaying =>
<SequenceDisplay
displaySeq=[1, 2, 3, 4, 5, 6, 7]
timeoutDelay=500
displayEndNotifier=(self.reduce (fun _ => DisplayComplete))
/>
| Playing =>
<SequenceInput
inputs=(
List.map
(
fun i => {
keyValue: i
/* inputCallback: self.reduce (fun _ => Check i) */
}
)
self.state.displaySeq
)
/>
| _ => ReasonReact.nullElement
}
)
(
switch self.state.gameState {
| Waiting =>
<button onClick=(self.reduce (fun _ => Start))>
(stringToEl "Start Round")
</button>
| Ready =>
<button onClick=(self.reduce (fun _ => Display))>
(stringToEl "Display Sequence")
</button>
| Displaying => ReasonReact.nullElement
| Displayed =>
<button onClick=(self.reduce (fun _ => Play))>
(stringToEl "Start Guessing")
</button>
| Playing => ReasonReact.nullElement
}
)
</figure>
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment