Nankatsu High School Team's first Practice
MMMH, REAL CODE
This RUNS! Playful premises aside, this is a functioning showcase of fennecs principles.
Get comfy, grab a cup of Java CoffeeScript Visual J# whatever, and get your paws dirty playing around in the code! It's good fun!
All .csproj
and .cs
files are over here on Github!
Premise
Japan's least coordinated soccer team tries to score a goal. They're just kids, all running after the ball at once. Only one of them is kind of a good shot, some kid named "Tsubasa"...
We create a team of 11 Entities with Player
, Name
, Talent
, and Position
Components, and a ball entity with Ball
and Position
Components.
In our "game" loop, we get the current position of our ball Entity, and let each player Entity run after it. If they get close enough, they kick the ball to a new position. As soon as the only player with a truthy Talent
Component scores, the match ends with a golden goal.
Recipe
// Tsubasa.cs (type declarations at bottom of file)
using fennecs;
using Name = string;
if (!Console.IsOutputRedirected) Console.Clear();
Console.WriteLine("School's out!");
// Meet the Team!
Name[] names =
[
"Kojiro", "Genzo", "Taro", "Hikaru", "Jun",
"Shingo", "Ryo", "Takeshi", "Masao", "Kazuo",
];
var world = new World();
var random = new Random(10);
foreach (var name in names)
world.Spawn()
.Add<Player>()
.Add(name)
.Add<Talent>(data: false)
.Add<Position>(RandomRadius(radius: 25));
// Meet our Star!
world.Spawn()
.Add<Player>()
.Add("Tsubasa")
.Add<Talent>(data: true)
.Add<Position>(new Vector2(x: 0, y: 50));
var ball = world.Spawn()
.Add<Ball>()
.Add<Position>(new Vector2(x: 0, y: 0));
var team = world
.Query<Name, Position, Talent>()
.Has<Player>()
.Stream();
// Game on! This is our "Game" Loop.
var kicked = false;
var goldenGoal = false;
do
{
if (kicked)
{
Thread.Sleep(millisecondsTimeout: 400);
kicked = false;
}
Thread.Sleep(millisecondsTimeout: 200);
if (!Console.IsOutputRedirected) Console.Clear();
// Update each player on the field.
team.For((
ref Name playerName,
ref Position playerPosition,
ref Talent playerTalent
)
=>
{
ref var ballPosition = ref ball.Ref<Position>();
var direction = ballPosition.value - playerPosition.value;
if (direction.LengthSquared() > 1f)
{
var dash = direction * (random.NextSingle() * .9f + 0.1f);
playerPosition += dash;
Console.WriteLine($"{playerName,15} runs towards the ball!" +
$" ... d = {direction.Length():f2}m");
return;
}
ballPosition += RandomRadius(radius: 5, onCircle: true);
kicked = true;
Console.WriteLine($">>>>> {playerName} kicks the ball!");
if (!playerTalent) return;
Console.WriteLine($"***** {playerName} scores!!!".ToUpper());
goldenGoal = true;
});
} while (!goldenGoal);
Console.WriteLine("..... Hit the Showers, boys! You've earned it.");
return;
#region Components and Maths
// Math Helpers
Vector2 RandomRadius(float radius, bool onCircle = false)
{
var result = new Vector2(
random.NextSingle() * radius,
random.NextSingle() * radius);
if (onCircle) return Vector2.Normalize(result) * radius;
return result;
}
// "tag" (zero-size type) identifying an Entity as a Player
internal struct Player;
// "tag" (zero-size type) identifying an Entity as a Ball
internal struct Ball;
// Component that represents a truthy value for a player's talent.
internal readonly struct Talent(bool value)
{
private bool value { get; } = value;
public static implicit operator Talent(bool value) => new(value);
public static implicit operator bool(Talent talent) => talent.value;
}
// Position component wrapping a Vector2.
internal readonly struct Position(Vector2 value)
{
public Vector2 value { get; } = value;
public static implicit operator Vector2(Position other) => other.value;
public static implicit operator Position(Vector2 value) => new(value);
public override string ToString() => value.ToString();
}
#endregion
School's out!
Kojiro runs towards the ball! ... d = 30,29m
Genzo runs towards the ball! ... d = 25,67m
Taro runs towards the ball! ... d = 19,50m
Hikaru runs towards the ball! ... d = 15,00m
Jun runs towards the ball! ... d = 24,62m
Shingo runs towards the ball! ... d = 17,85m
Ryo runs towards the ball! ... d = 21,05m
Takeshi runs towards the ball! ... d = 25,50m
Masao runs towards the ball! ... d = 5,15m
Kazuo runs towards the ball! ... d = 24,15m
Tsubasa runs towards the ball! ... d = 50,00m
Kojiro runs towards the ball! ... d = 23,29m
Genzo runs towards the ball! ... d = 16,14m
Taro runs towards the ball! ... d = 5,58m
Hikaru runs towards the ball! ... d = 6,40m
Jun runs towards the ball! ... d = 7,09m
>>>>> Shingo kicks the ball!
Ryo runs towards the ball! ... d = 4,57m
Takeshi runs towards the ball! ... d = 2,55m
Masao runs towards the ball! ... d = 3,24m
Kazuo runs towards the ball! ... d = 14,25m
Tsubasa runs towards the ball! ... d = 5,08m
Kojiro runs towards the ball! ... d = 14,17m
Genzo runs towards the ball! ... d = 5,06m
Taro runs towards the ball! ... d = 1,38m
Hikaru runs towards the ball! ... d = 1,55m
Jun runs towards the ball! ... d = 3,91m
Shingo runs towards the ball! ... d = 4,34m
Ryo runs towards the ball! ... d = 3,88m
>>>>> Takeshi kicks the ball!
Masao runs towards the ball! ... d = 7,19m
Kazuo runs towards the ball! ... d = 2,29m
Tsubasa runs towards the ball! ... d = 5,45m
Kojiro runs towards the ball! ... d = 3,29m
Genzo runs towards the ball! ... d = 2,64m
Taro runs towards the ball! ... d = 5,40m
Hikaru runs towards the ball! ... d = 5,14m
Jun runs towards the ball! ... d = 5,83m
Shingo runs towards the ball! ... d = 8,38m
Ryo runs towards the ball! ... d = 4,27m
Takeshi runs towards the ball! ... d = 5,76m
>>>>> Masao kicks the ball!
Kazuo runs towards the ball! ... d = 4,98m
Tsubasa runs towards the ball! ... d = 8,63m
Kojiro runs towards the ball! ... d = 4,08m
Genzo runs towards the ball! ... d = 6,08m
Taro runs towards the ball! ... d = 8,59m
Hikaru runs towards the ball! ... d = 9,28m
Jun runs towards the ball! ... d = 6,05m
Shingo runs towards the ball! ... d = 6,69m
Ryo runs towards the ball! ... d = 6,55m
Takeshi runs towards the ball! ... d = 9,25m
Masao runs towards the ball! ... d = 5,62m
Kazuo runs towards the ball! ... d = 1,92m
>>>>> Tsubasa kicks the ball!
***** TSUBASA SCORES!!!
..... Hit the Showers, boys! You've earned it.