diff --git a/.gitignore b/.gitignore index f8755f7..a5b3cba 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,6 @@ counter/counter # Output of the go coverage tool, specifically when used with LiteIDE *.out + +# +.DS_Store diff --git a/cmd/fengen/main.go b/cmd/fengen/main.go index b28dc94..236d131 100644 --- a/cmd/fengen/main.go +++ b/cmd/fengen/main.go @@ -12,7 +12,6 @@ import ( "github.com/ChizhovVadim/CounterGo/internal/quiet" "github.com/ChizhovVadim/CounterGo/pkg/common" - "github.com/ChizhovVadim/CounterGo/pkg/engine" eval "github.com/ChizhovVadim/CounterGo/pkg/eval/counter" ) @@ -24,22 +23,6 @@ func quietServiceBuilder() IQuietService { return quiet.NewQuietService(eval.NewEvaluationService(), 30) } -type IEngine interface { - Prepare() - Clear() - Search(ctx context.Context, searchParams common.SearchParams) common.SearchInfo -} - -func engineBuilder() IEngine { - var eng = engine.NewEngine(func() engine.Evaluator { - return eval.NewEvaluationService() - }) - eng.Hash = 32 - eng.Threads = 1 - eng.Prepare() - return eng -} - func main() { log.SetFlags(log.LstdFlags | log.Lshortfile) var err = run() diff --git a/cmd/trainhce/dataset.go b/cmd/trainhce/dataset.go new file mode 100644 index 0000000..eca20d3 --- /dev/null +++ b/cmd/trainhce/dataset.go @@ -0,0 +1,111 @@ +package main + +import ( + "bufio" + "fmt" + "os" + "strconv" + "strings" + + "github.com/ChizhovVadim/CounterGo/internal/domain" + + "github.com/ChizhovVadim/CounterGo/pkg/common" +) + +type Sample struct { + Target float32 + domain.TuneEntry +} + +func LoadDataset(filepath string, e ITunableEvaluator, + parser func(string, ITunableEvaluator) (Sample, error)) ([]Sample, error) { + file, err := os.Open(filepath) + if err != nil { + return nil, err + } + defer file.Close() + + var result []Sample + + var scanner = bufio.NewScanner(file) + for scanner.Scan() { + var s = scanner.Text() + var sample, err = parser(s, e) + if err != nil { + return nil, fmt.Errorf("parse fail %v %w", s, err) + } + result = append(result, sample) + } + + err = scanner.Err() + if err != nil { + return nil, err + } + return result, nil +} + +func parseValidationSample(s string, e ITunableEvaluator) (Sample, error) { + var sample Sample + + var index = strings.Index(s, "\"") + if index < 0 { + return Sample{}, fmt.Errorf("bad separator") + } + + var fen = s[:index] + var strScore = s[index+1:] + + var pos, err = common.NewPositionFromFEN(fen) + if err != nil { + return Sample{}, err + } + sample.TuneEntry = e.ComputeFeatures(&pos) + + var prob float32 + if strings.HasPrefix(strScore, "1/2-1/2") { + prob = 0.5 + } else if strings.HasPrefix(strScore, "1-0") { + prob = 1.0 + } else if strings.HasPrefix(strScore, "0-1") { + prob = 0.0 + } else { + return Sample{}, fmt.Errorf("bad game result") + } + sample.Target = prob + + return sample, nil +} + +func parseTrainingSample(line string, e ITunableEvaluator) (Sample, error) { + var sample Sample + + var fileds = strings.SplitN(line, ";", 3) + if len(fileds) < 3 { + return Sample{}, fmt.Errorf("Bad line") + } + + var fen = fileds[0] + var pos, err = common.NewPositionFromFEN(fen) + if err != nil { + return Sample{}, err + } + sample.TuneEntry = e.ComputeFeatures(&pos) + + var sScore = fileds[1] + score, err := strconv.Atoi(sScore) + if err != nil { + return Sample{}, err + } + + var sResult = fileds[2] + gameResult, err := strconv.ParseFloat(sResult, 64) + if err != nil { + return Sample{}, err + } + + const W = 0.75 + var prob = W*Sigmoid(float64(score)) + (1-W)*gameResult + sample.Target = float32(prob) + + return sample, nil +} diff --git a/cmd/trainhce/gradient.go b/cmd/trainhce/gradient.go new file mode 100644 index 0000000..a44ad55 --- /dev/null +++ b/cmd/trainhce/gradient.go @@ -0,0 +1,44 @@ +package main + +import ( + "math" +) + +type Gradient struct { + Value float64 + M1 float64 + M2 float64 +} + +const ( + Beta1 float64 = 0.9 + Beta2 float64 = 0.999 +) + +// Implementing Gradient + +func (g *Gradient) Update(delta float64) { + g.Value += delta +} + +func (g *Gradient) Calculate() float64 { + + if g.Value == 0 { + // nothing to calculate + return 0 + } + + g.M1 = g.M1*Beta1 + g.Value*(1-Beta1) + g.M2 = g.M2*Beta2 + (g.Value*g.Value)*(1-Beta2) + + return LearningRate * g.M1 / (math.Sqrt(g.M2) + 1e-8) +} + +func (g *Gradient) Reset() { + g.Value = 0.0 +} + +func (g *Gradient) Apply(elem *float64) { + *elem -= g.Calculate() + g.Reset() +} diff --git a/cmd/trainhce/main.go b/cmd/trainhce/main.go new file mode 100644 index 0000000..0731cce --- /dev/null +++ b/cmd/trainhce/main.go @@ -0,0 +1,74 @@ +package main + +import ( + "fmt" + "log" + "math" + "math/rand" + "runtime" + + "github.com/ChizhovVadim/CounterGo/internal/domain" + eval "github.com/ChizhovVadim/CounterGo/pkg/eval/linear" + + "github.com/ChizhovVadim/CounterGo/pkg/common" +) + +type ITunableEvaluator interface { + StartingWeights() []float64 + ComputeFeatures(pos *common.Position) domain.TuneEntry +} + +func main() { + log.SetFlags(log.LstdFlags | log.Lshortfile) + + var e = eval.NewEvaluationService() + var trainingPath = "/Users/vadimchizhov/chess/fengen.txt" + var validationPath = "/Users/vadimchizhov/chess/tuner/quiet-labeled.epd" + var threads = 4 + var epochs = 100 + + var err = run(e, trainingPath, validationPath, threads, epochs) + if err != nil { + log.Println(err) + } +} + +func run(evaluator ITunableEvaluator, trainingPath, validationPath string, threads, epochs int) error { + td, err := LoadDataset(trainingPath, evaluator, parseTrainingSample) + if err != nil { + return err + } + log.Println("Loaded dataset", len(td)) + runtime.GC() + + vd, err := LoadDataset(validationPath, evaluator, parseValidationSample) + if err != nil { + return err + } + log.Println("Loaded validation", len(vd)) + + var weights = evaluator.StartingWeights() + log.Println("Num of weights", len(weights)) + + var trainer = &Trainer{ + threads: threads, + weigths: weights, + gradients: make([]Gradient, len(weights)), + training: td, + validation: vd, + rnd: rand.New(rand.NewSource(0)), + } + + err = trainer.Train(epochs) + if err != nil { + return err + } + + var wInt = make([]int, len(trainer.weigths)) + for i := range wInt { + wInt[i] = int(math.Round(100 * trainer.weigths[i])) + } + fmt.Printf("var w = %#v\n", wInt) + + return nil +} diff --git a/cmd/trainhce/trainer.go b/cmd/trainhce/trainer.go new file mode 100644 index 0000000..dd2dca5 --- /dev/null +++ b/cmd/trainhce/trainer.go @@ -0,0 +1,130 @@ +package main + +import ( + "log" + "math/rand" + "sync" + "sync/atomic" +) + +const ( + BatchSize = 16384 +) + +type Trainer struct { + threads int + weigths []float64 + gradients []Gradient + training []Sample + validation []Sample + rnd *rand.Rand +} + +func (t *Trainer) Shuffle() { + t.rnd.Shuffle(len(t.training), func(i, j int) { + t.training[i], t.training[j] = t.training[j], t.training[i] + }) +} + +func (t *Trainer) computeOutput(sample *Sample) float64 { + var output, _ = t.computeOutput2(sample) + return output +} + +func (t *Trainer) computeOutput2(sample *Sample) (output, strongScale float64) { + var mg, eg float64 + for _, f := range sample.Features { + var val = float64(f.Value) + mg += val * t.weigths[2*f.Index] + eg += val * t.weigths[2*f.Index+1] + } + var mix = mg*float64(sample.MgPhase) + eg*float64(sample.EgPhase) + if mix > 0 { + strongScale = float64(sample.WhiteStrongScale) + } else { + strongScale = float64(sample.BlackStrongScale) + } + output = Sigmoid(strongScale * mix) + return +} + +func (t *Trainer) calcCost(samples []Sample) float64 { + var totalCost float64 + var index int32 = -1 + var wg = &sync.WaitGroup{} + var mu = &sync.Mutex{} + for i := 0; i < t.threads; i++ { + wg.Add(1) + go func() { + defer wg.Done() + var localCost float64 + for { + var i = int(atomic.AddInt32(&index, 1)) + if i >= len(samples) { + break + } + sample := &samples[i] + predicted := t.computeOutput(&samples[i]) + cost := ValidationCost(predicted, float64(sample.Target)) + localCost += cost + } + mu.Lock() + totalCost += localCost + mu.Unlock() + }() + } + wg.Wait() + averageCost := totalCost / float64(len(samples)) + return averageCost +} + +func (t *Trainer) Train(epochs int) error { + log.Println("Train started") + defer log.Println("Train finished") + + for epoch := 1; epoch <= epochs; epoch++ { + t.StartEpoch() + log.Printf("Finished Epoch %v\n", epoch) + + validationCost := t.calcCost(t.validation) + log.Printf("Current validation cost is: %f\n", validationCost) + + if epoch%10 == 0 { + trainingCost := t.calcCost(t.training) + log.Printf("Current training cost is: %f\n", trainingCost) + } + } + + return nil +} + +func (t *Trainer) StartEpoch() { + t.Shuffle() + for i := 0; i+BatchSize <= len(t.training); i += BatchSize { + var batch = t.training[i : i+BatchSize] + t.trainBatch(batch) + } +} + +func (t *Trainer) trainBatch(samples []Sample) { + for weightIndex := range t.gradients { + t.gradients[weightIndex].Reset() + } + + for i := range samples { + var sample = &samples[i] + var output, strongScale = t.computeOutput2(sample) + outputGradient := strongScale * CalculateCostGradient(output, float64(sample.Target)) * SigmoidPrime(output) + var mgOutputGradient = outputGradient * float64(sample.MgPhase) + var egOutputGradient = outputGradient * float64(sample.EgPhase) + for _, f := range sample.Features { + var val = float64(f.Value) + t.gradients[2*f.Index].Update(mgOutputGradient * val) + t.gradients[2*f.Index+1].Update(egOutputGradient * val) + } + } + + for weightIndex := range t.gradients { + t.gradients[weightIndex].Apply(&t.weigths[weightIndex]) + } +} diff --git a/cmd/trainhce/utils.go b/cmd/trainhce/utils.go new file mode 100644 index 0000000..221b8f2 --- /dev/null +++ b/cmd/trainhce/utils.go @@ -0,0 +1,25 @@ +package main + +import "math" + +var ( + SigmoidScale = 3.5 / 512 + LearningRate = 0.01 +) + +func Sigmoid(x float64) float64 { + return 1.0 / (1.0 + math.Exp(SigmoidScale*(-x))) +} + +func SigmoidPrime(x float64) float64 { + return x * (1 - x) * SigmoidScale +} + +func ValidationCost(output, target float64) float64 { + var x = output - target + return x * x +} + +func CalculateCostGradient(output, target float64) float64 { + return 2.0 * (output - target) +} diff --git a/internal/domain/types.go b/internal/domain/types.go new file mode 100644 index 0000000..195c5b7 --- /dev/null +++ b/internal/domain/types.go @@ -0,0 +1,14 @@ +package domain + +type TuneEntry struct { + Features []FeatureInfo + MgPhase float32 + EgPhase float32 + WhiteStrongScale float32 + BlackStrongScale float32 +} + +type FeatureInfo struct { + Index int16 + Value int16 +} diff --git a/pkg/eval/linear/evaluation.go b/pkg/eval/linear/evaluation.go new file mode 100644 index 0000000..d5e5dc3 --- /dev/null +++ b/pkg/eval/linear/evaluation.go @@ -0,0 +1,488 @@ +package eval + +import ( + "errors" + + "github.com/ChizhovVadim/CounterGo/internal/domain" + + . "github.com/ChizhovVadim/CounterGo/pkg/common" +) + +const totalPhase = 24 + +var errAddComplexFeature = errors.New("errAddComplexFeature") + +type Score struct { + Mg int + Eg int +} + +type EvaluationService struct { + score Score + features []int + weights []Score + pieceCount [COLOUR_NB][PIECE_NB]int + phase int + mobilityArea [COLOUR_NB]uint64 + attacked [COLOUR_NB]uint64 + attackedBy2 [COLOUR_NB]uint64 + attackedBy [COLOUR_NB][PIECE_NB]uint64 + pawnAttacksBy2 [COLOUR_NB]uint64 + kingAttacksCount [COLOUR_NB]int + kingSq [COLOUR_NB]int + kingAttackersCount [COLOUR_NB]int + kingAreas [COLOUR_NB]uint64 +} + +func NewEvaluationService() *EvaluationService { + var e = &EvaluationService{ + features: make([]int, totalFeatureSize), + weights: make([]Score, totalFeatureSize), + } + e.initWeights() + return e +} + +func (e *EvaluationService) initWeights() { + if 2*len(e.features) != len(w) { + return + } + for i := range e.weights { + e.weights[i] = Score{Mg: w[2*i], Eg: w[2*i+1]} + } +} + +var kingAttackWeight = [...]int{2, 4, 8, 12, 13, 14, 15, 16} + +func (e *EvaluationService) Evaluate(p *Position) int { + e.init(p) + e.evalFirstPass(p) + e.evalSecondPass(p) + + e.addFeature(fPawnValue, e.pieceCount[SideWhite][Pawn]-e.pieceCount[SideBlack][Pawn]) + e.addFeature(fKnightValue, e.pieceCount[SideWhite][Knight]-e.pieceCount[SideBlack][Knight]) + e.addFeature(fBishopValue, e.pieceCount[SideWhite][Bishop]-e.pieceCount[SideBlack][Bishop]) + e.addFeature(fRookValue, e.pieceCount[SideWhite][Rook]-e.pieceCount[SideBlack][Rook]) + e.addFeature(fQueenValue, e.pieceCount[SideWhite][Queen]-e.pieceCount[SideBlack][Queen]) + + if p.WhiteMove { + e.addFeature(fTempo, 1) + } else { + e.addFeature(fTempo, -1) + } + + var phase = e.pieceCount[SideWhite][Knight] + e.pieceCount[SideBlack][Knight] + + e.pieceCount[SideWhite][Bishop] + e.pieceCount[SideBlack][Bishop] + + 2*(e.pieceCount[SideWhite][Rook]+e.pieceCount[SideBlack][Rook]) + + 4*(e.pieceCount[SideWhite][Queen]+e.pieceCount[SideBlack][Queen]) + if phase > totalPhase { + phase = totalPhase + } + e.phase = phase + + var result = (e.score.Mg*phase + e.score.Eg*(totalPhase-phase)) / (totalPhase * 100) + var strongSide int + if result > 0 { + strongSide = SideWhite + } else { + strongSide = SideBlack + } + result = result * e.computeFactor(strongSide, p) / scaleNormal + + if !p.WhiteMove { + result = -result + } + + return result +} + +func (e *EvaluationService) init(p *Position) { + e.score = Score{} + + for pt := Pawn; pt <= King; pt++ { + e.pieceCount[SideWhite][pt] = 0 + e.pieceCount[SideBlack][pt] = 0 + + e.attackedBy[SideWhite][pt] = 0 + e.attackedBy[SideBlack][pt] = 0 + } + + e.attacked[SideWhite] = 0 + e.attacked[SideBlack] = 0 + e.attackedBy2[SideWhite] = 0 + e.attackedBy2[SideBlack] = 0 + e.kingAttackersCount[SideWhite] = 0 + e.kingAttackersCount[SideBlack] = 0 + e.kingAttacksCount[SideWhite] = 0 + e.kingAttacksCount[SideBlack] = 0 + + e.kingSq[SideWhite] = FirstOne(p.Kings & p.White) + e.kingSq[SideBlack] = FirstOne(p.Kings & p.Black) + + e.kingAreas[SideWhite] = kingAreaMasks[SideWhite][e.kingSq[SideWhite]] + e.kingAreas[SideBlack] = kingAreaMasks[SideBlack][e.kingSq[SideBlack]] + + e.attackedBy[SideWhite][Pawn] = AllWhitePawnAttacks(p.Pawns & p.White) + e.attackedBy[SideBlack][Pawn] = AllBlackPawnAttacks(p.Pawns & p.Black) + + e.pawnAttacksBy2[SideWhite] = UpLeft(p.Pawns&p.White) & UpRight(p.Pawns&p.White) + e.pawnAttacksBy2[SideBlack] = DownLeft(p.Pawns&p.Black) & DownRight(p.Pawns&p.Black) + + e.attacked[SideWhite] |= e.attackedBy[SideWhite][Pawn] + e.attacked[SideBlack] |= e.attackedBy[SideBlack][Pawn] + + e.mobilityArea[SideWhite] = ^(p.Pawns&p.White | e.attackedBy[SideBlack][Pawn]) + e.mobilityArea[SideBlack] = ^(p.Pawns&p.Black | e.attackedBy[SideWhite][Pawn]) +} + +func (e *EvaluationService) evalFirstPass(p *Position) { + var x, attacks uint64 + var sq int + + var occ = p.AllPieces() + + for side := SideWhite; side <= SideBlack; side++ { + var sign int + var forward int + if side == SideWhite { + sign = 1 + forward = 8 + } else { + sign = -1 + forward = -8 + } + var US = side + var THEM = side ^ 1 + var friendly = p.Colours(US) + var enemy = p.Colours(THEM) + + for x = p.Pawns & friendly; x != 0; x &= x - 1 { + sq = FirstOne(x) + e.pieceCount[US][Pawn]++ + e.addComplexFeature(fPawnPST, relativeSq32(side, sq), sign) + + if PawnAttacksNew(THEM, sq)&friendly&p.Pawns != 0 { + e.addComplexFeature(fPawnProtected, relativeSq32(side, sq), sign) + } + if adjacentFilesMask[File(sq)]&ranks[Rank(sq)]&friendly&p.Pawns != 0 { + e.addComplexFeature(fPawnDuo, relativeSq32(side, sq), sign) + } + + if adjacentFilesMask[File(sq)]&friendly&p.Pawns == 0 { + e.addFeature(fPawnIsolated, sign) + } + if FileMask[File(sq)]&^SquareMask[sq]&friendly&p.Pawns != 0 { + e.addFeature(fPawnDoubled, sign) + } + + var stoppers = enemy & p.Pawns & passedPawnMasks[side][sq] + // passed pawn + if stoppers == 0 && upperRankMasks[US][Rank(sq)]&FileMask[File(sq)]&p.Pawns == 0 { + var r = Max(0, relativeRankOf(side, sq)-Rank3) + e.addComplexFeature(fPassedPawn, r, sign) + var keySq = sq + forward + if enemy&SquareMask[keySq] == 0 { + e.addComplexFeature(fPassedCanMove, r, sign) + } + e.addComplexFeature(fPassedEnemyKing, 8*r+distanceBetween[keySq][e.kingSq[THEM]], sign) + e.addComplexFeature(fPassedOwnKing, 8*r+distanceBetween[keySq][e.kingSq[US]], sign) + } + } + + for x = p.Knights & friendly; x != 0; x &= x - 1 { + sq = FirstOne(x) + e.pieceCount[US][Knight]++ + e.addComplexFeature(fKnightPST, relativeSq32(side, sq), sign) + + attacks = KnightAttacks[sq] + e.addComplexFeature(fKnightMobility, PopCount(attacks&e.mobilityArea[US]), sign) + + e.attackedBy2[US] |= e.attacked[US] & attacks + e.attacked[US] |= attacks + e.attackedBy[US][Knight] |= attacks + + attacks &= e.kingAreas[THEM] &^ e.pawnAttacksBy2[THEM] + if attacks != 0 { + e.kingAttackersCount[THEM]++ + e.kingAttacksCount[THEM] += PopCount(attacks) + } + + if outpostSquares[side]&SquareMask[sq] != 0 && + outpostSquareMasks[US][sq]&enemy&p.Pawns == 0 { + e.addFeature(fKnightOutpost, sign) + } + } + + for x = p.Bishops & friendly; x != 0; x &= x - 1 { + sq = FirstOne(x) + e.pieceCount[US][Bishop]++ + e.addComplexFeature(fBishopPST, relativeSq32(side, sq), sign) + + attacks = BishopAttacks(sq, occ) + e.addComplexFeature(fBishopMobility, PopCount(attacks&e.mobilityArea[US]), sign) + + e.attackedBy2[US] |= e.attacked[US] & attacks + e.attacked[US] |= attacks + e.attackedBy[US][Bishop] |= attacks + + attacks &= e.kingAreas[THEM] &^ e.pawnAttacksBy2[THEM] + if attacks != 0 { + e.kingAttackersCount[THEM]++ + e.kingAttacksCount[THEM] += PopCount(attacks) + } + + if side == SideWhite { + e.addFeature(fBishopRammedPawns, + PopCount(sameColorSquares(sq)&p.Pawns&p.White&Down(p.Pawns&p.Black))) + } else { + e.addFeature(fBishopRammedPawns, + -PopCount(sameColorSquares(sq)&p.Pawns&p.Black&Up(p.Pawns&p.White))) + } + } + + for x = p.Rooks & friendly; x != 0; x &= x - 1 { + sq = FirstOne(x) + e.pieceCount[US][Rook]++ + e.addComplexFeature(fRookPST, relativeSq32(side, sq), sign) + + attacks = RookAttacks(sq, occ&^(friendly&p.Rooks)) + e.addComplexFeature(fRookMobility, PopCount(attacks&e.mobilityArea[US]), sign) + + e.attackedBy2[US] |= e.attacked[US] & attacks + e.attacked[US] |= attacks + e.attackedBy[US][Rook] |= attacks + + attacks &= e.kingAreas[THEM] &^ e.pawnAttacksBy2[THEM] + if attacks != 0 { + e.kingAttackersCount[THEM]++ + e.kingAttacksCount[THEM] += PopCount(attacks) + } + + attacks = FileMask[File(sq)] + if (attacks & friendly & p.Pawns) == 0 { + if (attacks & p.Pawns) == 0 { + e.addFeature(fRookOpen, sign) + } else { + e.addFeature(fRookSemiopen, sign) + } + } + } + + for x = p.Queens & friendly; x != 0; x &= x - 1 { + sq = FirstOne(x) + e.pieceCount[US][Queen]++ + e.addComplexFeature(fQueenPST, relativeSq32(side, sq), sign) + + attacks = QueenAttacks(sq, occ) + e.addComplexFeature(fQueenMobility, PopCount(attacks&e.mobilityArea[US]), sign) + + e.attackedBy2[US] |= e.attacked[US] & attacks + e.attacked[US] |= attacks + e.attackedBy[US][Queen] |= attacks + + attacks &= e.kingAreas[THEM] &^ e.pawnAttacksBy2[THEM] + if attacks != 0 { + e.kingAttackersCount[THEM]++ + e.kingAttacksCount[THEM] += PopCount(attacks) + } + } + + { + // KING + sq = e.kingSq[US] + e.addComplexFeature(fKingPST, relativeSq32(side, sq), sign) + + attacks = KingAttacks[sq] + e.attackedBy2[US] |= e.attacked[US] & attacks + e.attacked[US] |= attacks + e.attackedBy[US][King] |= attacks + + for x = kingShieldMasks[US][sq] & friendly & p.Pawns; x != 0; x &= x - 1 { + var sq = FirstOne(x) + e.addPst12(fKingShield, side, sq, sign) + } + + /*for file := Max(FileA, File(sq)-1); file <= Min(FileH, File(sq)+1); file++ { + var ours = friendly & p.Pawns & FileMask[file] & forwardRanksMasks[US][Rank(sq)] + var ourDist int + if ours == 0 { + ourDist = 7 + } else { + ourDist = Rank(sq) - Rank(Backmost(US, ours)) + if ourDist < 0 { + ourDist = -ourDist + } + } + e.addComplexFeature(fKingShield, 8*file+ourDist, sign) + }*/ + } + + if e.pieceCount[US][Bishop] >= 2 { + e.addFeature(fBishopPair, sign) + } + } + + e.addFeature(fMinorBehindPawn, + PopCount((p.Knights|p.Bishops)&p.White&Down(p.Pawns))- + PopCount((p.Knights|p.Bishops)&p.Black&Up(p.Pawns))) +} + +func (e *EvaluationService) evalSecondPass(p *Position) { + var occ = p.AllPieces() + + for side := SideWhite; side <= SideBlack; side++ { + var sign int + if side == SideWhite { + sign = 1 + } else { + sign = -1 + } + var US = side + var THEM = side ^ 1 + var friendly = p.Colours(US) + //var enemy = p.Colours(THEM) + + { + // king safety + + var val = sign * kingAttackWeight[Min(len(kingAttackWeight)-1, e.kingAttackersCount[THEM])] + + weak := e.attacked[US] & ^e.attackedBy2[THEM] & (^e.attacked[THEM] | e.attackedBy[THEM][Queen] | e.attackedBy[THEM][King]) + safe := ^friendly & (^e.attacked[THEM] | (weak & e.attackedBy2[US])) + + knightThreats := KnightAttacks[e.kingSq[THEM]] + bishopThreats := BishopAttacks(e.kingSq[THEM], occ) + rookThreats := RookAttacks(e.kingSq[THEM], occ) + queenThreats := bishopThreats | rookThreats + + e.addFeature(fSafetyKnightCheck, val*PopCount(knightThreats&safe&e.attackedBy[US][Knight])) + e.addFeature(fSafetyBishopCheck, val*PopCount(bishopThreats&safe&e.attackedBy[US][Bishop])) + e.addFeature(fSafetyRookCheck, val*PopCount(rookThreats&safe&e.attackedBy[US][Rook])) + e.addFeature(fSafetyQueenCheck, val*PopCount(queenThreats&safe&e.attackedBy[US][Queen])) + e.addFeature(fSafetyWeakSquares, val*PopCount(e.kingAreas[THEM]&weak)) + } + + { + // threats + + var knights = friendly & p.Knights + var bishops = friendly & p.Bishops + var rooks = friendly & p.Rooks + var queens = friendly & p.Queens + + var attacksByPawns = e.attackedBy[THEM][Pawn] + var attacksByMinors = e.attackedBy[THEM][Knight] | e.attackedBy[THEM][Bishop] + var attacksByMajors = e.attackedBy[THEM][Rook] | e.attackedBy[THEM][Queen] + + var poorlyDefended = (e.attacked[THEM] & ^e.attacked[US]) | + (e.attackedBy2[THEM] & ^e.attackedBy2[US] & ^e.attackedBy[US][Pawn]) + + var weakMinors = (knights | bishops) & poorlyDefended + + e.addFeature(fThreatWeakPawn, sign*PopCount(friendly&p.Pawns & ^attacksByPawns & poorlyDefended)) + e.addFeature(fThreatMinorAttackedByPawn, sign*PopCount((knights|bishops)&attacksByPawns)) + e.addFeature(fThreatMinorAttackedByMinor, sign*PopCount((knights|bishops)&attacksByMinors)) + e.addFeature(fThreatMinorAttackedByMajor, sign*PopCount(weakMinors&attacksByMajors)) + e.addFeature(fThreatRookAttackedByLesser, sign*PopCount(rooks&(attacksByPawns|attacksByMinors))) + e.addFeature(fThreatMinorAttackedByKing, sign*PopCount(weakMinors&e.attackedBy[THEM][King])) + e.addFeature(fThreatRookAttackedByKing, sign*PopCount(rooks&poorlyDefended&e.attackedBy[THEM][King])) + e.addFeature(fThreatQueenAttackedByOne, sign*PopCount(queens&e.attacked[THEM])) + } + } +} + +const ( + scaleNormal = 16 +) + +func (e *EvaluationService) computeFactor(own int, p *Position) int { + var them = own ^ 1 + var ownPawns = e.pieceCount[own][Pawn] + if ownPawns <= 1 { + var ownForce = computeForce(e, own) + var theirForce = computeForce(e, own^1) + if ownPawns == 0 { + if ownForce <= 4 { + return scaleNormal * 1 / 16 + } + if ownForce-theirForce <= 4 { + return scaleNormal * 1 / 4 + } + } else if ownPawns == 1 { + var theirMinor = e.pieceCount[them][Knight]+e.pieceCount[them][Bishop] != 0 + if ownForce <= 4 && theirMinor { + return scaleNormal * 1 / 8 + } + if ownForce == theirForce && theirMinor { + return scaleNormal * 1 / 2 + } + } + } + return scaleNormal +} + +func computeForce(e *EvaluationService, side int) int { + return 4*(e.pieceCount[side][Knight]+e.pieceCount[side][Bishop]) + + 6*e.pieceCount[side][Rook] + + 12*e.pieceCount[side][Queen] +} + +func (e *EvaluationService) StartingWeights() []float64 { + var material = []float64{100, 100, 325, 325, 325, 325, 500, 500, 1000, 1000} + var result = make([]float64, 2*totalFeatureSize) + copy(result, material) + return result +} + +func (e *EvaluationService) ComputeFeatures(pos *Position) domain.TuneEntry { + for i := range e.features { + e.features[i] = 0 + } + e.Evaluate(pos) + var size int + for _, v := range e.features { + if v != 0 { + size++ + } + } + var features = make([]domain.FeatureInfo, 0, size) + for i, v := range e.features { + if v != 0 { + features = append(features, domain.FeatureInfo{Index: int16(i), Value: int16(v)}) + } + } + var result = domain.TuneEntry{ + Features: features, + MgPhase: float32(e.phase) / totalPhase, + WhiteStrongScale: float32(e.computeFactor(SideWhite, pos)) / scaleNormal, + BlackStrongScale: float32(e.computeFactor(SideBlack, pos)) / scaleNormal, + } + result.EgPhase = 1 - result.MgPhase + return result +} + +func (e *EvaluationService) addPst12(feature, side, sq, value int) { + e.addComplexFeature(feature, file4(sq), value) + e.addComplexFeature(feature, 4+relativeRankOf(side, sq), value) +} + +func (e *EvaluationService) addMobility(feature, side, sq, mobility, value int) { + value *= sqrtInt[mobility] + e.addComplexFeature(feature, file4(sq), value) + e.addComplexFeature(feature, 4+relativeRankOf(side, sq), value) +} + +func (e *EvaluationService) addFeature(feature, value int) { + e.addComplexFeature(feature, 0, value) +} + +func (e *EvaluationService) addComplexFeature(feature, featureIndex, value int) { + var info = &infos[feature] + if featureIndex >= info.Size { + return + } + var index = info.StartIndex + featureIndex + e.score.Mg += value * e.weights[index].Mg + e.score.Eg += value * e.weights[index].Eg + e.features[index] += value +} diff --git a/pkg/eval/linear/feature.go b/pkg/eval/linear/feature.go new file mode 100644 index 0000000..2f2a2a3 --- /dev/null +++ b/pkg/eval/linear/feature.go @@ -0,0 +1,119 @@ +package eval + +const ( + fPawnValue = iota + fKnightValue + fBishopValue + fRookValue + fQueenValue + fBishopPair + fPawnPST + fKnightPST + fBishopPST + fRookPST + fQueenPST + fKingPST + fKnightMobility + fBishopMobility + fRookMobility + fQueenMobility + fPassedPawn + fPassedCanMove + fPassedEnemyKing + fPassedOwnKing + fPawnDuo + fPawnProtected + fPawnIsolated + fPawnDoubled + fKingShield + fSafetyWeakSquares + fSafetyKnightCheck + fSafetyBishopCheck + fSafetyRookCheck + fSafetyQueenCheck + fThreatMinorAttackedByPawn + fThreatMinorAttackedByMinor + fThreatMinorAttackedByMajor + fThreatRookAttackedByLesser + fThreatMinorAttackedByKing + fThreatRookAttackedByKing + fThreatQueenAttackedByOne + fThreatWeakPawn + fKnightOutpost + fMinorBehindPawn + fBishopRammedPawns + fRookOpen + fRookSemiopen + fTempo + fSize +) + +type FeatureInfo struct { + Name string + Size int + StartIndex int +} + +var infos = [fSize]FeatureInfo{ + fPawnValue: {Name: "PawnValue"}, + fKnightValue: {Name: "KnightValue"}, + fBishopValue: {Name: "BishopValue"}, + fRookValue: {Name: "RookValue"}, + fQueenValue: {Name: "QueenValue"}, + fBishopPair: {Name: "BishopPair"}, + fPawnPST: {Name: "PawnPST", Size: 32}, + fKnightPST: {Name: "KnightPST", Size: 32}, + fBishopPST: {Name: "BishopPST", Size: 32}, + fRookPST: {Name: "RookPST", Size: 32}, + fQueenPST: {Name: "QueenPST", Size: 32}, + fKingPST: {Name: "KingPST", Size: 32}, + fKnightMobility: {Name: "KnightMobility", Size: 9}, + fBishopMobility: {Name: "BishopMobility", Size: 14}, + fRookMobility: {Name: "RookMobility", Size: 15}, + fQueenMobility: {Name: "QueenMobility", Size: 28}, + fPassedPawn: {Name: "PassedPawn", Size: 5}, + fPassedCanMove: {Name: "PassedCanMove", Size: 5}, + fPassedEnemyKing: {Name: "PassedEnemyKing", Size: 5 * 8}, + fPassedOwnKing: {Name: "PassedOwnKing", Size: 5 * 8}, + fPawnDuo: {Name: "PawnDuo", Size: 32}, + fPawnProtected: {Name: "PawnProtected", Size: 32}, + fPawnIsolated: {Name: "PawnIsolated"}, + fPawnDoubled: {Name: "PawnDoubled"}, + fKingShield: {Name: "KingShield", Size: 12}, + fSafetyWeakSquares: {Name: "SafetyWeakSquares"}, + fSafetyKnightCheck: {Name: "SafetyKnightCheck"}, + fSafetyBishopCheck: {Name: "SafetyBishopCheck"}, + fSafetyRookCheck: {Name: "SafetyRookCheck"}, + fSafetyQueenCheck: {Name: "SafetyQueenCheck"}, + fThreatMinorAttackedByPawn: {Name: "fThreatMinorAttackedByPawn"}, + fThreatMinorAttackedByMinor: {Name: "fThreatMinorAttackedByMinor"}, + fThreatMinorAttackedByMajor: {Name: "ThreatMinorAttackedByMajor"}, + fThreatRookAttackedByLesser: {Name: "ThreatRookAttackedByLesser"}, + fThreatMinorAttackedByKing: {Name: "ThreatMinorAttackedByKing"}, + fThreatRookAttackedByKing: {Name: "ThreatRookAttackedByKing"}, + fThreatQueenAttackedByOne: {Name: "ThreatQueenAttackedByOne"}, + fThreatWeakPawn: {Name: "ThreatWeakPawn"}, + fKnightOutpost: {Name: "KnightOutpost"}, + fMinorBehindPawn: {Name: "MinorBehindPawn"}, + fBishopRammedPawns: {Name: "BishopRammedPawns"}, + fRookOpen: {Name: "RookOpen"}, + fRookSemiopen: {Name: "RookSemiopen"}, + fTempo: {Name: "Tempo"}, +} + +var totalFeatureSize int + +func init() { + var startIndex = 0 + for i := range infos { + if infos[i].Name == "" { + continue + } + if infos[i].Size == 0 { + infos[i].Size = 1 + } + infos[i].StartIndex = startIndex + startIndex += infos[i].Size + } + totalFeatureSize = startIndex +} diff --git a/pkg/eval/linear/utils.go b/pkg/eval/linear/utils.go new file mode 100644 index 0000000..12b7cfe --- /dev/null +++ b/pkg/eval/linear/utils.go @@ -0,0 +1,134 @@ +package eval + +import ( + "math" + "math/bits" + + . "github.com/ChizhovVadim/CounterGo/pkg/common" +) + +const ( + darkSquares = uint64(0xAA55AA55AA55AA55) +) + +func sameColorSquares(sq int) uint64 { + if IsDarkSquare(sq) { + return darkSquares + } + return ^darkSquares +} + +func relativeSq32(side, sq int) int { + if side == SideBlack { + sq = FlipSquare(sq) + } + var f = File(sq) + if f >= FileE { + f = FileH - f + } + return f + 4*Rank(sq) +} + +func relativeRankOf(colour, sq int) int { + if colour == SideWhite { + return Rank(sq) + } + return Rank8 - Rank(sq) +} + +func file4(sq int) int { + var f = File(sq) + if f >= FileE { + f = FileH - f + } + return f +} + +func relativeUp(colour int, b uint64) uint64 { + if colour == SideWhite { + return Up(b) + } + return Down(b) +} + +func limit(v, min, max int) int { + if v <= min { + return min + } + if v >= max { + return max + } + return v +} + +func backmost(colour int, bb uint64) int { + if colour == SideWhite { + return bits.TrailingZeros64(bb) + } + return 63 - bits.LeadingZeros64(bb) +} + +var outpostSquares = [COLOUR_NB]uint64{ + (Rank4Mask | Rank5Mask | Rank6Mask), + (Rank5Mask | Rank4Mask | Rank3Mask), +} +var ranks = [8]uint64{Rank1Mask, Rank2Mask, Rank3Mask, Rank4Mask, Rank5Mask, Rank6Mask, Rank7Mask, Rank8Mask} + +var forwardFileMasks [COLOUR_NB][SQUARE_NB]uint64 +var pawnConnectedMask [COLOUR_NB][SQUARE_NB]uint64 +var passedPawnMasks [COLOUR_NB][SQUARE_NB]uint64 +var kingShieldMasks [COLOUR_NB][SQUARE_NB]uint64 +var distanceBetween [SQUARE_NB][SQUARE_NB]int +var kingAreaMasks [COLOUR_NB][SQUARE_NB]uint64 +var adjacentFilesMask [FILE_NB]uint64 +var upperRankMasks [COLOUR_NB][RANK_NB]uint64 //TODO +var forwardRanksMasks [COLOUR_NB][RANK_NB]uint64 +var outpostSquareMasks [COLOUR_NB][SQUARE_NB]uint64 +var sqrtInt [32]int + +func init() { + for i := 0; i < SQUARE_NB; i++ { + for j := 0; j < SQUARE_NB; j++ { + distanceBetween[i][j] = SquareDistance(i, j) + } + } + + for i := range sqrtInt { + sqrtInt[i] = int(math.Round(float64(i))) + } + + for f := FileA; f <= FileH; f++ { + adjacentFilesMask[f] = Left(FileMask[f]) | Right(FileMask[f]) + } + for r := Rank1; r <= Rank8; r++ { + upperRankMasks[SideWhite][r] = UpFill(Up(ranks[r])) + upperRankMasks[SideBlack][r] = DownFill(Down(ranks[r])) + + forwardRanksMasks[SideWhite][r] = UpFill(ranks[r]) + forwardRanksMasks[SideBlack][r] = DownFill(ranks[r]) + } + + for sq := 0; sq < SQUARE_NB; sq++ { + var x = SquareMask[sq] + + forwardFileMasks[SideWhite][sq] = UpFill(x) + forwardFileMasks[SideBlack][sq] = DownFill(x) + + pawnConnectedMask[SideWhite][sq] = Left(x) | Right(x) | Down(Left(x)|Right(x)) + pawnConnectedMask[SideBlack][sq] = Left(x) | Right(x) | Up(Left(x)|Right(x)) + + passedPawnMasks[SideWhite][sq] = UpFill(Up(Left(x) | Right(x) | x)) + passedPawnMasks[SideBlack][sq] = DownFill(Down(Left(x) | Right(x) | x)) + + outpostSquareMasks[SideWhite][sq] = passedPawnMasks[SideWhite][sq] & ^FileMask[File(sq)] + outpostSquareMasks[SideBlack][sq] = passedPawnMasks[SideBlack][sq] & ^FileMask[File(sq)] + + kingShieldMasks[SideWhite][sq] = UpFill(Left(x) | Right(x) | x) + kingShieldMasks[SideBlack][sq] = DownFill(Left(x) | Right(x) | x) + + var kingZoneSq = MakeSquare(limit(File(sq), FileB, FileG), limit(Rank(sq), Rank2, Rank7)) + //var kingZoneSq = sq + kingAreaMasks[SideWhite][sq] = KingAttacks[kingZoneSq] | SquareMask[kingZoneSq] + kingAreaMasks[SideBlack][sq] = kingAreaMasks[SideWhite][sq] + } +} diff --git a/pkg/eval/linear/weights.go b/pkg/eval/linear/weights.go new file mode 100644 index 0000000..582f7d8 --- /dev/null +++ b/pkg/eval/linear/weights.go @@ -0,0 +1,7 @@ +package eval + +// 2022/08/07 22:04:32 trainer.go:87: Finished Epoch 100 +// 2022/08/07 22:04:32 trainer.go:90: Current validation cost is: 0.055720 +// 2022/08/07 22:04:33 trainer.go:94: Current training cost is: 0.013552 +// 2022/08/07 22:04:33 trainer.go:98: Train finished +var w = []int{7719, 11315, 31846, 37245, 32615, 39467, 45516, 65344, 106067, 121669, 2698, 5456, 0, 0, 0, 0, 0, 0, 0, 0, -1550, -729, -421, -662, -832, 11, -1545, -147, -1733, -1034, -945, -1273, -1568, -1213, -1623, -1209, -1407, -356, -1143, -514, 244, -1669, 695, -2432, -217, 388, 303, -524, 817, -1411, 1522, -2181, 1452, 1491, 1656, 1398, 4139, 126, 3330, -111, 4756, 3062, 897, 6267, 5091, 3701, 7343, 3479, 0, 0, 0, 0, 0, 0, 0, 0, -3436, -3242, -1086, -689, -1123, -1039, -605, 274, -651, -1629, -556, 108, -695, -575, -121, 127, -1325, -490, -45, 67, 33, 306, 954, 1093, 380, 771, 1553, 646, 1163, 1515, 915, 2216, 161, 830, 762, 1136, 1744, 1762, 1838, 2332, -1985, 380, 280, 613, 1491, 1222, 1960, 2012, 923, 594, -237, 1704, 3538, 331, 3089, 1707, -13280, -1052, -3019, 1717, -6319, 3046, 331, 1240, -863, 323, 1154, -600, -721, 374, -395, 451, 551, -571, 780, -845, 1123, -424, -241, 405, 72, 54, 1785, 494, 467, 1096, 451, 1309, 402, 246, 150, 895, 50, 1557, 193, 1581, -1279, 993, -488, 1848, 750, 1442, 794, 1934, 799, 695, 2249, 900, 1890, 1307, 2090, 1405, -3652, 1640, -2161, 1638, -447, 1562, -1311, 2046, -4549, 2494, -1374, 2041, -3581, 1309, -3408, 1717, -1199, 587, -1069, 710, -684, 696, 7, 202, -3135, 1374, -2110, 1168, -1688, 934, -958, 710, -2370, 1578, -1812, 1935, -1539, 1529, -1027, 1168, -2177, 2868, -1392, 3051, -1631, 3004, -771, 2274, -736, 3261, 235, 3463, 831, 3063, 1453, 2617, 414, 3666, 2487, 3128, 2402, 3117, 2999, 2644, 1939, 3511, 594, 4150, 3149, 3232, 2898, 3385, 5141, 2157, 4564, 3136, 4949, 2831, 3667, 3342, 369, -2648, 347, -2844, 362, -3513, 28, -1855, 527, -1653, 900, -2961, 1021, -2889, 875, -2133, 36, -122, 278, 523, 66, 1022, -144, 1055, -174, 2501, -176, 3005, -392, 3014, -966, 4244, -256, 3015, -631, 4967, -521, 5000, -1086, 6102, 1146, 2199, 625, 3981, 57, 5772, 985, 4804, -1077, 3742, -2729, 5472, -717, 5640, -781, 6477, 3560, 1819, 4123, 1959, 3467, 2995, 4406, 2903, 1423, -4872, 534, -2596, -619, -2746, 2347, -4848, -88, -1698, -1518, -480, -2353, -636, -1123, -1242, -1674, -889, -844, -18, -934, 162, 387, -217, -1615, -429, -428, 626, -1262, 1504, -2079, 1611, -4326, 1039, -1591, 2317, -4870, 3473, -8401, 3730, -3615, 1659, -1371, 3486, -3830, 4366, -6025, 3691, -4818, -757, -4230, 3927, -605, 3455, 439, 1993, -4467, -13423, 4652, -320, 10880, -266, 8838, -243, -4394, -5302, -2095, -2991, -1208, -888, -259, 426, 190, 1385, 731, 2214, 1311, 2712, 1761, 2891, 2045, 2751, -3127, -6528, -1977, -2507, -766, -1107, -165, 133, 450, 962, 964, 1729, 1277, 2286, 1468, 2673, 1638, 3054, 1623, 3183, 1628, 3416, 2134, 3537, 2492, 4152, 2990, 3765, -3982, -5124, -2462, -3173, -1149, -520, -828, 560, -332, 1369, -235, 2322, -248, 2819, -127, 3148, 195, 3584, 459, 3984, 661, 4226, 818, 4673, 711, 5037, 1349, 4978, 1743, 4721, -725, -725, -3141, -1129, -3355, -10189, -1208, -9714, -492, -4795, -676, -1653, -451, -510, -119, 65, 133, 665, 311, 1314, 481, 1835, 554, 2195, 609, 2876, 760, 3093, 792, 3543, 674, 4096, 546, 4282, 800, 4069, 886, 4323, 1271, 3768, 1389, 3589, 3221, 1974, 3194, 2373, 3982, 949, 6026, 81, 4984, -746, 1499, -503, 3504, -658, -406, 1231, 103, 2472, 918, 3654, 3238, 4262, 6081, 5429, 421, -87, -115, 1353, 235, 2286, -85, 4058, 5613, 4140, -3396, -2290, 3065, -1263, -980, 370, 419, 436, 517, 383, 508, 801, -1734, 2022, -2781, 2759, 244, -3555, 3491, -3371, 1693, -654, 90, 1185, -696, 2928, -277, 4111, -1231, 5528, -466, 5579, -207, -5680, 3710, -5434, 1210, -1018, 86, 2230, -163, 5221, 148, 7568, -409, 8920, 457, 9712, -108, -7917, 4862, -9639, 3442, -2302, 405, 3439, -262, 8118, -401, 11585, 1741, 12490, 4738, 12108, -2381, -12376, 4937, -12242, 6579, -5370, 3710, 3245, 1627, 8369, 1792, 11764, 1891, 13925, 7621, 15446, -3929, 1677, -1038, 1875, -218, 809, -339, 146, -1302, 316, -1177, 100, 1760, -860, 1119, -1789, -4735, 3695, -3353, 4271, -507, 1953, -883, 348, -18, -1235, 279, -1374, 2674, -2171, 276, -2082, 461, 4954, 2062, 6527, 3317, 2287, -586, 1485, -1000, 137, -402, -1185, 1327, -2145, 1165, -2510, 7869, 6149, 10690, 11100, 18851, 1417, 10222, 652, 1001, 876, -926, 280, -2220, -206, -299, -1146, 12610, 12391, 10717, 16782, 16753, 6427, 16828, 1218, 14193, -801, 8340, -1255, -228, 434, -3850, 1602, 0, 0, 0, 0, 0, 0, 0, 0, 142, -746, 653, 167, 304, -175, 887, 1492, 250, -172, 877, 423, 503, 768, 1229, 1184, 355, 981, 1044, 701, 501, 1681, 1282, 1767, 2202, 3479, -445, 3753, 2922, 3598, 2042, 3668, 2360, 6917, 5026, 9551, 6721, 9655, 6951, 8901, 9660, 11683, 12389, 14830, 8726, 13237, 6984, 10564, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1334, 168, 1380, 604, 1315, 1147, 1829, 2102, 832, -28, 1475, 197, 927, 797, 1423, 1788, 652, 996, 1317, 1211, 1710, 1725, 2149, 2242, 816, 5666, 2931, 5347, 3622, 5829, 5333, 6522, 6876, 8598, 19069, 5280, 9982, 9382, 11258, 9712, 0, 0, 0, 0, 0, 0, 0, 0, -933, -1008, -646, -475, 2119, -803, 2623, -239, 1034, 577, 354, 770, 0, 0, 1935, -898, 1218, -753, 726, -719, 323, -451, 154, 76, -2027, 1480, 0, 0, 257, -53, 856, -79, 260, 280, 927, 53, 507, 365, -3768, -3243, -1947, -2089, -2051, -3394, -4390, -1725, -1540, -1961, -2086, -894, -3606, -2377, -787, -3154, 2346, -71, 455, 1216, -492, -1482, 3030, -92, 1059, 1371, 1295, 772} diff --git a/pkg/eval/linear/weights_test.go b/pkg/eval/linear/weights_test.go new file mode 100644 index 0000000..0eb027a --- /dev/null +++ b/pkg/eval/linear/weights_test.go @@ -0,0 +1,23 @@ +package eval + +import ( + "fmt" + "testing" +) + +func TestPgn(t *testing.T) { + for _, info := range infos { + if info.Size == 0 { + continue + } + if info.Size < 32 { + fmt.Print(info.Name, " ") + for i := 0; i < info.Size; i++ { + fmt.Printf("(%v,%v)", + w[2*(info.StartIndex+i)]/100, + w[1+2*(info.StartIndex+i)]/100) + } + fmt.Println() + } + } +} diff --git a/pkg/eval/material/evaluation.go b/pkg/eval/material/evaluation.go new file mode 100644 index 0000000..9cb60a7 --- /dev/null +++ b/pkg/eval/material/evaluation.go @@ -0,0 +1,23 @@ +package eval + +import ( + "github.com/ChizhovVadim/CounterGo/pkg/common" +) + +type EvaluationService struct{} + +func NewEvaluationService() *EvaluationService { + return &EvaluationService{} +} + +func (e *EvaluationService) Evaluate(p *common.Position) int { + var eval = 100*(common.PopCount(p.Pawns&p.White)-common.PopCount(p.Pawns&p.Black)) + + 400*(common.PopCount(p.Knights&p.White)-common.PopCount(p.Knights&p.Black)) + + 400*(common.PopCount(p.Bishops&p.White)-common.PopCount(p.Bishops&p.Black)) + + 600*(common.PopCount(p.Rooks&p.White)-common.PopCount(p.Rooks&p.Black)) + + 1200*(common.PopCount(p.Queens&p.White)-common.PopCount(p.Queens&p.Black)) + if !p.WhiteMove { + eval = -eval + } + return eval +}