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

public class CardMagnet : Card
{
	public override bool ShouldHandleEvent( EventType eventType )
	{
		if ( eventType == EventType.TurnStart )
		{
			if ( GetCardToPullFromDirection( new IntVector2( -1, 0 ) ) != null || GetCardToPullFromDirection( new IntVector2( 1, 0 ) ) != null || GetCardToPullFromDirection( new IntVector2( 0, -1 ) ) != null || GetCardToPullFromDirection( new IntVector2( 0, 1 ) ) != null )
				return true;
		}

		return false;
	}

	public override async Task HandleEventAsync( EventType eventType )
	{
		List<Card> validCards = new();
		
		var leftCard = GetCardToPullFromDirection( new IntVector2( -1, 0 ) );
		if ( leftCard != null )
			validCards.Add( leftCard );

		var rightCard = GetCardToPullFromDirection( new IntVector2( 1, 0 ) );
		if ( rightCard != null )
			validCards.Add( rightCard );

		var downCard = GetCardToPullFromDirection( new IntVector2( 0, -1 ) );
		if ( downCard != null )
			validCards.Add( downCard );

		var upCard = GetCardToPullFromDirection( new IntVector2( 0, 1 ) );
		if ( upCard != null )
			validCards.Add( upCard );

		if ( validCards.Count == 0 )
			return;

		var cardToPull = validCards[Game.Random.Int( 0, validCards.Count - 1 )];

		await Task.DelayRealtime( 300 );

		IntVector2 newGridPos;
		if ( cardToPull.GridPos.x < GridPos.x )
			newGridPos = cardToPull.GridPos + new IntVector2( 1, 0 );
		else if ( cardToPull.GridPos.x > GridPos.x )
			newGridPos = cardToPull.GridPos + new IntVector2( -1, 0 );
		else if ( cardToPull.GridPos.y < GridPos.y )
			newGridPos = cardToPull.GridPos + new IntVector2( 0, 1 );
		else
			newGridPos = cardToPull.GridPos + new IntVector2( 0, -1 );

		if ( !Manager.Instance.IsGridPosEmpty( newGridPos ) )
			Log.Error( $"Magnet - {newGridPos} isn't empty, it has {Manager.Instance.GetCardAtGridPos( newGridPos ).CardType}!" );

		Manager.Instance.PlayCardSfx( "card_move", cardToPull, volume: 1.1f, pitch: Game.Random.Float( 0.85f, 1.15f ) );

		Manager.Instance.RemoveCardGridPos( cardToPull );
		await Manager.Instance.SetCardGridPos( cardToPull, newGridPos );

		await Task.DelayRealtime( 400 );

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

	Card GetCardToPullFromDirection( IntVector2 dir )
	{
		bool hasPassedEmptySpace = false;

		var currGridPos = GridPos + dir;

		while ( Manager.Instance.IsGridPosInBounds( currGridPos ) )
		{
			var card = Manager.Instance.GetCardAtGridPos( currGridPos );

			if ( card == null )
			{
				hasPassedEmptySpace = true;
			}
			else
			{
				if ( hasPassedEmptySpace )
					return card;
				else
					return null;
			}

			currGridPos += dir;
		}

		return null;
	}
}