blob: dba678eab2b71892d44033ad8803b718282a3aba [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "promise_example2.h"
#include <algorithm>
#include <memory>
#include <string>
#include <lib/fit/promise.h>
#include <lib/fit/single_threaded_executor.h>
#include "utils.h"
namespace promise_example2 {
// State for a two player game.
//
// Players do battle by simultaneously rolling dice in order to inflict
// damage upon their opponent over the course of several rounds until one
// or both players' hit points are depleted to 0.
//
// Players start with 100 hit points. During each round, each player first
// rolls a Damage die (numbered 0 to 9) and an Effect die (numbered 0 to 3).
// If the Effect die comes up 0, the player casts a lightning spell and
// rolls an Effect Multiplier die (numbered 0 to 4) to determine the
// strength of the effect.
//
// The following calculation determines the damage dealt to the player's
// opponent:
//
// if Damage die value is non-zero,
// then opponent HP -= value of Damage die
// if Effect die is zero (cast lighting),
// then opponent HP -= value of Effect Multiplier die * 2 + 3
//
// Any dice that fly off the table during especially vigorous rolls are
// rerolled before damage is tallied.
struct game_state {
int red_hp = 100;
int blue_hp = 100;
};
// Rolls a die and waits for it to settle down then returns its value.
// This task might fail so the caller needs to be prepared to re-roll.
//
// This function demonstrates returning pending, error, and ok states as well
// as task suspension.
auto roll_die(std::string player, std::string type, int number_of_sides) {
return fit::make_promise([player, type, number_of_sides](fit::context& context)
-> fit::result<int> {
// Simulate the outcome of rolling a die.
// Either the die will settle, keep rolling, or fall off the table.
int event = rand() % 6;
if (event == 0) {
// The die flew off the table!
printf(" %s's '%s' die flew right off the table!\n",
player.c_str(), type.c_str());
return fit::error();
}
if (event < 3) {
// The die is still rolling around. Need to wait for it to settle.
utils::resume_in_a_little_while(context.suspend_task());
return fit::pending();
}
// The die has finished rolling, determine how it landed.
int value = rand() % number_of_sides;
printf(" %s rolled %d for '%s'\n", player.c_str(), value, type.c_str());
return fit::ok(value);
});
}
// Re-rolls a die until it succeeds.
//
// This function demonstrates looping a task using a recursive tail-call.
fit::promise<int> roll_die_until_successful(
std::string player, std::string type, int number_of_sides) {
return roll_die(player, type, number_of_sides)
.or_else([player, type, number_of_sides] {
// An error occurred while rolling the die. Recurse to try again.
return roll_die_until_successful(player, type, number_of_sides);
});
}
// Rolls an effect and damage die.
// If the effect die comes up 0 then also rolls an effect multiplier die to
// determine the strength of the effect. We can do this while waiting
// for the damage die to settle down.
//
// This functions demonstrates the benefits of capturing a task into a
// |fit::future| so that its result can be retained and repeatedly
// examined while awaiting other tasks.
auto roll_for_damage(std::string player) {
return fit::make_promise(
[player,
damage = fit::future<int>(roll_die_until_successful(player, "damage", 10)),
effect = fit::future<int>(roll_die_until_successful(player, "effect", 4)),
effect_multiplier = fit::future<int>()](fit::context& context) mutable
-> fit::result<int> {
// Evaluate the damage die roll future.
bool damage_ready = damage(context);
// Evaluate the effect die roll future.
// If the player rolled lightning, begin rolling the multiplier.
bool effect_ready = effect(context);
if (effect_ready) {
if (effect.value() == 0) {
if (!effect_multiplier)
effect_multiplier = roll_die_until_successful(player, "multiplier", 4);
effect_ready = effect_multiplier(context);
}
}
// If we're still waiting for the dice to settle, return pending.
// The task will be resumed once it can make progress.
if (!effect_ready || !damage_ready)
return fit::pending();
// Calculate the result and describe what happened.
if (damage.value() == 0)
printf("%s swings wildly and completely misses their opponent\n",
player.c_str());
else
printf("%s hits their opponent for %d damage\n",
player.c_str(), damage.value());
int effect_bonus = 0;
if (effect.value() == 0) {
if (effect_bonus == 0) {
printf("%s attempts to cast 'lightning' but the spell "
"fizzles without effect\n",
player.c_str());
} else {
effect_bonus = effect_multiplier.value() * 2 + 3;
printf("%s casts 'lightning' for %d damage\n",
player.c_str(), effect_bonus);
}
}
return fit::ok(damage.value() + effect_bonus);
});
}
// Plays one round of the game.
// Both players roll dice simultaneously to determine the damage dealt
// to their opponent.
//
// This function demonstrates joining the results of concurrently executed
// tasks as a new task which produces a tuple.
auto play_round(const std::shared_ptr<game_state>& state) {
return fit::join_promises(roll_for_damage("Red"), roll_for_damage("Blue"))
.and_then(
[state](const std::tuple<fit::result<int>, fit::result<int>>& damages) {
// Damage tallies are ready, apply them to the game state.
state->blue_hp = std::max(state->blue_hp - std::get<0>(damages).value(), 0);
state->red_hp = std::max(state->red_hp - std::get<1>(damages).value(), 0);
printf("Hit-points remaining: red %d, blue %d\n",
state->red_hp, state->blue_hp);
});
}
// Plays a little game.
// Red and Blue each start with 100 hit points.
// During each round, they both simultaneously roll dice to determine damage to
// their opponent. If at the end of the round one player's hit-points reaches
// 0, that player loses. If both players' hit-points reach 0, they both lose.
auto play_game() {
puts("Red and Blue are playing a game...");
return fit::make_promise(
[state = std::make_shared<game_state>(),
round = fit::future<>()](fit::context& context) mutable
-> fit::result<> {
// Repeatedly play rounds until the game ends.
while (state->red_hp != 0 && state->blue_hp != 0) {
if (!round)
round = play_round(state);
if (!round(context))
return fit::pending();
round = nullptr;
}
// Game over.
puts("Game over...");
if (state->red_hp == 0 && state->blue_hp == 0)
puts("Both players lose!");
else if (state->red_hp != 0)
puts("Red wins!");
else
puts("Blue wins!");
return fit::ok();
});
}
void run() {
fit::run_single_threaded(play_game());
}
} // namespace promise_example2