mirror of
https://github.com/coding-horror/basic-computer-games.git
synced 2025-12-23 15:37:51 -08:00
Reworked Bull Fight in a reactive style
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
|
||||
220
17 Bullfight/csharp/src/BullFight.cs
Normal file
220
17 Bullfight/csharp/src/BullFight.cs
Normal file
@@ -0,0 +1,220 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -93,10 +93,15 @@ namespace Game
|
||||
/// <returns>
|
||||
/// True if the player flees; otherwise, false.
|
||||
/// </returns>
|
||||
public static bool PlayerRunsFromRing()
|
||||
public static bool GetPlayerRunsFromRing()
|
||||
{
|
||||
View.PromptRunFromRing();
|
||||
return GetYesOrNo();
|
||||
|
||||
var playerFlees = GetYesOrNo();
|
||||
if (!playerFlees)
|
||||
View.ShowPlayerFoolhardy();
|
||||
|
||||
return playerFlees;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
7
17 Bullfight/csharp/src/Events/BullCharging.cs
Normal file
7
17 Bullfight/csharp/src/Events/BullCharging.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace Game.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates that the bull is charing the player.
|
||||
/// </summary>
|
||||
public sealed record BullCharging(int PassNumber) : Event;
|
||||
}
|
||||
7
17 Bullfight/csharp/src/Events/Event.cs
Normal file
7
17 Bullfight/csharp/src/Events/Event.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace Game.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// Common base class for all events in the game.
|
||||
/// </summary>
|
||||
public abstract record Event();
|
||||
}
|
||||
7
17 Bullfight/csharp/src/Events/MatchCompleted.cs
Normal file
7
17 Bullfight/csharp/src/Events/MatchCompleted.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace Game.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates that the fight has completed.
|
||||
/// </summary>
|
||||
public sealed record MatchCompleted(ActionResult Result, bool ExtremeBravery, Reward Reward) : Event;
|
||||
}
|
||||
13
17 Bullfight/csharp/src/Events/MatchStarted.cs
Normal file
13
17 Bullfight/csharp/src/Events/MatchStarted.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace Game.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates that a new match has started.
|
||||
/// </summary>
|
||||
public sealed record MatchStarted(
|
||||
Quality BullQuality,
|
||||
Quality ToreadorePerformance,
|
||||
Quality PicadorePerformance,
|
||||
int ToreadoresKilled,
|
||||
int PicadoresKilled,
|
||||
int HorsesKilled) : Event;
|
||||
}
|
||||
7
17 Bullfight/csharp/src/Events/PlayerGored.cs
Normal file
7
17 Bullfight/csharp/src/Events/PlayerGored.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace Game.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates that the player has been gored by the bull.
|
||||
/// </summary>
|
||||
public sealed record PlayerGored(bool Panicked, bool FirstGoring) : Event;
|
||||
}
|
||||
7
17 Bullfight/csharp/src/Events/PlayerSurvived.cs
Normal file
7
17 Bullfight/csharp/src/Events/PlayerSurvived.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace Game.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates that the player has survived being gored by the bull.
|
||||
/// </summary>
|
||||
public sealed record PlayerSurvived() : Event;
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
namespace Game
|
||||
{
|
||||
/// <summary>
|
||||
/// Stores the initial conditions of a match.
|
||||
/// </summary>
|
||||
public record MatchConditions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the quality of the bull.
|
||||
/// </summary>
|
||||
public Quality BullQuality { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the quality of help received from the toreadores.
|
||||
/// </summary>
|
||||
public Quality ToreadorePerformance { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the quality of help received from the picadores.
|
||||
/// </summary>
|
||||
public Quality PicadorePerformance { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of toreadores killed while preparing for the
|
||||
/// final round.
|
||||
/// </summary>
|
||||
public int ToreadoresKilled { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of picadores killed while preparing for the
|
||||
/// final round.
|
||||
/// </summary>
|
||||
public int PicadoresKilled { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of horses killed while preparing for the final
|
||||
/// round.
|
||||
/// </summary>
|
||||
public int HorsesKilled { get; init; }
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
namespace Game
|
||||
{
|
||||
/// <summary>
|
||||
/// Stores the current state of the match.
|
||||
/// </summary>
|
||||
public record MatchState(MatchConditions Conditions)
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the number of times the bull has charged.
|
||||
/// </summary>
|
||||
public int PassNumber { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Measures the player's bravery during the match.
|
||||
/// </summary>
|
||||
public double Bravery { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Measures how much style the player showed during the match.
|
||||
/// </summary>
|
||||
public double Style { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the result of the player's last action.
|
||||
/// </summary>
|
||||
public ActionResult Result { get; init; }
|
||||
}
|
||||
}
|
||||
48
17 Bullfight/csharp/src/Mediator.cs
Normal file
48
17 Bullfight/csharp/src/Mediator.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Game
|
||||
{
|
||||
/// <summary>
|
||||
/// Facilitates sending messages between the two game loops.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This class serves as a little piece of glue in between the main program
|
||||
/// loop and the bull fight coroutine. When the main program calls one of
|
||||
/// its methods, the mediator creates the appropriate input data that the
|
||||
/// bull fight coroutine later retrieves with <see cref="GetInput{T}"/>.
|
||||
/// </remarks>
|
||||
public class Mediator
|
||||
{
|
||||
private object? m_input;
|
||||
|
||||
public void Dodge(RiskLevel riskLevel) =>
|
||||
m_input = (Action.Dodge, riskLevel);
|
||||
|
||||
public void Kill(RiskLevel riskLevel) =>
|
||||
m_input = (Action.Kill, riskLevel);
|
||||
|
||||
public void Panic() =>
|
||||
m_input = (Action.Panic, default(RiskLevel));
|
||||
|
||||
public void RunFromRing() =>
|
||||
m_input = true;
|
||||
|
||||
public void ContinueFighting() =>
|
||||
m_input = false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the next input from the user.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">
|
||||
/// The type of input to receive.
|
||||
/// </typeparam>
|
||||
public T GetInput<T>()
|
||||
{
|
||||
Debug.Assert(m_input is not null, "No input received");
|
||||
Debug.Assert(m_input.GetType() == typeof(T), "Invalid input received");
|
||||
var result = (T)m_input;
|
||||
m_input = null;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,4 @@
|
||||
using System;
|
||||
|
||||
namespace Game
|
||||
namespace Game
|
||||
{
|
||||
class Program
|
||||
{
|
||||
@@ -8,49 +6,49 @@ namespace Game
|
||||
{
|
||||
Controller.StartGame();
|
||||
|
||||
var random = new Random();
|
||||
var match = Rules.StartMatch(random);
|
||||
View.ShowStartingConditions(match.Conditions);
|
||||
|
||||
while (match.Result == ActionResult.FightContinues)
|
||||
var mediator = new Mediator();
|
||||
foreach (var evt in BullFight.Begin(mediator))
|
||||
{
|
||||
match = match with { PassNumber = match.PassNumber + 1 };
|
||||
|
||||
View.StartOfPass(match.PassNumber);
|
||||
|
||||
var (action, riskLevel) = Controller.GetPlayerIntention(match.PassNumber);
|
||||
match = action switch
|
||||
switch (evt)
|
||||
{
|
||||
Action.Dodge => Rules.TryDodge(random, riskLevel, match),
|
||||
Action.Kill => Rules.TryKill(random, riskLevel, match),
|
||||
_ => Rules.Panic(match)
|
||||
};
|
||||
case Events.MatchStarted matchStarted:
|
||||
View.ShowStartingConditions(matchStarted);
|
||||
break;
|
||||
|
||||
var first = true;
|
||||
while (match.Result == ActionResult.BullGoresPlayer)
|
||||
{
|
||||
View.ShowPlayerGored(action == Action.Panic, first);
|
||||
first = false;
|
||||
case Events.BullCharging bullCharging:
|
||||
View.ShowStartOfPass(bullCharging.PassNumber);
|
||||
var (action, riskLevel) = Controller.GetPlayerIntention(bullCharging.PassNumber);
|
||||
switch (action)
|
||||
{
|
||||
case Action.Dodge:
|
||||
mediator.Dodge(riskLevel);
|
||||
break;
|
||||
case Action.Kill:
|
||||
mediator.Kill(riskLevel);
|
||||
break;
|
||||
case Action.Panic:
|
||||
mediator.Panic();
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
match = Rules.TrySurvive(random, match);
|
||||
if (match.Result == ActionResult.FightContinues)
|
||||
{
|
||||
case Events.PlayerGored playerGored:
|
||||
View.ShowPlayerGored(playerGored.Panicked, playerGored.FirstGoring);
|
||||
break;
|
||||
|
||||
case Events.PlayerSurvived:
|
||||
View.ShowPlayerSurvives();
|
||||
|
||||
if (Controller.PlayerRunsFromRing())
|
||||
{
|
||||
match = Rules.Flee(match);
|
||||
}
|
||||
if (Controller.GetPlayerRunsFromRing())
|
||||
mediator.RunFromRing();
|
||||
else
|
||||
{
|
||||
View.ShowPlayerFoolhardy();
|
||||
match = Rules.IgnoreInjury(random, action, match);
|
||||
}
|
||||
}
|
||||
mediator.ContinueFighting();
|
||||
break;
|
||||
|
||||
case Events.MatchCompleted matchCompleted:
|
||||
View.ShowFinalResult(matchCompleted.Result, matchCompleted.ExtremeBravery, matchCompleted.Reward);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
View.ShowFinalResult(match.Result, match.Bravery, Rules.GetReward(random, match));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,258 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Game
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides functions implementing the rules of the game.
|
||||
/// </summary>
|
||||
public static class Rules
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the state of a new match.
|
||||
/// </summary>
|
||||
/// <param name="random">
|
||||
/// The random number generator.
|
||||
/// </param>
|
||||
public static MatchState StartMatch(Random random)
|
||||
{
|
||||
var bullQuality = GetBullQuality();
|
||||
var toreadorePerformance = GetHelpQuality();
|
||||
var picadorePerformance = GetHelpQuality();
|
||||
|
||||
var conditions = new MatchConditions
|
||||
{
|
||||
BullQuality = bullQuality,
|
||||
ToreadorePerformance = toreadorePerformance,
|
||||
PicadorePerformance = picadorePerformance,
|
||||
ToreadoresKilled = GetHumanCasualties(toreadorePerformance),
|
||||
PicadoresKilled = GetHumanCasualties(picadorePerformance),
|
||||
HorsesKilled = GetHorseCasualties(picadorePerformance)
|
||||
};
|
||||
|
||||
return new MatchState(conditions)
|
||||
{
|
||||
Bravery = 1.0,
|
||||
Style = 1.0
|
||||
};
|
||||
|
||||
Quality GetBullQuality() =>
|
||||
(Quality)random.Next(1, 6);
|
||||
|
||||
Quality GetHelpQuality() =>
|
||||
((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
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines the result when the player attempts to dodge the bull.
|
||||
/// </summary>
|
||||
/// <param name="random">
|
||||
/// The random number generator.
|
||||
/// </param>
|
||||
/// <param name="riskLevel">
|
||||
/// The level of risk in the dodge manoeuvre chosen.
|
||||
/// </param>
|
||||
/// <param name="match">
|
||||
/// The current match state.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// The updated match state.
|
||||
/// </returns>
|
||||
public static MatchState TryDodge(Random random, RiskLevel riskLevel, MatchState match)
|
||||
{
|
||||
var difficultyModifier = riskLevel switch
|
||||
{
|
||||
RiskLevel.High => 3.0,
|
||||
RiskLevel.Medium => 2.0,
|
||||
_ => 0.5
|
||||
};
|
||||
|
||||
var outcome = (GetBullStrength(match) + (difficultyModifier / 10)) * random.NextDouble() /
|
||||
((GetAssisstance(match) + (match.PassNumber / 10.0)) * 5);
|
||||
|
||||
return outcome < 0.51 ?
|
||||
match with { Result = ActionResult.FightContinues, Style = match.Style + difficultyModifier } :
|
||||
match with { Result = ActionResult.BullGoresPlayer };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines the result when the player attempts to kill the bull.
|
||||
/// </summary>
|
||||
/// <param name="random">
|
||||
/// The random number generator.
|
||||
/// </param>
|
||||
/// <param name="riskLevel">
|
||||
/// The level of risk in the manoeuvre chosen.
|
||||
/// </param>
|
||||
/// <param name="match">
|
||||
/// The current match state.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// The updated match state.
|
||||
/// </returns>
|
||||
public static MatchState TryKill(Random random, RiskLevel riskLevel, MatchState match)
|
||||
{
|
||||
var K = GetBullStrength(match) * 10 * random.NextDouble() / (GetAssisstance(match) * 5 * match.PassNumber);
|
||||
|
||||
return ((riskLevel == RiskLevel.High && K > 0.2) || K > 0.8) ?
|
||||
match with { Result = ActionResult.BullGoresPlayer } :
|
||||
match with { Result = ActionResult.PlayerKillsBull };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the player survives being gored by the bull.
|
||||
/// </summary>
|
||||
/// <param name="random">
|
||||
/// The random number generator.
|
||||
/// </param>
|
||||
/// <param name="match">
|
||||
/// The current match state.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// The updated match state.
|
||||
/// </returns>
|
||||
public static MatchState TrySurvive(Random random, MatchState match) =>
|
||||
(random.Next(2) == 0) ?
|
||||
match with { Result = ActionResult.BullKillsPlayer, Bravery = 1.5 } :
|
||||
match with { Result = ActionResult.FightContinues };
|
||||
|
||||
/// <summary>
|
||||
/// Determines the result when the player panics and fails to do anything.
|
||||
/// </summary>
|
||||
/// <param name="match">
|
||||
/// The match state.
|
||||
/// </param>
|
||||
public static MatchState Panic(MatchState match) =>
|
||||
match with { Result = ActionResult.BullGoresPlayer };
|
||||
|
||||
/// <summary>
|
||||
/// Determines the result when the player flees the ring.
|
||||
/// </summary>
|
||||
/// <param name="match">
|
||||
/// The current match state.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// The updated match state.
|
||||
/// </returns>
|
||||
public static MatchState Flee(MatchState match) =>
|
||||
match with { Result = ActionResult.PlayerFlees, Bravery = 0.0 };
|
||||
|
||||
/// <summary>
|
||||
/// Determines the result when the player decides to continue fighting
|
||||
/// following an injury.
|
||||
/// </summary>
|
||||
/// <param name="random">
|
||||
/// The random number generator.
|
||||
/// </param>
|
||||
/// <param name="action">
|
||||
/// The action the player took that lead to the injury.
|
||||
/// </param>
|
||||
/// <param name="match">
|
||||
/// The current match state.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// The updated match state.
|
||||
/// </returns>
|
||||
public static MatchState IgnoreInjury(Random random, Action action, MatchState match) =>
|
||||
(random.Next(2) == 0) ?
|
||||
match with { Result = action == Action.Dodge ? ActionResult.FightContinues : ActionResult.Draw, Bravery = 2.0 } :
|
||||
match with { Result = ActionResult.BullGoresPlayer };
|
||||
|
||||
/// <summary>
|
||||
/// Gets the player's reward for completing a match.
|
||||
/// </summary>
|
||||
/// <param name="random">
|
||||
/// The random number generator.
|
||||
/// </param>
|
||||
/// <param name="match">
|
||||
/// The final match state.
|
||||
/// </param>
|
||||
public static Reward GetReward(Random random, MatchState match)
|
||||
{
|
||||
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 += match.Style / 6;
|
||||
|
||||
// Assisstance
|
||||
score -= GetAssisstance(match) * 2.5;
|
||||
|
||||
// Courage
|
||||
score += 4 * match.Bravery;
|
||||
|
||||
// Kill bonus
|
||||
score += (match.Result == ActionResult.PlayerKillsBull) ? 4 : 2;
|
||||
|
||||
// Match length
|
||||
score -= Math.Pow(match.PassNumber, 2) / 120;
|
||||
|
||||
// Difficulty
|
||||
score -= (int)match.Conditions.BullQuality;
|
||||
|
||||
return score;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the strength of the bull in a match.
|
||||
/// </summary>
|
||||
private static double GetBullStrength(MatchState match) =>
|
||||
6 - (int)match.Conditions.BullQuality;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the amount of assistance received from the toreadores and
|
||||
/// picadores in a match.
|
||||
/// </summary>
|
||||
private static double GetAssisstance(MatchState match) =>
|
||||
GetPerformanceBonus(match.Conditions.ToreadorePerformance) +
|
||||
GetPerformanceBonus(match.Conditions.PicadorePerformance);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the amount of assistance rendered by a performance of the
|
||||
/// given quality.
|
||||
/// </summary>
|
||||
private static double GetPerformanceBonus(Quality performance) =>
|
||||
(6 - (int)performance) * 0.1;
|
||||
}
|
||||
}
|
||||
@@ -47,22 +47,22 @@ namespace Game
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
public static void ShowStartingConditions(MatchConditions conditions)
|
||||
public static void ShowStartingConditions(Events.MatchStarted matchStarted)
|
||||
{
|
||||
ShowBullQuality();
|
||||
ShowHelpQuality("TOREADORES", conditions.ToreadorePerformance, conditions.ToreadoresKilled, 0);
|
||||
ShowHelpQuality("PICADORES", conditions.PicadorePerformance, conditions.PicadoresKilled, conditions.HorsesKilled);
|
||||
ShowHelpQuality("TOREADORES", matchStarted.ToreadorePerformance, matchStarted.ToreadoresKilled, 0);
|
||||
ShowHelpQuality("PICADORES", matchStarted.PicadorePerformance, matchStarted.PicadoresKilled, matchStarted.HorsesKilled);
|
||||
|
||||
void ShowBullQuality()
|
||||
{
|
||||
Console.WriteLine($"YOU HAVE DRAWN A {QualityString[(int)conditions.BullQuality - 1]} BULL.");
|
||||
Console.WriteLine($"YOU HAVE DRAWN A {QualityString[(int)matchStarted.BullQuality - 1]} BULL.");
|
||||
|
||||
if (conditions.BullQuality > Quality.Poor)
|
||||
if (matchStarted.BullQuality > Quality.Poor)
|
||||
{
|
||||
Console.WriteLine("YOU'RE LUCKY");
|
||||
}
|
||||
else
|
||||
if (conditions.BullQuality < Quality.Good)
|
||||
if (matchStarted.BullQuality < Quality.Good)
|
||||
{
|
||||
Console.WriteLine("GOOD LUCK. YOU'LL NEED IT.");
|
||||
Console.WriteLine();
|
||||
@@ -86,9 +86,11 @@ namespace Game
|
||||
case Quality.Poor:
|
||||
if (horsesKilled > 0)
|
||||
Console.WriteLine($"ONE OF THE HORSES OF THE {helperName} WAS KILLED.");
|
||||
|
||||
|
||||
if (helpersKilled > 0)
|
||||
Console.WriteLine($"ONE OF THE {helperName} WAS KILLED.");
|
||||
else
|
||||
Console.WriteLine($"NO {helperName} WERE KILLED.");
|
||||
break;
|
||||
|
||||
case Quality.Awful:
|
||||
@@ -101,7 +103,7 @@ namespace Game
|
||||
}
|
||||
}
|
||||
|
||||
public static void StartOfPass(int passNumber)
|
||||
public static void ShowStartOfPass(int passNumber)
|
||||
{
|
||||
Console.WriteLine();
|
||||
Console.WriteLine();
|
||||
@@ -129,7 +131,7 @@ namespace Game
|
||||
Console.WriteLine("YOU ARE BRAVE. STUPID, BUT BRAVE.");
|
||||
}
|
||||
|
||||
public static void ShowFinalResult(ActionResult result, double bravery, Reward reward)
|
||||
public static void ShowFinalResult(ActionResult result, bool extremeBravery, Reward reward)
|
||||
{
|
||||
Console.WriteLine();
|
||||
Console.WriteLine();
|
||||
@@ -156,7 +158,7 @@ namespace Game
|
||||
}
|
||||
else
|
||||
{
|
||||
if (bravery == 2) // You were gored by the bull but survived (and did not later die or flee)
|
||||
if (extremeBravery)
|
||||
Console.WriteLine("THE CROWD CHEERS WILDLY!");
|
||||
else
|
||||
if (result == ActionResult.PlayerKillsBull)
|
||||
|
||||
Reference in New Issue
Block a user