GNU/Linux >> Belajar Linux >  >> Panels >> Docker

Emulator GameBoy sisi server multi-pemain yang ditulis dalam .NET Core dan Angular

Salah satu kesenangan besar berbagi dan menemukan kode online adalah ketika Anda menemukan sesuatu yang benar-benar epik, sangat menakjubkan , yang harus Anda gali. Kunjungi https://github.com/axle-h/Retro.Net dan tanyakan pada diri Anda mengapa proyek GitHub ini hanya memiliki 20 bintang?

Alex Haslehurst telah membuat beberapa pustaka perangkat keras retro di open source .NET Core dengan Angular Front End!

Terjemahan?

Emulator Game Boy sisi server multipemain. Epik.

Anda dapat menjalankannya dalam hitungan menit dengan

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

Kemudian cukup telusuri ke http://localhost:2500 dan mainkan Tetris di GameBoy asli!

Saya menyukai ini karena beberapa alasan.

Pertama, saya suka sudut pandangnya:

Silakan periksa emulator GameBoy saya yang ditulis dalam .NET Core; Retro.Net . Ya, emulator GameBoy yang ditulis dalam .NET Core. Mengapa? Kenapa tidak. Saya berencana untuk membuat beberapa tulisan tentang pengalaman saya dengan proyek ini. Pertama:mengapa itu ide yang buruk.

  1. Emulasi pada .NET
  2. Meniru CPU GameBoy di .NET

Masalah terbesar yang mencoba meniru CPU dengan platform seperti .NET adalah kurangnya waktu presisi tinggi yang andal. Namun, ia mengelola emulasi awal yang bagus dari prosesor Z80, memodelkan hal-hal tingkat rendah seperti register di C# tingkat sangat tinggi. Saya suka bahwa GameBoyFlagsRegister kelas publik adalah sesuatu.;) Saya melakukan hal serupa ketika saya mem-porting "CPU Kecil" berusia 15 tahun ke .NET Core/C#.

Pastikan untuk membaca penjelasan Alex yang sangat mendetail tentang bagaimana dia memodelkan mikroprosesor Z80.

Untungnya GameBoy CPU, Sharp LR35902, berasal dari Zilog Z80 yang populer dan terdokumentasi dengan sangat baik - Mikroprosesor yang luar biasa masih diproduksi hingga saat ini, lebih dari 40 tahun setelah diperkenalkan.

Z80 adalah mikroprosesor 8-bit, artinya setiap operasi secara native dilakukan pada satu byte. Set instruksi memang memiliki beberapa operasi 16-bit tetapi ini hanya dieksekusi sebagai beberapa siklus logika 8-bit. Z80 memiliki bus alamat lebar 16-bit, yang secara logis mewakili peta memori 64K. Data ditransfer ke CPU melalui bus data lebar 8-bit tetapi ini tidak relevan untuk mensimulasikan sistem pada tingkat mesin keadaan. Z80 dan Intel 8080 yang memiliki 256 port I/O untuk mengakses periferal eksternal tetapi CPU GameBoy tidak memilikinya - lebih memilih I/O yang dipetakan dengan memori

Dia tidak hanya membuat emulator - ada banyak emulator - tetapi secara unik dia menjalankannya di sisi server sambil mengizinkan kontrol bersama di browser. "Di antara setiap bingkai unik, semua klien yang terhubung dapat memilih apa yang seharusnya menjadi input kontrol berikutnya. Server akan memilih salah satu dengan suara terbanyak... hampir sepanjang waktu." GameBoy online multi-pemain besar-besaran! Kemudian dia mengalirkan frame berikutnya! "Perenderan GPU selesai di server satu kali per bingkai unik, dikompresi dengan LZ4 dan dialirkan ke semua klien yang terhubung melalui soket web."

Ini adalah gudang pembelajaran yang bagus karena:

  • memiliki logika bisnis yang kompleks di sisi server tetapi ujung depan menggunakan Angular dan soket web serta teknologi web terbuka.
  • Senang juga karena dia memiliki Dockerfile multi-tahap lengkap yang merupakan contoh bagus tentang cara membangun aplikasi .NET Core dan Angular di Docker.
  • Ekstensif (ribuan) Pengujian Unit dengan Kerangka Asersi Seharusnya dan Kerangka Mengejek Moq.
  • Contoh bagus penggunaan Pemrograman Reaktif
  • Pengujian Unit pada server DAN klien, menggunakan Pengujian Unit Karma untuk Angular

Berikut beberapa cuplikan kode elegan favorit di repositori besar ini.

Tombol Reaktif Menekan:

_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();
                });

Perender GPU:

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++;
}

Frame GameBoy disusun di sisi server kemudian dikompresi dan dikirim ke klien melalui WebSockets. Dia memiliki latar belakang dan sprite yang bekerja, dan masih ada pekerjaan yang harus diselesaikan.

LCD Mentah adalah kanvas 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>

Saya suka seluruh proyek ini karena memiliki segalanya. TypeScript, Kanvas JavaScript 2D, game retro, dan banyak lagi!

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);

Saya akan mendorong Anda untuk pergi STAR dan CLONE https://github.com/axle-h/Retro.Net dan mencobanya dengan Docker! Anda kemudian dapat menggunakan Visual Studio Code dan .NET Core untuk mengompilasi dan menjalankannya secara lokal. Dia mencari bantuan dengan suara GameBoy dan Debugger.

Sponsor: Dapatkan JetBrains Rider terbaru untuk men-debug kode .NET pihak ketiga, Smart Step Into, peningkatan debugger lainnya, C# Interactive, wizard proyek baru, dan kode pemformatan dalam kolom.


Docker
  1. Cara Menginstal .NET Core di Debian 10

  2. Layanan mikro Aplikasi .NET Core terkemas lengkap yang sekecil mungkin

  3. Mendeteksi bahwa aplikasi .NET Core berjalan di Docker Container dan SkippableFacts di XUnit

  1. Setup .Net Core di Ubuntu 20.04 - Panduan langkah demi langkah?

  2. Mengoptimalkan ukuran Gambar ASP.NET Core Docker

  3. Apakah Visual Basic didukung oleh .NET Core di Linux?

  1. Cara menginstal dan menggunakan Dolphin Emulator di Ubuntu

  2. NuGet untuk .NET Core di Linux

  3. .NET core X509Store di linux