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