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

public class CardDancer : Card
{
	public override bool IsAlive => true;

	public override bool ShouldHandleEvent( EventType eventType )
	{
		return eventType == EventType.Mismatch && Manager.Instance.ChosenCards.Contains( this ) && !Manager.Instance.IsMismatchALockedMatch;
	}

	public override async Task HandleEventAsync( EventType eventType )
	{
		Manager.Instance.PushEventMessage( this, eventType );

		List<Card> neighbourCards = new();
		List<IntVector2> neighboursEmpty = new();
		Card lastPartnerCard = null;

		await Task.DelayRealtime( 300 );

		Manager.Instance.PlayCardSfx( "dancer", this, volume: 1.1f, pitch: Game.Random.Float( 0.98f, 1.02f ) );

		for (int i = 0; i < 4; i++)
		{
			neighbourCards.Clear();
			neighboursEmpty.Clear();

			EvaluateNeighbour( GridPos + new IntVector2( -1, 0 ), neighbourCards, neighboursEmpty );
			EvaluateNeighbour( GridPos + new IntVector2( 1, 0 ), neighbourCards, neighboursEmpty );
			EvaluateNeighbour( GridPos + new IntVector2( 0, -1 ), neighbourCards, neighboursEmpty );
			EvaluateNeighbour( GridPos + new IntVector2( 0, 1 ), neighbourCards, neighboursEmpty );

			IntVector2 targetGridPos;
			Card targetCard = null;

			var validNeighbourCards = neighbourCards.Where( x => x != lastPartnerCard ).ToList();

			if ( validNeighbourCards.Count > 0 )
			{
				validNeighbourCards.Shuffle();
				targetCard = validNeighbourCards.First();
				targetGridPos = targetCard.GridPos;
			}
			else
			{
				neighboursEmpty.Shuffle();
				targetGridPos = neighboursEmpty.First();
			}

			if ( targetCard != null )
			{
				await Manager.Instance.ShakeCard( this );
				await Manager.Instance.ShakeCard( targetCard );

				lastPartnerCard = targetCard;

				await Task.DelayRealtime( 650 );

				if(i < 3)
				{
					await Manager.Instance.SwapCardPositions( this, targetCard );
					await Task.DelayRealtime( 250 );
				}
			}
			else
			{
				lastPartnerCard = null;

				if ( i < 3 )
				{
					Manager.Instance.RemoveCardGridPos( this );
					await Manager.Instance.SetCardGridPos( this, targetGridPos );

					await Task.DelayRealtime( 400 );
				}
			}
		}

		await Task.DelayRealtime( 100 );

		Manager.Instance.PopEventMessage();

		await Manager.Instance.EventHappened( EventType.AfterCardsMoved );
	}

	void EvaluateNeighbour(IntVector2 gridPos, List<Card> neighbourCards, List<IntVector2> empty)
	{
		if ( !Manager.Instance.IsGridPosInBounds( gridPos ) )
			return;

		var card = Manager.Instance.GetCardAtGridPos( gridPos );
		if ( card != null )
			neighbourCards.Add( card );
		else
			empty.Add( gridPos );
	}
}