diff --git a/src/Chip8.Core/Chip8.Core.csproj b/src/Chip8.Core/Chip8.Core.csproj new file mode 100644 index 0000000..3483111 --- /dev/null +++ b/src/Chip8.Core/Chip8.Core.csproj @@ -0,0 +1,16 @@ + + + + net6.0 + disable + enable + Chip8.Core + Chip8.Core + 10 + + + + + + + diff --git a/src/Chip8.Core/Palette.cs b/src/Chip8.Core/Palette.cs new file mode 100644 index 0000000..de8ce5f --- /dev/null +++ b/src/Chip8.Core/Palette.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; + +namespace Chip8.Core; + +public sealed class Palette +{ + readonly Rgb[] colors; + + public Palette(params Rgb[] colors) + { + if (colors is null || colors.Length < 2) + { + throw new ArgumentException("a palette needs at least two colours", nameof(colors)); + } + this.colors = (Rgb[])colors.Clone(); + } + + public int colorCount => colors.Length; + + public Rgb this[int index] => colors[index]; + + public Rgb resolve(int index) => colors[index < 0 ? 0 : index % colors.Length]; + + public IReadOnlyList toList() => (Rgb[])colors.Clone(); + + public static Palette monochrome() => new( + new Rgb(0x00, 0x00, 0x00), + new Rgb(0xFF, 0xFF, 0xFF)); + + public static Palette grayscale() => new( + new Rgb(0x00, 0x00, 0x00), + new Rgb(0x55, 0x55, 0x55), + new Rgb(0xAA, 0xAA, 0xAA), + new Rgb(0xFF, 0xFF, 0xFF)); + + public static Palette octo() => new( + Rgb.fromHex("#996600"), + Rgb.fromHex("#FFCC00"), + Rgb.fromHex("#FF6600"), + Rgb.fromHex("#662200")); + + public static Palette extended16() => new( + Rgb.fromHex("#000000"), + Rgb.fromHex("#1D2B53"), + Rgb.fromHex("#7E2553"), + Rgb.fromHex("#008751"), + Rgb.fromHex("#AB5236"), + Rgb.fromHex("#5F574F"), + Rgb.fromHex("#C2C3C7"), + Rgb.fromHex("#FFF1E8"), + Rgb.fromHex("#FF004D"), + Rgb.fromHex("#FFA300"), + Rgb.fromHex("#FFEC27"), + Rgb.fromHex("#00E436"), + Rgb.fromHex("#29ADFF"), + Rgb.fromHex("#83769C"), + Rgb.fromHex("#FF77A8"), + Rgb.fromHex("#FFCCAA")); + + public static Palette byName(string name) => name.ToLowerInvariant() switch + { + "mono" or "monochrome" => monochrome(), + "gray" or "grey" or "grayscale" => grayscale(), + "octo" => octo(), + "extended" or "extended16" or "pico8" => extended16(), + _ => throw new ArgumentException("unknown palette '" + name + "'", nameof(name)), + }; +} diff --git a/src/Chip8.Core/Platform.cs b/src/Chip8.Core/Platform.cs new file mode 100644 index 0000000..ca88efc --- /dev/null +++ b/src/Chip8.Core/Platform.cs @@ -0,0 +1,8 @@ +namespace Chip8.Core; + +public enum Platform +{ + Chip8, + SuperChip, + XoChip, +} diff --git a/src/Chip8.Core/Rgb.cs b/src/Chip8.Core/Rgb.cs new file mode 100644 index 0000000..7737e66 --- /dev/null +++ b/src/Chip8.Core/Rgb.cs @@ -0,0 +1,48 @@ +using System; +using System.Globalization; + +namespace Chip8.Core; + +public readonly struct Rgb : IEquatable +{ + public Rgb(byte r, byte g, byte b) + { + this.r = r; + this.g = g; + this.b = b; + } + + public byte r { get; } + public byte g { get; } + public byte b { get; } + + public static Rgb fromHex(string hex) + { + if (hex is null) + { + throw new ArgumentNullException(nameof(hex)); + } + + string digits = hex.StartsWith("#", StringComparison.Ordinal) ? hex.Substring(1) : hex; + if (digits.Length != 6) + { + throw new FormatException("expected a 6 digit hex colour like #RRGGBB, got '" + hex + "'"); + } + + byte channel(int start) => byte.Parse(digits.Substring(start, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture); + return new Rgb(channel(0), channel(2), channel(4)); + } + + public string toHex() => + "#" + r.ToString("X2", CultureInfo.InvariantCulture) + + g.ToString("X2", CultureInfo.InvariantCulture) + + b.ToString("X2", CultureInfo.InvariantCulture); + + public bool Equals(Rgb other) => r == other.r && g == other.g && b == other.b; + + public override bool Equals(object? obj) => obj is Rgb other && Equals(other); + + public override int GetHashCode() => (r << 16) | (g << 8) | b; + + public override string ToString() => toHex(); +}