Skip to content

Nankatsu High School Team's first Practice

Neofox: floof_mug 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

cs
// 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
txt
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.

fennecs is released under the MIT License. Neofox is released under the CC BY-NC-SA 4.0 License.