cards/CardMap.cs
using Sandbox;
using System.Threading.Tasks;

public class CardMap : Card
{
	public override bool ValidateStartingGridPos()
	{
		var maps = Manager.Instance.Cards.Where( x => x.CardType == CardType.Map ).ToList();

		var map0 = maps[0];
		var map1 = maps[1];

		int farthestDist = GetFarthestDistance( map0, map1 );

		if ( maps.Count < 2 || farthestDist > 3 )
			return true;

		int NUM_TRIES = 100;
		for ( int i = 0; i < NUM_TRIES; i++ )
		{
			var newGridPos = Manager.Instance.GetRandomGridPos( except: map0.GridPos );
			var otherCard = Manager.Instance.GetCardAtGridPos( newGridPos );

			if ( GetFarthestDistance( otherCard, map1 ) > 3 && otherCard.CardType != CardType.Map )
			{
				Manager.Instance.SwapCardPositionsNonAsync( map0, otherCard );
				break;
			}
		}

		if ( !Manager.IsNearby( map0.GridPos, map1.GridPos ) )
			return true;

		NUM_TRIES = 100;
		for ( int i = 0; i < NUM_TRIES; i++ )
		{
			var newGridPos = Manager.Instance.GetRandomGridPos( except: map0.GridPos );
			var otherCard = Manager.Instance.GetCardAtGridPos( newGridPos );

			if ( !Manager.IsNearby( newGridPos, map1.GridPos ) && otherCard.CardType != CardType.Map )
			{
				Manager.Instance.SwapCardPositionsNonAsync( map0, otherCard );
				break;
			}
		}

		return false;
	}

	int GetFarthestDistance(Card card0, Card card1)
	{
		return Math.Max(Math.Abs( card0.GridPos.x - card1.GridPos.x ), Math.Abs( card0.GridPos.y - card1.GridPos.y ));
	}

	public override bool ShouldHandleEvent( EventType eventType )
	{
		return eventType == EventType.Match && Manager.Instance.ChosenCards[0] == this;
	}

	public override async Task HandleEventAsync( EventType eventType )
	{
		var mapPos0 = Manager.Instance.ChosenCards[0].GridPos;
		var mapPos1 = Manager.Instance.ChosenCards[1].GridPos;
		var linePositions = GetLine( mapPos0.x, mapPos0.y, mapPos1.x, mapPos1.y );

		List<Card> lineCards = new();
		foreach(var linePos in linePositions)
		{
			var card = Manager.Instance.GetCardAtGridPos( linePos );
			if ( card != null && !Manager.Instance.ChosenCards.Contains( card ) )
				lineCards.Add( card );
		}

		if ( lineCards.Count == 0 )
			return;

		Manager.Instance.PushEventMessage( this, eventType );

		await Task.DelayRealtime( 50 );

		Manager.Instance.PlayCardSfxBetween( "map_open", this, Manager.Instance.ChosenCards[1], volume: 1.5f, pitch: Game.Random.Float(0.95f, 1.1f) );

		await Task.DelayRealtime( 500 );

		foreach(var card in lineCards)
		{
			Manager.Instance.PlayCardSfx( "card_flip", card, volume: 0.8f, pitch: Game.Random.Float( 1.15f, 1.2f ) );
			await Manager.Instance.RevealCard( card );
			await Task.DelayRealtime( 200 );
		}

		await Task.DelayRealtime( 1400 );

		Manager.Instance.PlayCardSfxBetween( "map_close", this, Manager.Instance.ChosenCards[1], volume: 1.4f, pitch: Game.Random.Float( 0.95f, 1.1f ) );

		await Task.DelayRealtime( 500 );

		foreach ( var card in lineCards )
		{
			Manager.Instance.HideCard( card );
			Manager.Instance.PlayCardSfx( "card_flip", card, volume: 0.7f, pitch: Game.Random.Float( 0.65f, 0.75f ) );
			await Task.DelayRealtime( 150 );
		}

		await Task.DelayRealtime( 300 );

		Manager.Instance.PopEventMessage();
	}

	List<IntVector2> GetLine( int x0, int y0, int x1, int y1 )
	{
		List<IntVector2> points = new List<IntVector2>();

		int dx = Math.Abs( x1 - x0 );
		int dy = Math.Abs( y1 - y0 );
		int sx = x0 < x1 ? 1 : -1;
		int sy = y0 < y1 ? 1 : -1;
		int err = dx - dy;

		while ( true )
		{
			points.Add( new IntVector2( x0, y0 ) );

			if ( x0 == x1 && y0 == y1 )
				break;

			int e2 = 2 * err;

			if ( e2 > -dy )
			{
				err -= dy;
				x0 += sx;
			}

			if ( e2 < dx )
			{
				err += dx;
				y0 += sy;
			}
		}

		return points;
	}
}