protected abstract List <Vector2i> GetMovesOccupied(GamePiece gamePiece, Vector2i from);
 protected abstract bool IsMoveAllowed(GamePiece gamePiece, Vector2i from, Vector2i to);
        public Grid(IEnumerable <GamePiece> tiles, Vector2u gridSize, Vector2f scale, uint gridLayers = 1, DragManager mouseManager = null, int dragLayer = -1, int dropLayer = -1, Vector2f dropScale = default, Vector2u cellSize = default)
        {
            if (dropScale == default)
            {
                dropScale = scale;
            }

            window = mouseManager.Window;
            if (tiles == null)
            {
                this.tiles = new List <GamePiece>();
            }
            else
            {
                this.tiles = tiles.ToList();
            }
            this.mapSize = gridSize;
            this.scale   = scale;
            mapLayers    = gridLayers;
            map          = new int[gridSize.X, gridSize.Y, gridLayers];
            if (cellSize == default)
            {
                tileSize = this.tiles[0].TextureRect.GetSize().ToVector2f();
            }
            else
            {
                tileSize = (Vector2f)cellSize;
            }

            // apply scale to tiles
            for (var i = 0; i < this.tiles.Count; i++)
            {
                this.tiles[i].Scale = scale;
            }

            size           = new Vector2f(gridSize.X * tileSize.X, gridSize.Y * tileSize.Y);
            scaledSize     = new Vector2f(Size.X * scale.X, Size.Y * scale.Y);
            scaledTileSize = new Vector2f(tileSize.X * scale.X, tileSize.Y * scale.Y);

            // init map
            for (var z = 0; z < mapLayers; z++)
            {
                for (var y = 0; y < mapSize.Y; y++)
                {
                    for (var x = 0; x < mapSize.X; x++)
                    {
                        map[x, y, z] = -1;
                    }
                }
            }

            // mouse
            if (mouseManager != null)
            {
                if (dragLayer != -1)
                {
                    GamePiece       picked      = null;
                    int             pickedIndex = -1;
                    Action <Sprite> dropHandled = null;
                    Vector2f        dragOffset  = default;
                    Vector2i        pickCoords  = default;

                    mouseManager.DragStart += (object sender, DragEventArgs e) =>
                    {
                        if (Enabled)
                        {
                            if (e.Button == Mouse.Button.Left &&
                                e.DragStart.X >= Rect.Left &&
                                e.DragStart.Y >= Rect.Top &&
                                e.DragStart.X < Rect.Left + Rect.Width &&
                                e.DragStart.Y < Rect.Top + Rect.Height)
                            {
                                picked = SpritePicker(e.DragStart, dragLayer, out pickCoords);

                                if (picked != null)
                                {
                                    moves         = GetMoves(picked, pickCoords);
                                    movesOccupied = GetMovesOccupied(picked, pickCoords);
                                    Debug.WriteLine($"[Grid] Picked {pickCoords.X},{pickCoords.Y}");

                                    pickedIndex = map[pickCoords.X, pickCoords.Y, dragLayer];
                                    dropHandled = sprite =>
                                    {
                                        //map[pickCoords.X, pickCoords.Y, dragLayer] = -1;
                                    };
                                    picked.Color         = new Color(255, 255, 255, 0);
                                    draggingSprite       = new GamePiece(picked);
                                    draggingSprite.Color = new Color(255, 255, 255, 200);
                                    dragOffset           = (e.DragStart.ToVector2f() - draggingSprite.Position) * -1;

                                    // empty origial area
                                    var subgridSize = getSubGridSizeOfTile(picked);
                                    for (var y = 0; y < subgridSize.Y; y++)
                                    {
                                        for (var x = 0; x < subgridSize.X; x++)
                                        {
                                            map[pickCoords.X + x, pickCoords.Y + y, dragLayer] = -1;
                                        }
                                    }
                                }
                            }
                        }
                    };
                    mouseManager.Dragging += (object sender, DragEventArgs e) =>
                    {
                        if (Enabled && draggingSprite != null)
                        {
                            var dp = e.DragCurrent.ToVector2f();
                            draggingSprite.Position = dp + dragOffset;
                        }
                    };
                    mouseManager.DragEnd += (object sender, DragEventArgs e) =>
                    {
                        if (Enabled)
                        {
                            if (picked != null)
                            {
                                picked.Color  = new Color(255, 255, 255, 255);
                                picked        = null;
                                moves         = null;
                                movesOccupied = null;
                            }
                            if (draggingSprite != null)
                            {
                                if (!mouseManager.Drop(this, draggingSprite, dropHandled, e))
                                {
                                    // if the drop was unhandled, restore the item to its original location
                                    var subgridSize = getSubGridSizeOfTile(draggingSprite);
                                    if (subgridSize.X > 1 || subgridSize.Y > 1)
                                    {
                                        for (var y = 0; y < subgridSize.Y; y++)
                                        {
                                            for (var x = 0; x < subgridSize.X; x++)
                                            {
                                                map[pickCoords.X + x, pickCoords.Y + y, dropLayer] = pickedIndex - int.MinValue;
                                            }
                                        }
                                    }
                                    map[pickCoords.X, pickCoords.Y, dropLayer] = pickedIndex;
                                }
                                draggingSprite = null;
                            }
                        }
                    };
                }
                if (dropLayer != -1)
                {
                    mouseManager.Dropped += (object sender, DropEventArgs e) =>
                    {
                        if (Enabled)
                        {
                            if (e.DragStop.X >= Rect.Left && e.DragStop.Y >= Rect.Top &&
                                e.DragStop.X < Rect.Left + Rect.Width &&
                                e.DragStop.Y < Rect.Top + Rect.Height)
                            {
                                var p = e.Sprite.GetGlobalBounds().GetCenter().ToVector2i()
                                        + new Vector2i(0, 32); // move the drop point down so it is centered on the base of the chess piece
                                                               //var p = (Vector2i)(e.Sprite.Position + (cellSize.ToVector2f() / 2.0f));
                                var tpi = ScreenToGridCoordinates(p);
                                Debug.WriteLine($"[Grid] Dropped {tpi.X},{tpi.Y}");

                                var subgridSize = getSubGridSizeOfTile(e.Sprite);
                                if (tpi.X >= 0 && tpi.X + (subgridSize.X - 1) < mapSize.X && tpi.Y >= 0 && tpi.Y + (subgridSize.Y - 1) < mapSize.Y)
                                {
                                    var obstacleFound = false;

                                    /*
                                     * for (var y=tpi.Y; y<tpi.Y+subgridSize.Y; y++){
                                     *  for (var x=tpi.X; x<tpi.X+subgridSize.X; x++){
                                     *      if (map[x, y, dropLayer] != -1){
                                     *          obstacleFound=true;
                                     *          break;
                                     *      }
                                     *  }
                                     * }
                                     */

                                    // check if move is valid according to board state
                                    if (!obstacleFound)
                                    {
                                        obstacleFound = !IsMoveAllowed(e.Sprite, ScreenToGridCoordinates(e.DragStart), tpi);
                                    }
                                    //var index = map[tpi.X, tpi.Y, dropLayer];
                                    //if (index == -1)
                                    if (!obstacleFound)
                                    {
                                        e.Sprite.Color = new Color(255, 255, 255, 255);
                                        if (!this.tiles.Contains(e.Sprite))
                                        {
                                            this.tiles.Add(e.Sprite);
                                        }

                                        var tileIndex = this.tiles.IndexOf(e.Sprite);

                                        // if this item is larger than 1x1 fill the other grid cells with "shadow" indeces
                                        // so that items will not be able to overlap

                                        if (subgridSize.X > 1 || subgridSize.Y > 1)
                                        {
                                            for (var y = 0; y < subgridSize.Y; y++)
                                            {
                                                for (var x = 0; x < subgridSize.X; x++)
                                                {
                                                    map[tpi.X + x, tpi.Y + y, dropLayer] = tileIndex - int.MinValue;
                                                }
                                            }
                                        }

                                        map[tpi.X, tpi.Y, dropLayer] = tileIndex;

                                        e.Sprite.Scale = dropScale;
                                        if (e.OnHandled != null)
                                        {
                                            e.OnHandled.Invoke(e.Sprite);
                                            e.Handled = true;
                                            OnMoved();
                                        }
                                    }
                                }
                            }
                        }
                    };
                }
            }

            moveMarker = new CircleShape(13);
            moveMarker.OutlineThickness = 1;
            moveMarker.FillColor        = new Color(0, 0, 0, 64);
            moveMarker.OutlineColor     = new Color(0, 0, 0, 32);
            moveMarker.Origin           = moveMarker.GetGlobalBounds().GetCenter();

            moveMarkerOccupied                  = new CircleShape(24);
            moveMarkerOccupied.FillColor        = new Color(0, 0, 0, 0);
            moveMarkerOccupied.OutlineThickness = 6;
            moveMarkerOccupied.OutlineColor     = new Color(0, 0, 0, 64);
            moveMarkerOccupied.Origin           = moveMarkerOccupied.GetGlobalBounds().GetCenter();
        }
        protected override void OnMoved()
        {
            updateBoardFromGameState();

            //Debug.WriteLine(chessGame.Pos.GenerateFen().ToString());

            if (chessBoard.CurrentPlayer == Player.Black)
            {
                if (aiThinkingTask == null || aiThinkingTask.IsCompleted)
                {
                    aiThinking     = true;
                    aiThinkingTask = Task.Run(async() =>
                    {
                        try
                        {
                            var ai   = new GameAI();
                            var best = ai.Search(chessBoard);
                            var move = best;

                            aiThinking = false;

                            var fromIndex = move.from.ToInt();
                            var fromX     = move.from.x; //fromIndex % 8;
                            var fromY     = move.from.y; // 7 - (fromIndex / 8);
                            var tileIndex = map[fromX, fromY, 0];
                            var sprite    = tiles[tileIndex];

                            var toIndex               = move.to.ToInt();
                            var toX                   = move.to.x; //toIndex % 8;
                            var toY                   = move.to.y; //7 - (toIndex / 8);
                            var tile2Index            = map[toX, toY, 0];
                            GamePiece pieceBeingTaken = null;
                            if (tile2Index != -1)
                            {
                                pieceBeingTaken            = tiles[tile2Index];
                                pieceBeingTaken.beingTaken = true;
                            }

                            var tilePosition = new Vector2f(toX * tileSize.X * scale.X, toY * tileSize.Y * scale.Y);
                            var tileOffset   = (ScaledTileSize - sprite.GetGlobalBounds().GetSize()) / 2.0f;
                            tileOffset.Y    -= 24;
                            await sprite.MoveAsync(Position + tilePosition + tileOffset);

                            chessBoard.Move(move);
                            updateBoardFromGameState();
                            sprite.moveFinished = true;
                            if (pieceBeingTaken != null)
                            {
                                pieceBeingTaken.beingTaken = false;
                            }

                            updateGameText();
                            //Debug.WriteLine(chessGame.Pos.GenerateFen().ToString());
                        }
                        catch (Exception ex)
                        {
                            Debugger.Break();
                        }
                    });
                }
            }
        }