21

Re: Потрібна допомога у розробці проекту

Останні правки:

PdnToBoardConverter

using System;
using Microsoft.Extensions.Logging;

namespace CheckersApi.Engine
{
    public static class PdnToBoardConverter
    {
        public static int[,] Convert(string pdn)
        {
            var board = new int[8, 8];

            var parts = pdn.Split(':');
            if (parts.Length != 3)
                return board;

            string whitePart = parts[1].Replace("W", "");
            string blackPart = parts[2].Replace("B", "");

            Fill(board, whitePart, true);
            Fill(board, blackPart, false);

            return board;
        }

        private static void Fill(int[,] board, string part, bool isWhite)
        {
            if (string.IsNullOrWhiteSpace(part))
                return;

            var items = part.Split(',', StringSplitOptions.RemoveEmptyEntries);

            foreach (var item in items)
            {
                bool isKing = item.StartsWith("K");
                string numStr = isKing ? item[1..] : item;

                if (!int.TryParse(numStr, out int square))
                    continue;

                var (row, col) = SquareToCoords(square);

                int value;

                if (isWhite)
                    value = isKing ? 4 : 3; // white
                else
                    value = isKing ? 2 : 1; // black

                board[row, col] = value;
            }
        }

        // ???? ПРАВИЛЬНА ФОРМУЛА (без переворотів)
        private static (int row, int col) SquareToCoords(int square)
        {
            int index = square - 1;

            int row = index / 4;
            int col = (index % 4) * 2 + ((row % 2 == 0) ? 1 : 0);

            return (row, col);
        }
    }
}

MoveFormatter

namespace CheckersApi.Engine
{
    internal static class MoveFormatter
    {
        internal static string Format(NativeKingsRow.CBmove m)
        {
            int from = ToSquare(m.from);
            int to = ToSquare(m.to);

            return m.jumps > 0
                ? $"{from}x{to}"
                : $"{from}-{to}";
        }

        public static int ToSquare(NativeKingsRow.Coor c)
        {
            int row = c.y;

            int offset = (row % 2 == 0) ? 1 : 0;
            int pos = (c.x - offset) / 2;

            int index = row * 4 + pos;

            return index + 1;
        }
    }
}

NativeKingsRowAdapter

using CheckersApi.Contracts;
using CheckersApi.Validation;
using System.Threading;
using Microsoft.Extensions.Logging;

namespace CheckersApi.Engine
{
    public class NativeKingsRowAdapter : IEngineAdapter
    {
        private readonly ILogger<NativeKingsRowAdapter> _logger;

        public NativeKingsRowAdapter(ILogger<NativeKingsRowAdapter> logger)
        {
            _logger = logger;
        }

        public SuggestResponse Suggest(SuggestRequest request, CancellationToken ct)
        {
            _logger.LogInformation("=== SUGGEST START ===");

            string pdn = PdnNormalizer.Normalize(request.State.Position);
            _logger.LogInformation("PDN: {Pdn}", pdn);

            int[,] board = PdnToBoardConverter.Convert(pdn);

            int color = pdn.StartsWith("B:") ? 1 : 2; // 1 = black, 2 = white

            _logger.LogInformation("COLOR: {Color}", color);

            var (rc, move, status) =
                NativeKingsRow.GetMove(board, color, 0.3);

            if (rc != 0 && rc != 3)
            {
                _logger.LogWarning("ENGINE FAILED");

                return new SuggestResponse
                {
                    Engine = "kingsrow-native",
                    BestMove = null,
                    Pv = new[] { status }
                };
            }

            string bestMove = MoveFormatter.Format(move);

            _logger.LogInformation("BEST MOVE: {Move}", bestMove);

            return new SuggestResponse
            {
                Engine = "kingsrow-native",
                BestMove = bestMove,
                Pv = new[] { bestMove }
            };
        }
    }
}

NativeKingsRow

using System;
using System.Runtime.InteropServices;
using System.Text;
using Microsoft.Extensions.Logging;

namespace CheckersApi.Engine
{
    internal static class NativeKingsRow
    {
        private static bool _bound;
        private static ILogger? _logger;

