GNU/Linux >> Znalost Linux >  >> Panels >> Docker

Serverový GameBoy emulátor pro více hráčů napsaný v .NET Core a Angular

Jednou z velkých radostí ze sdílení a objevování kódu online je, když narazíte na něco tak skutečně epického, tak úžasného , které se musíte prohrabat. Přejděte na https://github.com/axle-h/Retro.Net a zeptejte se sami sebe, proč má tento projekt GitHub pouze 20 hvězdiček?

Alex Haslehurst vytvořil několik retro hardwarových knihoven v open source .NET Core s Angular Front End!

Překlad?

Serverový emulátor Game Boy pro více hráčů. Epické.

Můžete jej spustit během několika minut pomocí

docker run -p 2500:2500 alexhaslehurst/server-side-gameboy

Pak už jen přejděte na http://localhost:2500 a zahrajte si Tetris na původním GameBoyi!

Líbí se mi to z několika důvodů.

Za prvé, miluji jeho pohled:

Podívejte se prosím na můj emulátor GameBoy napsaný v .NET Core; Retro.Net . Ano, emulátor GameBoy napsaný v .NET Core. Proč? Proč ne. Mám v plánu udělat pár zápisů o mých zkušenostech s tímto projektem. Za prvé:proč to byl špatný nápad.

  1. Emulace na .NET
  2. Emulace CPU GameBoy na .NET

Největší problém, se kterým se člověk snaží emulovat CPU s platformou jako .NET, je nedostatek spolehlivého vysoce přesného časování. Zvládá však pěknou emulaci procesoru Z80 od nuly a modeluje věci na nízké úrovni, jako jsou registry, ve velmi vysoké úrovni C#. Líbí se mi, že veřejná třída GameBoyFlagsRegister je věc.;) Dělal jsem podobné věci, když jsem portoval 15 let starý "Tiny CPU" do .NET Core/C#.

Nezapomeňte se podívat na Alexovo extrémně podrobné vysvětlení, jak modeloval mikroprocesor Z80.

Naštěstí je procesor GameBoy, Sharp LR35902, odvozen od oblíbeného a velmi dobře zdokumentovaného Zilog Z80 – mikroprocesoru, který se dnes, více než 40 let po uvedení na trh, neuvěřitelně stále vyrábí.

Z80 je 8bitový mikroprocesor, což znamená, že každá operace se nativně provádí na jednom bajtu. Instrukční sada má nějaké 16bitové operace, ale ty se provádějí pouze jako více cyklů 8bitové logiky. Z80 má 16bitovou širokou adresovou sběrnici, která logicky představuje 64K paměťovou mapu. Data jsou přenášena do CPU přes 8bitovou datovou sběrnici, ale to je irelevantní pro simulaci systému na úrovni stavového stroje. Z80 a Intel 8080, ze kterých je odvozen, mají 256 I/O portů pro přístup k externím periferiím, ale procesor GameBoy žádný nemá – místo toho upřednostňuje I/O mapované v paměti

Nevytvořil pouze emulátor – je jich spousta – ale jedinečně jej provozuje na straně serveru a zároveň umožňuje sdílené ovládací prvky v prohlížeči. "Mezi každým jedinečným rámcem mohou všichni připojení klienti hlasovat o tom, jaký by měl být další ovládací vstup. Server vybere ten s největším počtem hlasů... většinou." Masivní online GameBoy pro více hráčů! Pak vysune další snímek! "Vykreslování GPU je na serveru dokončeno jednou za jedinečný rámec, komprimováno pomocí LZ4 a streamováno všem připojeným klientům přes webové sokety."

Toto je skvělé úložiště učení, protože:

  • má složitou obchodní logiku na straně serveru, ale frontend používá Angular a webové zásuvky a otevřené webové technologie.
  • Je také hezké, že má kompletní vícestupňový soubor Dockerfile, který je sám o sobě skvělým příkladem toho, jak v Dockeru vytvářet aplikace .NET Core i Angular.
  • Rozsáhlé (tisíce) testů jednotek s rámcem Shouldly Assertion Framework a rámcem Moq Mocking Framework.
  • Skvělé příklady použití reaktivního programování
  • Unit Testing na serveru I klientovi pomocí Karma Unit Testing pro Angular

Zde je několik oblíbených elegantních úryvků kódu v tomto obrovském úložišti.

Stisk tlačítka Reactive:

_joyPadSubscription = _joyPadSubject
    .Buffer(FrameLength)
    .Where(x => x.Any())
    .Subscribe(presses =>
                {
                    var (button, name) = presses
                        .Where(x => !string.IsNullOrEmpty(x.name))
                        .GroupBy(x => x.button)
                        .OrderByDescending(grp => grp.Count())
                        .Select(grp => (button: grp.Key, name: grp.Select(x => x.name).First()))
                        .FirstOrDefault();
                    joyPad.PressOne(button);
                    Publish(name, $"Pressed {button}");

                    Thread.Sleep(ButtonPressLength);
                    joyPad.ReleaseAll();
                });

GPU Renderer:

