Search code examples
c#linqchess

Efficient method of merging multiple lists based on item weights


I am writing a chess engine and I require an efficient way of merging multiple lists into a singular list ordered by the smallest value.

Each list is quintessentially a group of chess pieces that are already ordered in perfect attack order. For example I have a White Queen on a2, White Bishop on b3, White Rook on f1 and White Rook on f2. Now say I have a Black Pawn on f7 then all four White pieces are converging on the f7 square from two different discrete directions - North East (Queen & Bishop) and North (Rooks).

These two groups would be ordered as follows:

Group A) 1st - Bishop (b3); 2nd - Queen (a2)
Group B) 1st - Rook (f2); 2nd - Rook (f1)

Now using the points system below I would expect both lists to be merged into a single list in the following order (lowest value to highest value): Bishop (b3), Rook (f2), Rook (f1) and finally Queen (a2).

Queen = 900 pts
Rook = 500 pts
Bishop = 375 pts
Knight = 350 pts
Pawn = 100 pts

Some code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SO_22015528
{
  public enum eRayDirection
  {
    North,
    NorthEast,
    East,
    SouthEast,
    South,
    SouthWest,
    West,
    NorthWest
  }

  public enum ePieceType
  {
    Empty = 0,
    Pawn = 1,
    Knight = 2,
    Bishop = 3,
    Rook = 4,
    Queen = 5,
    King = 6
  }

  public struct BoardPosition : IComparable<BoardPosition>
  {
    public int File;
    public int Rank;
    public int Square;

    public BoardPosition( int square )
    {
      Square = square;
      File = square % 7;
      Rank = 8 - ( square >> 3 );
    }

    public static Boolean operator >( BoardPosition b1, BoardPosition b2 )
    {
      return ( b1.Rank > b2.Rank ) || ( b1.Rank == b2.Rank && b1.File < b2.File );
    }

    public static Boolean operator <( BoardPosition b1, BoardPosition b2 )
    {
      return ( b1.Rank < b2.Rank ) || ( b1.Rank == b2.Rank && b1.File > b2.File );
    }

    public int CompareTo( BoardPosition obj )
    {
      if ( this < obj ) return 1;
      else if ( this > obj ) return -1;
      else return 0;
    }
  }

  public class ChessPiece
  {
    public int Value { get; set; }
    public ePieceType Type { get; set; }
    public int Square { get; set; }
    public BoardPosition XY
    {
      get
      {
        return new BoardPosition( this.Square );
      }
    }
    public ChessPiece( ePieceType type, int value, int square )
    {
      Value = value;
      Type = type;
      Square = square;
    }
  }

  public class Constraint
  {
    public ChessPiece Piece { get; set; }
    public eRayDirection Ray { get; set; }
    public Constraint( ChessPiece piece, eRayDirection ray )
    {
      Piece = piece;
      Ray = ray;
    }
    public override string ToString()
    {
      return String.Format( "{0} {1} {2}", Piece.Square, Piece.Type, Ray );
    }
  }

}

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SO_22015528
{
  class Program
  {
    static void Main( string[] args )
    {
      // test code
      ChessPiece a2 = new ChessPiece( ePieceType.Queen, 900, 48 );
      ChessPiece b3 = new ChessPiece( ePieceType.Bishop, 375, 41 );
      ChessPiece f1 = new ChessPiece( ePieceType.Rook, 500, 61 );
      ChessPiece f2 = new ChessPiece( ePieceType.Rook, 500, 53 );

      // This just simulates pieces that attack on the f7 square.
      List<Constraint> f7 = new List<Constraint>();
      f7.Add( new Constraint( b3, eRayDirection.NorthEast ) );
      f7.Add( new Constraint( a2, eRayDirection.NorthEast ) );
      f7.Add( new Constraint( f1, eRayDirection.North ) );
      f7.Add( new Constraint( f2, eRayDirection.North ) );

      // Get all positive ray directions ( use to simplify LINQ orderby )
      List<eRayDirection> positiveRayDirections = new List<eRayDirection>();
      positiveRayDirections.Add( eRayDirection.North );
      positiveRayDirections.Add( eRayDirection.NorthEast );
      positiveRayDirections.Add( eRayDirection.NorthWest );
      positiveRayDirections.Add( eRayDirection.West );

      var groups = f7
        .GroupBy( g => g.Ray )
        .Select( a =>
        new
        {
          Key = a.Key,
          Results = positiveRayDirections.Contains( a.Key ) ? a.OrderBy( x => x.Piece.XY ).ToList() : a.OrderByDescending( x => x.Piece.XY ).ToList()
        } ).ToList();

              // The groups object returns two discrete groups here; 
              // NorthEast containing 2 entries (Bishop & Queen) and North 
              // also containing to entries (Rook x 2).
      List<Constraint> attackOrder = new List<Constraint>();

      List<Int32> groupIndicies = new List<Int32>( groups.Count() );
      for ( int n = 0; n < groups.Count(); n++ )
        groupIndicies.Add( 0 );

      while ( true )
      {
        Int32 value = Int32.MaxValue;
        Int32 groupIndex = -1;

        for ( int n = 0; n < groups.Count(); n++ )
        {
          var g = groups[ n ];
          int gIndex = groupIndicies[ n ];

          if ( gIndex < g.Results.Count && g.Results[ gIndex ].Piece.Value < value )
          {
            value = g.Results[ gIndex ].Piece.Value;
            groupIndex = n;
          }
        }

        if ( groupIndex < 0 )
          break;

        attackOrder.Add( groups[ groupIndex ].Results[ groupIndicies[ groupIndex ] ] );

        groupIndicies[ groupIndex ]++;

      }

              foreach ( var ao in attackOrder )
                  Console.WriteLine( ao.ToString() );

      Console.ReadKey();
    }
  }
}

I do not think the last bit is very efficient and I would appreciate it if someone could find a much simpler way of doing this.


Solution

  • I have decided to stick with my original code after discovering that LINQ was far too slow for my needs. After replacing all LINQ code with loops and iterators I managed to increase engine performance by 12x.