No one ever learned to ride a bicycle, while watching other people bike. Hence we Dojo.
We're trying to improve the quality of the Reason Dojo. Don't hesitate to leave us feedback: https://goo.gl/forms/nogGPAf9WQGkdgS83
- Don't be afraid to ask help from a mentor at the Dojo
- Join https://discordapp.com/invite/reasonml, look for the #reason-dojo channel
- Tweet at https://twitter.com/_iwan_refmt
Make sure you have node installed.
platform | install command |
---|---|
macOS | npm install -g reason-cli@3.2.0-darwin |
Linux | npm install -g reason-cli@3.2.0-linux |
Windows | Contact a Reason mentor |
-
VSCode recommended
-
Atom
apm install language-reason linter linter-refmt reason-refmt atom-ocaml-merlin
git clone https://github.com/IwanKaramazow/ReasonDojo.git
cd ReasonDojo
npm install
# or with yarn
yarn
npm run build
npm start
A window with a floating static bird should appear.
Reprocessing is able to live reload code while developing, opening up all possibilities for a tremendous dev experience. Ever heard of live reloading code in native environments? Me neither, until Reason ;)
npm start
(from the previous step) automatically starts the app with hot reloading enabled.
- Open
index.re
and modifyspeed
in thePhysics
module. (Hint:float
numbers, always contain a.
) - Experiment with different speeds
- Reset the speed to
150.
Let's start by taking a look at our bird. The image of the bird gets drawn by the following code:
Images.drawBird(~x=180., ~y=state.birdY, ~image=state.image, env);
-
Update the relevant part of the state in
draw
to give the bird a different position on they
-axis. For example give it a hard-coded position of150.
-
CONCEPT: Take a look at the example below. Can you guess what
~
does for arguments?let add = (~a, ~b) => a + b; let result = add(~b=2, ~a=4);
-
CONCEPT: Take a look at the example below. What happens if we don't provide
b
as an argument?let add = (~a, ~b=16) => a + b; let result = add(~a=4);
-
Right now, the bird is floating aimlessly through the sky, without a care in the world. Let's change that by blowing some wind in its face!
To do this, we want to increment the bird's Y-position with the result of ourMath.sineWave
function on every draw.Take a look at the
Math.sineWave
definition. You will see it requires several arguments but some of them have default values (see the concept above). These default values will be used if you omit them in the function invocation. So the only arguments we really need to provide is the offset and a()
(unit) as last argument to finish the function application.As offset we can provide the
xOffset
value from the state. -
CONCEPT: Can you guess what the difference is between
18. +. 12.
and18 + 12
?
TIP: Don't be afraid to ask a mentor for help. If you've never worked with named or default arguments before, you'll definitely have a lot of questions.
-
CONCEPT: Take a quick look at the documentation about Variants and Constructors and Pattern Matching. What will the result be of the message function in the example below?
type myFirstVariant = | Yes | PrettyMuch | No; let doYouUnderstandIt = Yes; let message = switch (doYouUnderstandIt) { | No => "No worries. Ask a mentor for help!" | Yes => "Great! Aren't Variants awesome?" | PrettyMuch => "Nice! Almost there!" };
Having a bird cruising on the wind is great but it's time to get some interaction going. When you press Space now, nothing is happening. And if you take a look at the player Variant, it's not difficult to see why. There is just one tag and it indicates a waiting state.
type player =
| Waiting;
- Add an extra tag (a.k.a. constructor) that indicates a player is "Playing". Note: tags (or constructors) always start with an uppercased character.
- Modify the switch statement on
state.player
, to pattern match on your new tag/constructor and return the next state. - Whenever the user presses a
space
in theWaiting
state, update thestate.player
to reflect that the game has started. Hint: to check whether a space was pressed you can useEnv.keyPressed(Space, env)
.
Now that we know the game started (a Space has been pressed), it's time to make the bird respond to our commands!
- Update the bird's
y
-axis position on every draw (when the user is playing) by incrementing its value withstate.acceleration *. deltaTime
- Gravity is a thing in the real world. Increment the
acceleration
on every draw with thegravity
from thePhysics
module. - Whenever the user presses space, the bird needs to fly higher and higher. If a space is pressed, the
acceleration
is equal to the force contained byjumpAcceleration
in thePhysics
module.
Amazing, the bird is able to fly through the air whenever the space-key is pressed. Due to weird laws governing the FlappyBird-world, a bird (unfortunately) dies when it hits the ground. It's time to implement ground collision detection.
- Add an extra tag to the
player
-type indicating the player has lost, because his bird hit the ground. - Modify the
hitsFloor
function. It should returntrue
whenever the bird hit the floor. - Use the
hitsFloor
function to update thestate.player
. (Indicates death of the bird when it hit the ground) - Make sure that the bird stays dead on the ground, whenever it hit the ground. (A bird falling through the earth violates certain laws of physics).
- If the user presses
space
when his bird is dead, reset the game to theWaiting
state.
Great, you wrote safe, approachable code, that runs seamlessly everywhere as fast as it possibly can in each possible environment. The code you just wrote doesn't care about where it runs, you can use it in old browsers, new browsers, native apps, compiled bytecode - anywhere. (The main point being that the backend is treated as an implementation detail.)
- Compile your game to a native app and play it
npm run build:native
# play the game
npm run start:native
- Compile your game to bytecode and play it
npm run build:bytecode
# play the game
npm run start:bytecode
- Compile your game to javascript and play it
npm run build:web
open `index.html` in a browser, e.g. safari, firefox... (there's a Reprocessing bug in Chrome atm, will be fixed)
- When the user is playing, use the
updatePipes
function to update thepipes
state on every draw - Uncomment following part of the code in the
draw
function:
let crash = birdHitsPipes(state.birdY, state.pipes);
- The
crash
boolean indicates whether a bird hit a pipe. When there's a crash:state.player
should transition to the same state as when the bird hit the ground- the pipes should stop moving, e.g. no need to update them anymore
We're trying to improve the quality of the Reason Dojo. Don't hesitate to leave us feedback: https://goo.gl/forms/nogGPAf9WQGkdgS83