private void Paint()
{
    var renderSettings = new RenderSettings(_gpuRegisters);

    var backgroundTileMap = _tileRam.ReadBytes(renderSettings.BackgroundTileMapAddress, 0x400);
    var tileSet = _tileRam.ReadBytes(renderSettings.TileSetAddress, 0x1000);
    var windowTileMap = renderSettings.WindowEnabled ? _tileRam.ReadBytes(renderSettings.WindowTileMapAddress, 0x400) : new byte[0];

    byte[] spriteOam, spriteTileSet;
    if (renderSettings.SpritesEnabled) {
        // If the background tiles are read from the sprite pattern table then we can reuse the bytes.
        spriteTileSet = renderSettings.SpriteAndBackgroundTileSetShared ? tileSet : _tileRam.ReadBytes(0x0, 0x1000);
        spriteOam = _spriteRam.ReadBytes(0x0, 0xa0);
    }
    else {
        spriteOam = spriteTileSet = new byte[0];
    }

    var renderState = new RenderState(renderSettings, tileSet, backgroundTileMap, windowTileMap, spriteOam, spriteTileSet);

    var renderStateChange = renderState.GetRenderStateChange(_lastRenderState);
    if (renderStateChange == RenderStateChange.None) {
        // No need to render the same frame twice.
        _frameSkip = 0;
        _framesRendered++;
        return;
    }

    _lastRenderState = renderState;
    _tileMapPointer = _tileMapPointer == null ? new TileMapPointer(renderState) : _tileMapPointer.Reset(renderState, renderStateChange);
    var bitmapPalette = _gpuRegisters.LcdMonochromePaletteRegister.Pallette;
    for (var y = 0; y < LcdHeight; y++) {
        for (var x = 0; x < LcdWidth; x++) {
            _lcdBuffer.SetPixel(x, y, (byte) bitmapPalette[_tileMapPointer.Pixel]);

            if (x + 1 < LcdWidth) {
                _tileMapPointer.NextColumn();
            }
        }

        if (y + 1 < LcdHeight){
            _tileMapPointer.NextRow();
        }
    }
    
    _renderer.Paint(_lcdBuffer);
    _frameSkip = 0;
    _framesRendered++;
}

Rámce GameBoy jsou složeny na straně serveru, poté komprimovány a odeslány klientovi přes WebSockets. Má fungující pozadí a skřítky a stále je na čem pracovat.

Raw LCD je plátno HTML5:

<canvas #rawLcd [width]="lcdWidth" [height]="lcdHeight" class="d-none"></canvas>
<canvas #lcd
        [style.max-width]="maxWidth + 'px'"
        [style.max-height]="maxHeight + 'px'"
        [style.min-width]="minWidth + 'px'"
        [style.min-height]="minHeight + 'px'"
        class="lcd"></canvas>

Miluji celý tento projekt, protože má všechno. TypeScript, 2D JavaScript Canvas, retro hry a mnoho dalšího!

const raw: HTMLCanvasElement = this.rawLcdCanvas.nativeElement;
const rawContext: CanvasRenderingContext2D = raw.getContext("2d");
const img = rawContext.createImageData(this.lcdWidth, this.lcdHeight);

for (let y = 0; y < this.lcdHeight; y++) {
  for (let x = 0; x < this.lcdWidth; x++) {
    const index = y * this.lcdWidth + x;
    const imgIndex = index * 4;
    const colourIndex = this.service.frame[index];
    if (colourIndex < 0 || colourIndex >= colours.length) {
      throw new Error("Unknown colour: " + colourIndex);
    }

    const colour = colours[colourIndex];

    img.data[imgIndex] = colour.red;
    img.data[imgIndex + 1] = colour.green;
    img.data[imgIndex + 2] = colour.blue;
    img.data[imgIndex + 3] = 255;
  }
}
rawContext.putImageData(img, 0, 0);

context.drawImage(raw, lcdX, lcdY, lcdW, lcdH);

Doporučuji vám přejít na STAR a KLONOVAT https://github.com/axle-h/Retro.Net a vyzkoušet to s Dockerem! Poté můžete použít Visual Studio Code a .NET Core ke kompilaci a spuštění lokálně. Hledá pomoc se zvukem GameBoy a debuggerem.

Sponzor: Získejte nejnovější JetBrains Rider pro ladění kódu .NET třetích stran, Smart Step Into, další vylepšení debuggeru, C# Interactive, nového průvodce projektu a formátování kódu ve sloupcích.


Docker
  1. Jak nainstalovat .NET Core na Debian 10

  2. Kompletní kontejnerizovaná mikroslužba .NET Core Application, která je co nejmenší

  3. Zjištění, že aplikace .NET Core běží v kontejneru Docker a SkippableFacts v XUnit

  1. Nastavení .Net Core v Ubuntu 20.04 – průvodce krok za krokem?

  2. Optimalizace velikostí obrazu ASP.NET Core Docker

  3. Podporuje .NET Core v Linuxu Visual Basic?

  1. Jak nainstalovat a používat Dolphin Emulator na Ubuntu

  2. NuGet pro .NET Core na Linuxu

  3. .NET core X509Store na linuxu