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

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

	private bool _hasMoved;

	public override bool ShouldHandleEvent( EventType eventType )
	{
		if ( eventType == EventType.TurnStart )
			return true;

		if ( eventType == EventType.AfterCardsMoved )
		{
			if ( IsGridPosValid( GridPos + new IntVector2( -1, 0 ) ) || IsGridPosValid( GridPos + new IntVector2( 1, 0 ) ) )
				return true;
		}

		return false;
	}

	//protected override void OnUpdate()
	//{
	//	base.OnUpdate();

	//	Gizmo.Draw.Color = Color.White.WithAlpha( 1f );
	//	Gizmo.Draw.Text( $"{_hasMoved}", new global::Transform( WorldPosition ), size: 24f );
	//}

	public override async Task HandleEventAsync( EventType eventType )
	{
		if ( IsRevealed )
		{
			Manager.Instance.PushEventMessage( this, eventType );
			await Task.DelayRealtime( 300 );
		}

		if (eventType == EventType.TurnStart)
		{
			await TryMove();

			_hasMoved = false;
		}
		else
		{
			await TryMove();
		}

		if(IsRevealed)
			Manager.Instance.PopEventMessage();
	}

	async Task TryMove()
	{
		if ( _hasMoved )
			return;

		List<IntVector2> validGridPositions = new();

		var leftGridPos = GridPos + new IntVector2( -1, 0 );
		if ( IsGridPosValid( leftGridPos ) )
			validGridPositions.Add( leftGridPos );

		var rightGridPos = GridPos + new IntVector2( 1, 0 );
		if ( IsGridPosValid( rightGridPos ) )
			validGridPositions.Add( rightGridPos );

		if ( validGridPositions.Count == 0 )
			return;

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

		await Task.DelayRealtime( 200 );

		if(IsRevealed)
			Manager.Instance.PlayCardSfx( "crab", this, volume: 1.4f, pitch: Game.Random.Float( 0.98f, 1.03f ) );
		else
			Manager.Instance.PlayCardSfx( "card_move", this, volume: 1.1f, pitch: Game.Random.Float( 0.85f, 1.15f ) );

		await Task.DelayRealtime( 100 );

		Manager.Instance.RemoveCardGridPos( this );
		await Manager.Instance.SetCardGridPos( this, targetGridPos );

		_hasMoved = true;

		await Task.DelayRealtime( 250 );

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

	bool IsGridPosValid(IntVector2 gridPos)
	{
		if(!Manager.Instance.IsGridPosInBounds(gridPos)) 
			return false;

		if ( !Manager.Instance.IsGridPosEmpty( gridPos ) )
			return false;

		return true;
	}
}