Tag Archives: Refactor

Creating a Game in Blazor – Part 3 – Graphics

In this post I started writing a game in Blazor. In this last post, I covered how we could use the keyboard to move an object around, and how we could apply gravity.

In this post, we’re going to refactor, and we’re going to replace the word test with something approximating Willy.

Refactor

Just because we’re writing a game in Blazor is no reason not to use the IoC container in order to better structure the code. I’m not going to cover all of the refactoring here; however, the changes are here.

We’ve added a sub-directory called GameLogic which contains all the relevant classes:

At some point in the future, we may separate this directory into its own project; but for now, we have four classes:

Controls – this handles the user input.

Graphics – this will handle the manipulation of the graphics.

Player – this handles behaviour of the player.

World – this deals with the things such as collisions, gravity, etc.

WorldSettings – these are just a list of variables that control how things move. In the original game, there was a POKE that meant you could jump so high that you went into the next room.

I won’t cover what’s actually in these classes – it’s essentially what was in the main Razor file. I will cover the change to the razor file itself:

@page "/"
@using System.Timers
@using BlazorGame.GameLogic
@inject IPlayer Player
@inject IControls Controls
@inject IWorld World

<div @onkeydown="HandleKeyDown" @onkeyup="HandleKeyUp" @onkeydown:preventDefault 
    style="background-color: #000000; width: 80vw; height: 80vh; margin: auto"
    tabindex="0" @ref="mainDiv">
    <div style="color: white; top: @(Player.Top)px; left: @(Player.Left)px; width: 20px; position: relative">test</div>
</div>

@code {

    private ElementReference mainDiv;    
    private Timer _timer;

    private void HandleKeyUp(KeyboardEventArgs e) =>
        Controls.KeyUp(e.Code);    

    private void HandleKeyDown(KeyboardEventArgs e) =>    
        Controls.KeyDown(e.Code);    

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            await mainDiv.FocusAsync();
        }
    }

    protected override Task OnInitializedAsync()
    {
        _timer = new Timer();
        _timer.Interval = 16;
        _timer.Elapsed += TimerElapsed;
        _timer.AutoReset = true;
        _timer.Enabled = true;        

        return base.OnInitializedAsync();
    }

    private void TimerElapsed(Object source, System.Timers.ElapsedEventArgs e)
    {
        Update();
        Draw();
    }

    private void Update()
    {
        World.ApplyPhysics();
    }

    private void Draw() => 
        this.StateHasChanged();    

}

This is much more terse than before, and delegates most of its functionality to the classes that we described above. You’ll see at the top that we @inject those classes into the page.

Finally, the classes are registered in Program.cs:

        public static async Task Main(string[] args)
        {
            var builder = WebAssemblyHostBuilder.CreateDefault(args);
            builder.RootComponents.Add<App>("#app");

            builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
            builder.Services.AddSingleton<IWorld, World>();
            builder.Services.AddSingleton<IPlayer, Player>();
            builder.Services.AddSingleton<IControls, Controls>();

            await builder.Build().RunAsync();
        }

This is by no means the last refactoring that we’ll do, but it’s perhaps the last one that will make it into its own section of a post.

Graphics

For the graphics, I spent a while trying to get various graphics libraries to work cross platform. I finally realised that, not only did I not need a graphics library, but that I’d solved this issue before – well, more or less. The answer was to use CSS to animate the image. The very first step was to add a sprite sheet; which I got from here, and since Jet Set Willy is the same character as manic miner (with just one pixel difference in the hat), I managed to add a sprite sheet:

The next change was to alter the HTML in Game.razor slightly:

<div @onkeydown="HandleKeyDown" @onkeyup="HandleKeyUp" @onkeydown:preventDefault 
    style="background-color: #000000; width: 80vw; height: 80vh; margin: auto"
    tabindex="0" @ref="mainDiv">
    <div style="color: white; top: @(Player.Top)px; left: @(Player.Left)px; width: 16px; height: 17px; overflow: hidden; position: relative">
        <img 
            src="/images/Willy-Sprite-Sheet.png" 
            style="margin: 0 @(Graphics.PlayerOffset)px; transform: scaleX(@(Graphics.PlayerDirection))" />
    </div>
</div>

There’s a few things to unpick here. Let’s start with the interaction between the div and the img. Essentially, we’re using the div as a window into the image; similar to this:

Both the margin and transform are set to bound properties of a new Graphics class, which we’ll come to in a second; but first, let’s see the other change to this file:

    private void Update()
    {
        World.ApplyPhysics();
        if (Player.IsWalking)
        {
            Graphics.CyclePlayer();
        }
    }

This allows us to change the bound variables that we mentioned earlier.

Now that we’ve seen the changes to the main razor file, let’s see the new Graphics class:

    public class Graphics : IGraphics
    {
        private readonly IPlayer _player;
        private int _playerOffset = 0;
        private DateTime _lastUpdate = DateTime.MinValue;

        public Graphics(IPlayer player)
        {
            _player = player;
        }

        public int PlayerOffset => _playerOffset;

        public int PlayerDirection =>
            _player switch
            {
                { IsWalkingLeft: true } => -1,
                { IsWalkingRight: true } => 1,
                _ => 0
            };
        
        public void CyclePlayer()
        {
            if (_lastUpdate.AddMilliseconds(100) > DateTime.Now) return;

            if (_playerOffset > -48)
                _playerOffset -= 16;
            else
                _playerOffset = 0;

            _lastUpdate = DateTime.Now;
        }
    }

This is essentially a utility, or helper class. It encapsulates details about the graphics that are displayed, and uses the Player class to do so. Most of it is relatively self-explanatory, with the possible exception of CyclePlayer which moves the offset that we mentioned earlier no more frequently that every 100ms.