        public static void SetLogger(ILogger logger)
        {
            _logger = logger;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct Coor
        {
            public int x;
            public int y;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct CBmove
        {
            public Coor from;
            public Coor to;
            public int path1;
            public int path2;
            public int path3;
            public int path4;
            public int jumps;
            public int newpiece;
        }

        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
        private delegate int GetMoveDelegate(
            int[,] board,
            int color,
            double maxTime,
            StringBuilder status,
            ref int playnow,
            int info,
            int moreinfo,
            ref CBmove move);

        private static GetMoveDelegate? _getMove;

        internal static void BindOnce(IntPtr dll, ILogger logger)
        {
            if (_bound) return;

            _logger = logger;

            _getMove = Marshal.GetDelegateForFunctionPointer<GetMoveDelegate>(
                NativeLibrary.GetExport(dll, "getmove"));

            _logger.LogInformation("getmove bind OK");

            _bound = true;
        }

        internal static (int rc, CBmove move, string status) GetMove(
            int[,] board,
            int color,
            double time)
        {
            if (_getMove == null)
                throw new Exception("KingsRow not initialized");

            _logger?.LogInformation("=== CALL getmove ===");
            _logger?.LogInformation("COLOR: {Color}", color);
            _logger?.LogInformation("TIME: {Time}", time);

            PrintBoard(board);

            var status = new StringBuilder(256);
            var move = new CBmove();
            int playnow = 0;

            int rc = _getMove(
                board,
                color,
                time,
                status,
                ref playnow,
                0,
                0,
                ref move);

            _logger?.LogInformation("RC: {Rc}", rc);
            _logger?.LogInformation("STATUS: {Status}", status.ToString());
            _logger?.LogInformation("MOVE: from({fx},{fy}) to({tx},{ty}) jumps={j}",
                move.from.x, move.from.y,
                move.to.x, move.to.y,
                move.jumps);

            return (rc, move, status.ToString());
        }

        private static void PrintBoard(int[,] board)
        {
            _logger?.LogInformation("=== BOARD ===");

            for (int i = 0; i < 8; i++)
            {
                var row = "";
                for (int j = 0; j < 8; j++)
                {
                    row += board[i, j] + " ";
                }
                _logger?.LogInformation(row);
            }

            _logger?.LogInformation("=== END BOARD ===");
        }
    }
}

РЕЗУЛЬТАТ

Building...
info: Program[0]
      getmove bind OK
info: Program[0]
      KingsRow initialized
info: Microsoft.Hosting.Lifetime[14]
      Now listening on: http://localhost:5200
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
      Content root path: A:\Documents\CheckersApi
info: CheckersApi.Engine.NativeKingsRowAdapter[0]
      === SUGGEST START ===
info: CheckersApi.Engine.NativeKingsRowAdapter[0]
      PDN: B:W18,19,22,25,27,28,30,32:B1,5,6,7,10,12,14,16
info: CheckersApi.Engine.NativeKingsRowAdapter[0]
      COLOR: 1
info: Program[0]
      === CALL getmove ===
info: Program[0]
      COLOR: 1
info: Program[0]
      TIME: 0.3
info: Program[0]
      === BOARD ===
info: Program[0]
      0 1 0 0 0 0 0 0
info: Program[0]
      1 0 1 0 1 0 0 0
info: Program[0]
      0 0 0 1 0 0 0 1
info: Program[0]
      0 0 1 0 0 0 1 0
info: Program[0]
      0 0 0 3 0 3 0 0
info: Program[0]
      0 0 3 0 0 0 0 0
info: Program[0]
      0 3 0 0 0 3 0 3
info: Program[0]
      0 0 3 0 0 0 3 0
info: Program[0]
      === END BOARD ===
info: Program[0]
      RC: 2
      0 0 3 0 0 0 3 0
info: Program[0]
      === END BOARD ===
      0 0 3 0 0 0 3 0
info: Program[0]
      0 0 3 0 0 0 3 0
      0 0 3 0 0 0 3 0
info: Program[0]
      === END BOARD ===
info: Program[0]
      RC: 2
info: Program[0]
      STATUS: No moves
info: Program[0]
      MOVE: from(0,0) to(0,0) jumps=0
warn: CheckersApi.Engine.NativeKingsRowAdapter[0]
      ENGINE FAILED