DiamondShapes.cs
using System;
using System.Collections.Generic;

namespace Diamonds;

/// <summary>Edge-connected shapes on the half-width / half-height diamond lattice.</summary>
public static class DiamondShapes
{
	public readonly record struct Cell( int Lane, int Row );

	// Rotate on the diamond lattice, preserving each building block's upright proportions.
	public static (float Lane, float Row) Rotate( (float Lane, float Row) cell, int turns ) => (turns & 3) switch
	{
		1 => (-cell.Row, cell.Lane),
		2 => (-cell.Lane, -cell.Row),
		3 => (cell.Row, -cell.Lane),
		_ => cell
	};

	public sealed class Shape( string name, params Cell[] cells )
	{
		public string Name { get; } = name;
		// Compute from the stable identity: hotload preserves existing catalog
		// objects and does not rerun their constructors to initialize new fields.
		// V shapes (including Hook's three-cell bend) turn around the shared
		// corner point between their arms, not the center of a diamond.
		public (float Lane, float Row) Pivot => Name switch
		{
			"Twin" or "Crown" => (0, 0),
			"Slash" or "Fork" or "Cluster" or "Hook" or "Mirrored Hook" or "Trident" => (0, 1),
			"Zigzag" or "Spear" => (0.5f, 1.5f),
			"Mirrored Zigzag" => (-0.5f, 1.5f),
			_ => throw new InvalidOperationException( $"Missing rotation pivot for {Name}" )
		};
		public IReadOnlyList<Cell> Cells { get; } = Array.AsReadOnly( cells );

		public (float Lane, float Row) RotatedCell( int index, int turns )
		{
			var cell = Cells[index];
			var offset = Rotate( (cell.Lane - Pivot.Lane, cell.Row - Pivot.Row), turns );
			return (Pivot.Lane + offset.Lane, Pivot.Row + offset.Row);
		}
	}

	static IReadOnlyList<Shape> catalog;
	// Hotload preserves static objects. Rebuild an older catalog when shapes are
	// added, keeping existing indices stable for pieces already on the board.
	public static IReadOnlyList<Shape> All => catalog?.Count == 11 ? catalog : catalog = CreateCatalog();

	static IReadOnlyList<Shape> CreateCatalog() => Array.AsReadOnly<Shape>(
	[
		new( "Twin", new( 0, 0 ), new( 1, 1 ) ),
		new( "Slash", new( -1, 0 ), new( 0, 1 ), new( 1, 2 ) ),
		new( "Fork", new( 0, 0 ), new( -1, 1 ), new( 1, 1 ) ),
		new( "Crown", new( -1, 0 ), new( 1, 0 ), new( 0, 1 ) ),
		new( "Cluster", new( 0, 0 ), new( -1, 1 ), new( 1, 1 ), new( 0, 2 ) ),
		new( "Hook", new( 0, 0 ), new( 1, 1 ), new( 0, 2 ), new( -1, 3 ) ),
		new( "Mirrored Hook", new( 0, 0 ), new( -1, 1 ), new( 0, 2 ), new( 1, 3 ) ),
		new( "Zigzag", new( 0, 0 ), new( 1, 1 ), new( 0, 2 ), new( 1, 3 ) ),
		new( "Mirrored Zigzag", new( 0, 0 ), new( -1, 1 ), new( 0, 2 ), new( -1, 3 ) ),
		new( "Spear", new( -1, 0 ), new( 0, 1 ), new( 1, 2 ), new( 2, 3 ) ),
		new( "Trident", new( 0, 1 ), new( -1, 0 ), new( 1, 0 ), new( 1, 2 ) )
	] );
}