1 // Copyright 2018 The Fuchsia Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "promise_example2.h"
6 
7 #include <algorithm>
8 #include <string>
9 
10 #include <lib/fit/promise.h>
11 #include <lib/fit/single_threaded_executor.h>
12 
13 #include "utils.h"
14 
15 namespace promise_example2 {
16 
17 // Rolls a die and waits for it to settle down then returns its value.
18 // This task might fail so the caller needs to be prepared to re-roll.
19 //
20 // This function demonstrates returning pending, error, and ok states as well
21 // as task suspension.
roll_die(std::string player,std::string type,int number_of_sides)22 auto roll_die(std::string player, std::string type, int number_of_sides) {
23     return fit::make_promise([player, type, number_of_sides](fit::context& context)
24                                  -> fit::result<int> {
25         int event = rand() % 6;
26         if (event == 0) {
27             // Imagine that the die flew off the table!
28             printf("    %s's '%s' die flew right off the table!\n",
29                    player.c_str(), type.c_str());
30             return fit::error();
31         }
32         if (event < 3) {
33             // Imagine that the die is still rolling around.
34             utils::resume_in_a_little_while(context.suspend_task());
35             return fit::pending();
36         }
37         // Imagine that the die has finished rolling.
38         int value = rand() % number_of_sides;
39         printf("    %s rolled %d for '%s'\n", player.c_str(), value, type.c_str());
40         return fit::ok(value);
41     });
42 }
43 
44 // Re-rolls a die until it succeeds.
45 //
46 // This function demonstrates looping a task using a recursive tail-call.
roll_die_until_successful(std::string player,std::string type,int number_of_sides)47 fit::promise<int> roll_die_until_successful(
48     std::string player, std::string type, int number_of_sides) {
49     return roll_die(player, type, number_of_sides)
50         .or_else([player, type, number_of_sides] {
51             return roll_die_until_successful(player, type, number_of_sides);
52         });
53 }
54 
55 // Rolls an effect and damage die.
56 // If the effect die comes up 0 then also rolls an effect multiplier die to
57 // determine the strength of the effect.  We can do this while waiting
58 // for the damage die to settle down.
59 //
60 // This functions demonstrates the benefits of capturing a task into a
61 // |fit::future| so that its result can be retained and repeatedly
62 // examined while awaiting other tasks.
roll_for_damage(std::string player)63 auto roll_for_damage(std::string player) {
64     return fit::make_promise(
65         [player,
66          damage = fit::future<int>(roll_die_until_successful(player, "damage", 10)),
67          effect = fit::future<int>(roll_die_until_successful(player, "effect", 4)),
68          effect_multiplier = fit::future<int>()](fit::context& context) mutable
69         -> fit::result<int> {
70             bool damage_ready = damage(context);
71 
72             bool effect_ready = effect(context);
73             if (effect_ready) {
74                 if (effect.value() == 0) {
75                     if (!effect_multiplier)
76                         effect_multiplier = roll_die_until_successful(player, "multiplier", 4);
77                     effect_ready = effect_multiplier(context);
78                 }
79             }
80 
81             if (!effect_ready || !damage_ready)
82                 return fit::pending();
83 
84             if (damage.value() == 0)
85                 printf("%s swings wildly and completely misses their opponent\n",
86                        player.c_str());
87             else
88                 printf("%s hits their opponent for %d damage\n",
89                        player.c_str(), damage.value());
90 
91             int effect_bonus = 0;
92             if (effect.value() == 0) {
93                 if (effect_bonus == 0) {
94                     printf("%s attempts to cast 'lightning' but the spell "
95                            "fizzles without effect\n",
96                            player.c_str());
97                 } else {
98                     effect_bonus = effect_multiplier.value() * 2 + 3;
99                     printf("%s casts 'lightning' for %d damage\n",
100                            player.c_str(), effect_bonus);
101                 }
102             }
103             return fit::ok(damage.value() + effect_bonus);
104         });
105 }
106 
107 // Plays one round of the game.
108 // Both players roll dice simultaneously to determine the damage dealt
109 // to their opponent.  Returns true if the game is over.
110 //
111 // This function demonstrates joining the results of concurrently executed
112 // tasks as a new task which produces a tuple.
play_round(int * red_hp,int * blue_hp)113 auto play_round(int* red_hp, int* blue_hp) {
114     return fit::join_promises(roll_for_damage("Red"), roll_for_damage("Blue"))
115         .and_then(
116             [red_hp, blue_hp](
117                 std::tuple<fit::result<int>, fit::result<int>> damages) mutable
118             -> fit::result<bool> {
119                 *blue_hp = std::max(*blue_hp - std::get<0>(damages).value(), 0);
120                 *red_hp = std::max(*red_hp - std::get<1>(damages).value(), 0);
121                 printf("Hit-points remaining: red %d, blue %d\n", *red_hp, *blue_hp);
122                 if (*red_hp != 0 && *blue_hp != 0)
123                     return fit::ok(false);
124 
125                 // Game over.
126                 puts("Game over...");
127                 if (*red_hp == 0 && *blue_hp == 0)
128                     puts("Both players lose!");
129                 else if (*red_hp != 0)
130                     puts("Red wins!");
131                 else
132                     puts("Blue wins!");
133                 return fit::ok(true);
134             });
135 }
136 
137 // Plays a little game.
138 // Red and Blue each start with 100 hit points.
139 // During each round, they both simultaneously roll dice to determine damage to
140 // their opponent.  If at the end of the round one player's hit-points reaches
141 // 0, that player loses.  If both players' hit-points reach 0, they both lose.
play_game()142 auto play_game() {
143     return fit::make_promise([red_hp = 100, blue_hp = 100](fit::context& context) mutable {
144         puts("Red and Blue are playing a game...");
145 
146         // TODO: We might benefit from some kind of loop combinator here.
147         return fit::make_promise(
148             [&red_hp, &blue_hp,
149              round = fit::future<bool>()](fit::context& context) mutable
150             -> fit::result<> {
151                 for (;;) {
152                     if (!round)
153                         round = play_round(&red_hp, &blue_hp);
154                     if (!round(context))
155                         return fit::pending();
156 
157                     bool game_over = round.value();
158                     if (game_over)
159                         return fit::ok();
160                     round = nullptr;
161                 }
162             });
163     });
164 }
165 
run()166 void run() {
167     fit::run_single_threaded(play_game());
168 }
169 
170 } // namespace promise_example2
171