That’s pretty much it; we now have a walking Willy:

What’s Next?

In the next post, we’ll try to add a platform, and some collision logic.

References

https://www.spriters-resource.com/fullview/113060/

https://gunnarpeipman.com/csharp-reading-embedded-files/

https://www.hanselman.com/blog/how-do-you-use-systemdrawing-in-net-core

https://www.davidguida.net/blazor-gamedev-part-11-improved-assets-loading/

https://stackoverflow.com/questions/493296/css-display-an-image-resized-and-cropped

Scientist.Net

The purpose of the library is to allow you to try new code in a small sample of production usage – effectively, testing in production. The idea being that if you’re refactoring an important part of the system, you can re-write, and then call your new code on occasion; it’s logged and, should it reveal a major issue, can be simply switched off.

The first port of call is the GitHub repository:

Which adds this:

The following is some test code; there are two methods, an old, slow method, and a refactored new method:

class LegacyCode
{
    public void OldMethod1()
    {
        System.Threading.Thread.Sleep(1000);
        System.Console.WriteLine("This is old code");
    }
}
class RefactoredCode
{
    public void RefactoredNewMethod()
    {
        System.Console.WriteLine("RefactoredNewMethod called");
    }
}
static void Main(string[] args)
{
    System.Console.WriteLine("Start Test");
 
    for (int i = 1; i <= 100; i++)
    {
        Scientist.Science<bool>("Test", testNewCode =>
        {
            testNewCode.Use(() =>
            {
                new LegacyCode().OldMethod1();
                return true;
            });
            testNewCode.Try(() =>
            {
                new RefactoredCode().RefactoredNewMethod();
                return true;
            });
        });
    }
 
    System.Console.ReadLine();
}

In the code above you’ll notice that the call to Scientist looks a little forced – that’s because it insists on a return value from the experiments (and experiment being a trial of new code).

As you can see, Scientist is managing the calls between the new and old method:

One thing that wasn’t immediately obvious to me here was exactly how / what it does with this; especially given that the Try and Use blocks were not always appearing in a consistent order; the following test revealed it more clearly:

Because the order of the runs are randomly altered, I had assumed that which code was called was also randomly determined; in fact, both code paths are run. This is a hugely important distinction, because if you are changing data in one or the other, you need to factor this in.

Statistics

Scientist collects a number of statistics on the run; to see these, you need to implement an IResultPublisher; for example:

public class ResultPublisher : IResultPublisher
{
    public Task Publish<T, TClean>(Result<T, TClean> result)
    {
        System.Console.WriteLine($"Publishing results for experiment '{result.ExperimentName}'");
        System.Console.WriteLine($"Result: {(result.Matched ? "MATCH" : "MISMATCH")}");
        System.Console.WriteLine($"Control value: {result.Control.Value}");
        System.Console.WriteLine($"Control duration: {result.Control.Duration}");
        foreach (var observation in result.Candidates)
        {
            System.Console.WriteLine($"Candidate name: {observation.Name}");
            System.Console.WriteLine($"Candidate value: {observation.Value}");
            System.Console.WriteLine($"Candidate duration: {observation.Duration}");
        }
 
        return Task.FromResult(0);
    }
}

The code in here is executed for every call:

We’ve clearly sped up the call, but does it still do the same thing?

Matches… and mismatches

There’s a lot of information in the trace above. One thing that Scientist.Net does allow you to do is to compare the results of a function; let’s change the initial experiment a little:

public bool OldMethod1(int test)
{            
    System.Threading.Thread.Sleep(1000);
    System.Console.WriteLine("This is old code");
    return test >= 50;
}

public bool RefactoredNewMethod(int test)
{
    System.Console.WriteLine("RefactoredNewMethod called");
 
    return test >= 50;
}

for (int i = 1; i <= 100; i++)
{
    var result = Scientist.Science<bool>("Test", testNewCode =>
    {
        testNewCode.Use(() =>
        {
            return new LegacyCode().OldMethod1(i);                        
        });
        testNewCode.Try(() =>
        {
            return new RefactoredCode().RefactoredNewMethod(i);                        
        });
    });
}

Now we’re returning a boolean flag to say that the number is greater or equal to 50, and returning that. Finally, we need to change ResultPublisher (otherwise we won’t be able to see the wood for the trees:

public Task Publish<T, TClean>(Result<T, TClean> result)
{
    if (result.Mismatched)
    {
        System.Console.WriteLine($"Publishing results for experiment '{result.ExperimentName}'");
        System.Console.WriteLine($"Result: {(result.Matched ? "MATCH" : "MISMATCH")}");
        System.Console.WriteLine($"Control value: {result.Control.Value}");
        System.Console.WriteLine($"Control duration: {result.Control.Duration}");
        foreach (var observation in result.Candidates)
        {
            System.Console.WriteLine($"Candidate name: {observation.Name}");
            System.Console.WriteLine($"Candidate value: {observation.Value}");
            System.Console.WriteLine($"Candidate duration: {observation.Duration}");
        }
    }
 
    return Task.FromResult(0);
}

If we run that:

Everything is the same. So, let’s break the new code:

public bool RefactoredNewMethod(int test)
{
    System.Console.WriteLine("RefactoredNewMethod called");
 
    return test > 50;
}

Now we have a bug in the new code, so what happens:

We have a mismatch. The old code is now behaving differently, and so Scientist has identified this.

Summary

I came across this on this episode of .Net Rocks with Phil Haack. There are more features here, too – you can control the way the comparison works, categorise the results, and so forth.

References

http://haacked.com/archive/2016/01/20/scientist/

https://visualstudiomagazine.com/articles/2016/11/01/testing-experimental-code.aspx

https://github.com/github/Scientist.net