mirror of
https://github.com/coding-horror/basic-computer-games.git
synced 2025-12-22 07:10:42 -08:00
221 lines
7.3 KiB
C#
221 lines
7.3 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
|
|
namespace Game
|
|
{
|
|
/// <summary>
|
|
/// Provides a method for simulating a bull fight.
|
|
/// </summary>
|
|
public static class BullFight
|
|
{
|
|
/// <summary>
|
|
/// Begins a new fight.
|
|
/// </summary>
|
|
/// <param name="mediator">
|
|
/// Object used to communicate with the player.
|
|
/// </param>
|
|
/// <returns>
|
|
/// The sequence of events that take place during the fight.
|
|
/// </returns>
|
|
/// <remarks>
|
|
/// After receiving each event, the caller must invoke the appropriate
|
|
/// mediator method to inform this coroutine what to do next. Failure
|
|
/// to do so will result in an exception.
|
|
/// </remarks>
|
|
public static IEnumerable<Events.Event> Begin(Mediator mediator)
|
|
{
|
|
var random = new Random();
|
|
var result = ActionResult.FightContinues;
|
|
|
|
var bullQuality = GetBullQuality();
|
|
var toreadorePerformance = GetHelpQuality(bullQuality);
|
|
var picadorePerformance = GetHelpQuality(bullQuality);
|
|
|
|
var bullStrength = 6 - (int)bullQuality;
|
|
var assistanceLevel = (12 - (int)toreadorePerformance - (int)picadorePerformance) * 0.1;
|
|
var bravery = 1.0;
|
|
var style = 1.0;
|
|
var passNumber = 0;
|
|
|
|
yield return new Events.MatchStarted(
|
|
bullQuality,
|
|
toreadorePerformance,
|
|
picadorePerformance,
|
|
GetHumanCasualties(toreadorePerformance),
|
|
GetHumanCasualties(picadorePerformance),
|
|
GetHorseCasualties(picadorePerformance));
|
|
|
|
while (result == ActionResult.FightContinues)
|
|
{
|
|
yield return new Events.BullCharging(++passNumber);
|
|
|
|
var (action, riskLevel) = mediator.GetInput<(Action, RiskLevel)>();
|
|
result = action switch
|
|
{
|
|
Action.Dodge => TryDodge(riskLevel),
|
|
Action.Kill => TryKill(riskLevel),
|
|
_ => Panic()
|
|
};
|
|
|
|
var first = true;
|
|
while (result == ActionResult.BullGoresPlayer)
|
|
{
|
|
yield return new Events.PlayerGored(action == Action.Panic, first);
|
|
first = false;
|
|
|
|
result = TrySurvive();
|
|
if (result == ActionResult.FightContinues)
|
|
{
|
|
yield return new Events.PlayerSurvived();
|
|
|
|
var runFromRing = mediator.GetInput<bool>();
|
|
if (runFromRing)
|
|
result = Flee();
|
|
else
|
|
result = IgnoreInjury(action);
|
|
}
|
|
}
|
|
}
|
|
|
|
yield return new Events.MatchCompleted(
|
|
result,
|
|
bravery == 2,
|
|
GetReward());
|
|
|
|
Quality GetBullQuality() =>
|
|
(Quality)random.Next(1, 6);
|
|
|
|
Quality GetHelpQuality(Quality bullQuality) =>
|
|
((3.0 / (int)bullQuality) * random.NextDouble()) switch
|
|
{
|
|
< 0.37 => Quality.Superb,
|
|
< 0.50 => Quality.Good,
|
|
< 0.63 => Quality.Fair,
|
|
< 0.87 => Quality.Poor,
|
|
_ => Quality.Awful
|
|
};
|
|
|
|
int GetHumanCasualties(Quality performance) =>
|
|
performance switch
|
|
{
|
|
Quality.Poor => random.Next(0, 2),
|
|
Quality.Awful => random.Next(1, 3),
|
|
_ => 0
|
|
};
|
|
|
|
int GetHorseCasualties(Quality performance) =>
|
|
performance switch
|
|
{
|
|
// NOTE: The code for displaying a single horse casuality
|
|
// following a poor picadore peformance was unreachable
|
|
// in the original BASIC version. I've assumed this was
|
|
// a bug.
|
|
Quality.Poor => 1,
|
|
Quality.Awful => random.Next(1, 3),
|
|
_ => 0
|
|
};
|
|
|
|
ActionResult TryDodge(RiskLevel riskLevel)
|
|
{
|
|
var difficultyModifier = riskLevel switch
|
|
{
|
|
RiskLevel.High => 3.0,
|
|
RiskLevel.Medium => 2.0,
|
|
_ => 0.5
|
|
};
|
|
|
|
var outcome = (bullStrength + (difficultyModifier / 10)) * random.NextDouble() /
|
|
((assistanceLevel + (passNumber / 10.0)) * 5);
|
|
|
|
if (outcome < 0.51)
|
|
{
|
|
style += difficultyModifier;
|
|
return ActionResult.FightContinues;
|
|
}
|
|
else
|
|
return ActionResult.BullGoresPlayer;
|
|
}
|
|
|
|
ActionResult TryKill(RiskLevel riskLevel)
|
|
{
|
|
var luck = bullStrength * 10 * random.NextDouble() / (assistanceLevel * 5 * passNumber);
|
|
|
|
return ((riskLevel == RiskLevel.High && luck > 0.2) || luck > 0.8) ?
|
|
ActionResult.BullGoresPlayer : ActionResult.PlayerKillsBull;
|
|
}
|
|
|
|
ActionResult Panic() =>
|
|
ActionResult.BullGoresPlayer;
|
|
|
|
ActionResult TrySurvive()
|
|
{
|
|
if (random.Next(2) == 0)
|
|
{
|
|
bravery = 1.5;
|
|
return ActionResult.BullKillsPlayer;
|
|
}
|
|
else
|
|
return ActionResult.FightContinues;
|
|
}
|
|
|
|
ActionResult Flee()
|
|
{
|
|
bravery = 0.0;
|
|
return ActionResult.PlayerFlees;
|
|
}
|
|
|
|
ActionResult IgnoreInjury(Action action)
|
|
{
|
|
if (random.Next(2) == 0)
|
|
{
|
|
bravery = 2.0;
|
|
return action == Action.Dodge ? ActionResult.FightContinues : ActionResult.Draw;
|
|
}
|
|
else
|
|
return ActionResult.BullGoresPlayer;
|
|
}
|
|
|
|
Reward GetReward()
|
|
{
|
|
var score = CalculateScore();
|
|
|
|
if (score * random.NextDouble() < 2.4)
|
|
return Reward.Nothing;
|
|
else
|
|
if (score * random.NextDouble() < 4.9)
|
|
return Reward.OneEar;
|
|
else
|
|
if (score * random.NextDouble() < 7.4)
|
|
return Reward.TwoEars;
|
|
else
|
|
return Reward.CarriedFromRing;
|
|
}
|
|
|
|
double CalculateScore()
|
|
{
|
|
var score = 4.5;
|
|
|
|
// Style
|
|
score += style / 6;
|
|
|
|
// Assisstance
|
|
score -= assistanceLevel * 2.5;
|
|
|
|
// Courage
|
|
score += 4 * bravery;
|
|
|
|
// Kill bonus
|
|
score += (result == ActionResult.PlayerKillsBull) ? 4 : 2;
|
|
|
|
// Match length
|
|
score -= Math.Pow(passNumber, 2) / 120;
|
|
|
|
// Difficulty
|
|
score -= (int)bullQuality;
|
|
|
|
return score;
|
|
}
|
|
}
|
|
}
|
|
}
|