github.com/frankkopp/FrankyGo@v1.0.3/internal/moveslice/moveslice.go (about)

     1  //
     2  // FrankyGo - UCI chess engine in GO for learning purposes
     3  //
     4  // MIT License
     5  //
     6  // Copyright (c) 2018-2020 Frank Kopp
     7  //
     8  // Permission is hereby granted, free of charge, to any person obtaining a copy
     9  // of this software and associated documentation files (the "Software"), to deal
    10  // in the Software without restriction, including without limitation the rights
    11  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    12  // copies of the Software, and to permit persons to whom the Software is
    13  // furnished to do so, subject to the following conditions:
    14  //
    15  // The above copyright notice and this permission notice shall be included in all
    16  // copies or substantial portions of the Software.
    17  //
    18  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    19  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    20  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    21  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    22  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    23  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    24  // SOFTWARE.
    25  //
    26  
    27  // Package moveslice provides helper functionality for slices
    28  // of type Move (chess moves).
    29  package moveslice
    30  
    31  import (
    32  	"fmt"
    33  	"strings"
    34  	"sync"
    35  
    36  	. "github.com/frankkopp/FrankyGo/internal/types"
    37  )
    38  
    39  // MoveSlice represents a data structure (go slice) for Move.
    40  type MoveSlice []Move
    41  
    42  // NewMoveSlice creates a new move slice with the given capacity
    43  // and 0 elements.
    44  // Is identical to MoveSlice(make([]Move, 0, cap))
    45  func NewMoveSlice(cap int) *MoveSlice {
    46  	moves := make([]Move, 0, cap)
    47  	return (*MoveSlice)(&moves)
    48  }
    49  
    50  // Len returns the number of moves currently stored in the slice.
    51  // Equivalent to len(ms)
    52  func (ms *MoveSlice) Len() int {
    53  	return len(*ms)
    54  }
    55  
    56  // Cap returns the capacity of the slice
    57  // Equivalent to cap(ms)
    58  func (ms *MoveSlice) Cap() int {
    59  	return cap(*ms)
    60  }
    61  
    62  // PushBack appends an element at the end of the slice
    63  // Equivalent to append(ms, m)
    64  func (ms *MoveSlice) PushBack(m Move) {
    65  	*ms = append(*ms, m)
    66  }
    67  
    68  // PopBack removes and returns the move from the back of the slice.
    69  // If the slice is empty, the call panics.
    70  func (ms *MoveSlice) PopBack() Move {
    71  	if len(*ms) <= 0 {
    72  		panic("MoveSlice: PopBack() called on empty slice")
    73  	}
    74  	backMove := (*ms)[len(*ms)-1]
    75  	*ms = (*ms)[:len(*ms)-1]
    76  	return backMove
    77  }
    78  
    79  // PushFront prepends an element at the beginning of the slice using
    80  // the underlying array (does not create a new array)
    81  // Moves (copies) all elements by one index slot and adds the new move at
    82  // the front.
    83  func (ms *MoveSlice) PushFront(m Move) {
    84  	*ms = append(*ms, MoveNone)
    85  	copy((*ms)[1:], *ms)
    86  	(*ms)[0] = m
    87  }
    88  
    89  // PopFront removes and returns the move from the front of the slice.
    90  // If the slice is empty, the call panics.
    91  // Shrinks the capacity of the slice as it only shifts the start of
    92  // the slice within the underlying array. Might lead to earlier
    93  // re-allocations
    94  func (ms *MoveSlice) PopFront() Move {
    95  	if len(*ms) <= 0 {
    96  		panic("MoveSlice: PopFront() called on empty slice")
    97  	}
    98  	frontMove := (*ms)[0]
    99  	*ms = (*ms)[1:]
   100  	return frontMove
   101  }
   102  
   103  // Front returns the move at the front of the slice. This is the element
   104  // that would be returned by ms[0].
   105  // This call panics if the slice is empty.
   106  func (ms *MoveSlice) Front() Move {
   107  	if len(*ms) <= 0 {
   108  		panic("MoveSlice: Front() called when empty")
   109  	}
   110  	return (*ms)[0]
   111  }
   112  
   113  // Back returns the move at the back of the slice. This is the element
   114  // that would be returned by ms[len[ms)-1].
   115  // This call panics if the slice is empty.
   116  func (ms *MoveSlice) Back() Move {
   117  	if len(*ms) <= 0 {
   118  		panic("MoveSlice: Back() called when empty")
   119  	}
   120  	return (*ms)[len(*ms)-1]
   121  }
   122  
   123  // At returns the move at index i in the slice without removing the move
   124  // from the slice. At(0) refers to the first move and is the same as Front().
   125  // At(Len()-1) refers to the last move and is the same as Back().
   126  // Index will be checked against bounds and panics if out of bounds
   127  func (ms *MoveSlice) At(i int) Move {
   128  	if len(*ms) == 0 || i < 0 || i >= len(*ms) {
   129  		panic("MoveSlice: Index out of bounds")
   130  	}
   131  	return (*ms)[i]
   132  }
   133  
   134  // Set puts a move at index i in the slice. Set shares the same purpose
   135  // than At() but performs the opposite operation. The index i is the same
   136  // index defined by At().
   137  // Index will be checked against bounds and panics if out of bounds
   138  func (ms *MoveSlice) Set(i int, move Move) {
   139  	if len(*ms) == 0 || i < 0 || i >= len(*ms) {
   140  		panic("MoveSlice: Index out of bounds")
   141  	}
   142  	(*ms)[i] = move
   143  }
   144  
   145  // Filter removes all elements from the MoveSlice for
   146  // which the given call to func will return false.
   147  // Rebuilds the data slice by looping over all elements
   148  // and only re-adding elements for which the call to the
   149  // given func is true. Reuses the underlying array
   150  func (ms *MoveSlice) Filter(f func(index int) bool) {
   151  	b := (*ms)[:0]
   152  	for i, x := range *ms {
   153  		if f(i) {
   154  			b = append(b, x)
   155  		}
   156  	}
   157  	*ms = b
   158  }
   159  
   160  // FilterCopy copies the MoveSlice into the given destination slice
   161  // without the filtered elements. An element is filtered when
   162  // the given call to func will return false for the element.
   163  func (ms *MoveSlice) FilterCopy(dest *MoveSlice, f func(index int) bool) {
   164  	for i, x := range *ms {
   165  		if f(i) {
   166  			*dest = append(*dest, x)
   167  		}
   168  	}
   169  }
   170  
   171  // Clone copies the MoveSlice into a newly create MoveSlice
   172  // doing a deep copy.
   173  func (ms *MoveSlice) Clone() *MoveSlice {
   174  	dest := make([]Move, ms.Len(), ms.Cap())
   175  	copy(dest, *ms)
   176  	return (*MoveSlice)(&dest)
   177  }
   178  
   179  // Equals returns true if all elements of the MoveSlice equals
   180  // the elements of the other MoveSlice
   181  func (ms *MoveSlice) Equals(other *MoveSlice) bool {
   182  	if ms.Len() != other.Len() {
   183  		return false
   184  	}
   185  	for i, m := range *ms {
   186  		if m != (*other)[i] {
   187  			return false
   188  		}
   189  	}
   190  	return true
   191  }
   192  
   193  // ForEach simple range loop calling the given function on each element
   194  // in stored order
   195  func (ms *MoveSlice) ForEach(f func(index int)) {
   196  	for index := range *ms {
   197  		f(index)
   198  	}
   199  }
   200  
   201  // ForEachParallel simple loop over all elements calling a goroutine
   202  // which calls the given func with the index of the current element
   203  // as a parameter.
   204  // Waits until all elements have been processed. There is no
   205  // synchronization for the parallel execution. This needs to done
   206  // in the provided function if necessary
   207  func (ms *MoveSlice) ForEachParallel(f func(index int)) {
   208  	sliceLength := len(*ms)
   209  	var wg sync.WaitGroup
   210  	wg.Add(sliceLength)
   211  	for index := range *ms {
   212  		go func(i int) {
   213  			defer wg.Done()
   214  			f(i)
   215  		}(index)
   216  	}
   217  	wg.Wait()
   218  }
   219  
   220  // Clear removes all moves from the slice, but retains the current capacity.
   221  // This is useful when repeatedly reusing the slice at high frequency to avoid
   222  // GC during reuse.
   223  func (ms *MoveSlice) Clear() {
   224  	// *ms = nil
   225  	*ms = (*ms)[:0]
   226  }
   227  
   228  // Sort will sort moves from highest Value to lowest Value .
   229  // It uses a stable InsertionSort as MoveSlices are mostly pre-sorted and small.
   230  // Sorts move only on the base of their Value (Move value == move&0xFFFF0000).
   231  // Otherwise the order will not be changed.
   232  func (ms *MoveSlice) Sort() {
   233  	l := len(*ms)
   234  	for i := 1; i < l; i++ {
   235  		tmp := (*ms)[i]
   236  		j := i
   237  		for j > 0 && (tmp&0xFFFF0000) > ((*ms)[j-1]&0xFFFF0000) {
   238  			(*ms)[j] = (*ms)[j-1]
   239  			j--
   240  		}
   241  		(*ms)[j] = tmp
   242  	}
   243  }
   244  
   245  // String returns a string representation of a slice of moves
   246  func (ms *MoveSlice) String() string {
   247  	var os strings.Builder
   248  	size := len(*ms)
   249  	os.WriteString(fmt.Sprintf("MoveList: [%d] { ", size))
   250  	for i := 0; i < size; i++ {
   251  		if i > 0 {
   252  			os.WriteString(", ")
   253  		}
   254  		m := ms.At(i)
   255  		os.WriteString(m.String())
   256  	}
   257  	os.WriteString(" }")
   258  	return os.String()
   259  }
   260  
   261  // StringUci returns a string with a space separated list
   262  // of all moves in the list in UCI protocol format
   263  func (ms *MoveSlice) StringUci() string {
   264  	var os strings.Builder
   265  	size := len(*ms)
   266  	for i := 0; i < size; i++ {
   267  		if i > 0 {
   268  			os.WriteString(" ")
   269  		}
   270  		m := (*ms)[i]
   271  		os.WriteString(m.StringUci())
   272  	}
   273  	return os.String()
   274  }