~dvko/chessrepetitions

6c1df08a798a0233b9c5cbd3edbcd206a5f06fef — Danny van Kooten 2 years ago 693e3d4
accept any move that is not an inaccuracy (-0.1 cp loss) as valid move
2 files changed, 66 insertions(+), 38 deletions(-)

M puzzles.py
M test_puzzles.py
M puzzles.py => puzzles.py +30 -32
@@ 38,23 38,13 @@ def process_game(pgn_stream, user):
        if node.next().is_end():
            break

        # app.logger.debug(f"Ply {node.ply()}: {node.move.uci()} ({eval_best.score()})")

        eval_actual = node.next().eval().pov(perspective == 'white')
        best_score = eval_best.score(mate_score=100000)
        actual_score = eval_actual.score(mate_score=100000)
        best_expectation = eval_best.wdl().expectation()

        # uncomment the following line if generating a CSV of all lines in all games
        # str += f"{game.headers['Site']}/{COLOR_NAMES[color]}#{node.ply()},{best_score},{actual_score},{best_expectation},{eval_actual.wdl().expectation()}\n"
        diff = actual_score - best_score 
        
        #print(2 / (1 + math.exp(-0.004 * diff)) - 1)

        if best_expectation > 0.001 and (2 / (1 + math.exp(-0.004 * diff)) - 1) < -0.3:
        #if best_expectation > 0.001 and (actual_score - best_score) < -100 and pct_change < -0.5:
            # Mistake or blunder detected!
            # Store as puzzle in local database
        if best_expectation > 0.001 and classify_move(actual_score, best_score) < -0.3:
            puzzle = Puzzle(
                fen=node.board().fen(),
                move_played=node.next().uci(),


@@ 69,6 59,16 @@ def process_game(pgn_stream, user):
    return local_game 


"""
Calculates a relative centipawn loss value for the move played.
- < -0.3 is a blunder
- < -0.2 is a mistake
- < -0.1 is an inaccuracy
"""
def classify_move(eval_move, eval_best_move):
    return (2 / (1 + math.exp(-0.004 * (eval_move - eval_best_move))) - 1)


@click.group()
def puzzles():
    True


@@ 106,31 106,29 @@ def for_user(username):
    run_engine()
    return game

def solve(p):
    engine = chess.engine.SimpleEngine.popen_uci("stockfish")
    limit = chess.engine.Limit(depth=16)
    board = chess.Board(p.fen)
    results = engine.analyse(board, limit, multipv=5)
    top = results[0]
    top_score = top['score'].pov(board.turn).score(mate_score=10000)
    score_treshold = top_score - (0.2 * abs(top_score))
    moves = [top['pv'][0].uci()]
    for r in results[1:]:
        move_score = r['score'].pov(board.turn).score(mate_score=10000)
        #print(r['pv'][0].uci(), move_score, classify_move(move_score, top_score))
        if (classify_move(move_score, top_score) >= -0.1):
            moves.append(r['pv'][0].uci())

    p.move_engine = ','.join(moves)
    engine.quit()
    return p

def run_engine():
    engine = chess.engine.SimpleEngine.popen_uci("stockfish")
    limit = chess.engine.Limit(depth=10)
    puzzles = Puzzle.query.filter_by(move_engine=None).all()
    click.echo(f"Found {len(puzzles)} puzzles")

    for p in puzzles:
        board = chess.Board(p.fen)
        results = engine.analyse(board, limit, multipv=5)
        top = results[0]
        top_score = top['score'].pov(board.turn).score(mate_score=10000)
        score_treshold = top_score - (0.2 * abs(top_score))
        moves = [top['pv'][0].uci()]
        for r in results[1:]:
             if r['score'].pov(board.turn).score(mate_score=10000) > score_treshold:
                    moves.append(r['pv'][0].uci())

        p.move_engine = ','.join(moves)

        # TODO: Check if engine move matches played move... (not good)

        click.echo(p.move_engine)

    engine.quit()
        solve(p)
    db.session.commit()

if __name__ == '__main__':

M test_puzzles.py => test_puzzles.py +36 -6
@@ 2,16 2,46 @@ import puzzles

def test_fetch_puzzles():
    # https://lichess.org/RsO2gx3n/white
    # Ply's we want as puzzle: 40, 46?, 60,
    # Ply's we want as puzzle: 40, 46, 58, 60
    game = puzzles.by_id("dvko", "RsO2gx3n")
    assert len(game.puzzles) == 4
    assert(game.puzzles[0].ply == 40)
    assert(game.puzzles[1].ply == 46)
    assert(game.puzzles[2].ply == 58)
    assert(game.puzzles[3].ply == 60)
    assert game.puzzles[0].ply == 40 
    assert game.puzzles[1].ply == 46 
    assert game.puzzles[2].ply == 58 
    assert game.puzzles[3].ply == 60 

    # https://lichess.org/mzoborqJ/black
    # Ply's we want as puzzle: 9
    game = puzzles.by_id("dvko", "mzoborqJ")
    assert len(game.puzzles) == 1
    assert(game.puzzles[0].ply == 9)
    assert game.puzzles[0].ply == 9 

def test_run_engine():
    # https://lichess.org/RsO2gx3n/white
    game = puzzles.by_id("dvko", "RsO2gx3n")

    # Top moves: c1c2 (uci)
    puzzles.solve(game.puzzles[0])
    assert game.puzzles[0].move_engine is not None
    assert game.puzzles[0].move_engine == "c1c2"

    # Top moves: d1b1, d1f1, h1f1
    puzzles.solve(game.puzzles[1])
    moves = game.puzzles[1].move_engine.split(',')
    assert game.puzzles[1].move_engine is not None
    assert len(moves) == 3
    assert sorted(moves) == sorted(["d1f1","d1b1","h1f1"])

    # Top moves: f1f5, d3d2
    puzzles.solve(game.puzzles[2])
    moves = game.puzzles[2].move_engine.split(',')
    assert game.puzzles[2].move_engine is not None
    assert len(moves) == 2
    assert sorted(moves) == sorted(["f1f5", "d3d2"])

    # Top moves: g5f5, f1f5
    puzzles.solve(game.puzzles[3])
    moves = game.puzzles[3].move_engine.split(',')
    assert game.puzzles[3].move_engine is not None
    assert len(moves) == 3
    assert sorted(moves) == sorted(["g5f5", "f1f5", "f1f6"])