DiamondContact.cs
using System;
using System.Collections.Generic;
using static Diamonds.DiamondBoard;
namespace Diamonds;
/// <summary>Lower faces touching external supports at the instant of a vertical landing.</summary>
public static class DiamondContact
{
[Flags]
public enum Faces { None = 0, LowerLeft = 1, LowerRight = 2, Corner = 4 }
const float Tolerance = 0.02f;
public static Faces Find( IEnumerable<Diamond> moving, IReadOnlyList<Diamond> stationary, ISet<int> excluded = null, bool fillCornerPockets = true )
{
Faces faces = Faces.None;
foreach ( var cell in moving )
{
if ( MathF.Abs( cell.Y - FloorY( cell.Lane ) ) <= Tolerance )
{
float phase = cell.Lane % 2;
// Even lanes nest in a valley; odd lanes balance tip-on-peak.
if ( MathF.Min( phase, 2 - phase ) * Width * 0.5f <= Tolerance )
faces |= Faces.LowerLeft | Faces.LowerRight;
else if ( MathF.Abs( phase - 1 ) * Width * 0.5f <= Tolerance )
faces |= Faces.Corner;
else faces |= phase < 1 ? Faces.LowerRight : Faces.LowerLeft;
}
// Visit the two sources directly: materializing Concat().ToArray() here
// allocates per contact and first-use JITs the generic LINQ array builder.
for ( int i = 0; i < stationary.Count; i++ )
{
if ( excluded?.Contains( i ) == true ) continue;
faces |= SupportFace( cell, stationary[i] );
}
int boundaryIndex = stationary.Count;
foreach ( var support in BoundaryDiamonds( fillCornerPockets ) )
{
if ( excluded?.Contains( boundaryIndex++ ) == true ) continue;
faces |= SupportFace( cell, support );
}
}
return faces;
}
static Faces SupportFace( Diamond cell, Diamond support )
{
float dx = (support.Lane - cell.Lane) * Width * 0.5f;
float dy = support.Y - cell.Y;
// Ignore side tips, supports above us, and anything not actually touching.
if ( dy <= Tolerance || MathF.Abs( dx ) >= Width - Tolerance ) return Faces.None;
float gap = dy - Height * (1 - MathF.Abs( dx ) / Width);
if ( MathF.Abs( gap ) > Tolerance ) return Faces.None;
return MathF.Abs( dx ) <= Tolerance ? Faces.Corner : dx < 0 ? Faces.LowerLeft : Faces.LowerRight;
}
}