i have this code where i’m trying to search an LLM response for a valid move, then make that move. i made the move e4 in the game. the LLM message response was “34 +0.28 1… e5 2.Nf3 Nc6 3.Bb5…”. i wanted the program to search through the message for the first useable move, then make that move on the board, but it doesn’t seem to try making any of the moves in the LLM message. the move in this example that should have been made was e5.

import tkinter as tk
from PIL import Image, ImageTk
import chess
import requests
import re
import threading


def draw_board(board, selected_square=None):
    global board_images

    board_images = {}
    canvas.delete("all")
    canvas_width = 900
    canvas_height = 500
    canvas.config(width=canvas_width, height=canvas_height)
    start_x = 2
    start_y = 2

    for square in chess.SQUARES:
        rank = chess.square_rank(square)
        file = chess.square_file(square)
        x0 = start_x + file * square_size
        y0 = start_y + (7 - rank) * square_size
        if (rank + file) % 2 == 0:
            square_color = "white"
        else:
            square_color = "light gray"
        canvas.create_rectangle(x0, y0, x0 + square_size, y0 + square_size, fill=square_color)

        piece = board.piece_at(square)
        if piece:
            image = Image.open(PIECE_IMAGES[str(piece)]).resize((square_size, square_size), Image.Resampling.LANCZOS)
            photo = ImageTk.PhotoImage(image)
            board_images[square] = photo
            canvas.create_image(x0 + square_size // 2, y0 + square_size // 2, image=photo)

def choose_white():
    global user_color, white_button, black_button
    user_color = "white"
    history.append({"role": "user", "content": "White"})
    print_history()
    white_button.destroy()  # Remove the "Choose White" button
    black_button.destroy()  # Remove the "Choose Black" button

def choose_black():
    global user_color, white_button, black_button
    user_color = "black"
    history.append({"role": "user", "content": "Black"})
    print_history()
    white_button.destroy()  # Remove the "Choose White" button
    black_button.destroy()  # Remove the "Choose Black" button
  
def process_move(move_str, game_board):
    global san_moves, san_number, total_moves

    try:
        move = game_board.parse_san(move_str)
        if move in game_board.legal_moves:
            game_board.push(move)
            draw_board(game_board)

            if total_moves % 2 == 1:
                san_moves += f"{san_number}.{move_str} "
                total_moves += 1
            else:
                san_moves += f"{move_str} "
                total_moves += 1
                san_number += 1
            print("SAN Moves:", san_moves)
            draw_board(game_board)

    except ValueError:
        result_label.config(text="Invalid move")

def interact_with_llm(message):
    global history

    try:
        data = {
            "mode": "chat",
            "character": "Assistant",
            "instruction_template": "ChatML",
            "max_tokens": 75,
            "temperature": 1.5,
            "top_p": 0.9,
            "typical_p": 1,
            "min_p": 0,
            "repetition_penalty": 1.17,
            "top_k": 20,
            "seed": -1,            
            "messages": history
        }

        response = requests.post(api_url, headers=headers, json=data).json()

        if 'choices' in response and response['choices']:
            assistant_message = response['choices'][0]['message']['content']
            history.append({"role": "assistant", "content": assistant_message})
            chat_window.insert(tk.END, f"\nAssistant: {assistant_message}\n")
            print_history()

    except Exception as e:
        print(f"Error in LLM request: {e}")
        chat_window.insert(tk.END, "\nAssistant: Error in LLM request")

def on_enter(event):
    # Check if the cursor is in the input area
    if event.widget == input_area:
        move_str = move_entry.get()
        submit_message()
    elif event.widget == move_entry:
        submit_move() 

def submit_move():
    global move_entry
    move = move_entry.get()
    print(move)
    process_move(move, game_board)
    move_entry.delete(0, tk.END)  # Clear the entry after the move

def submit_message():
    global chat_window, user_color, history
    message = input_area.get("1.0", tk.END).strip()
    role = "user"
    chat_window.insert(tk.END, f"\n{role.capitalize()}: {message}\n")
    history.append({"role": role, "content": message})
    interact_with_llm(message)
    print("line 215")

def extract_moves_from_message(message):
    # Use a regular expression to find SAN moves like “e4”, “Nf3”, “Bb5”, and promotions like “cxd8=Q”
    san_moves = re.findall(r'\b(?:[NBRQK]?[a-h]?[1-8]?x?[a-h]1-8?|[O-]{3,5})\b', message)
    return san_moves

def llm_move():
    global san_moves, history, user_color, chat_window, san_moves

    if (game_board.turn == chess.WHITE and user_color == "black") or (game_board.turn == chess.BLACK and user_color == "white"):

        chat_window.delete(1.0, tk.END)

        history = []
        role = "assistant"

        message = f"{san_moves.rstrip()}.... 35 +0.29"

        history.append({"role": role, "content": message})

        message = (f"35 +0.29")
        history.append({"role": role, "content": message})

        try:
            data = {
                "mode": "chat",
                "character": "ChessAI",
                "max_tokens": 40,
                "temperature": 0.82,
                "top_p": 0.21,
                "min_p": 0,
                "top_k": 72,
                "repetition_penalty": 1.19,
                "typical_p": 1,
                "seed": -1,
                "messages": history
            }

            response = requests.post(api_url, headers=headers, json=data).json()

            if 'choices' in response and response['choices']:
                assistant_message = response['choices'][0]['message']['content']
                history.append({"role": "assistant", "content": assistant_message})
                chat_window.insert(tk.END, san_moves)
                chat_window.insert(tk.END, f"\n\nAssistant: {assistant_message}\n")
                print_history()

                try:
                    assistant_message = response['choices'][0]['message']['content']
                    history.append({"role": "assistant", "content": assistant_message})
                    chat_window.insert(tk.END, f"\nAssistant: {assistant_message}\n")
                    print_history()

                    # Extract moves from the assistant's message
                    moves_from_message = extract_moves_from_message(assistant_message)

                    # Process each extracted move
                    for move_str in moves_from_message:
                        try:
                            move = game_board.parse_san(move_str)
                            if move in game_board.legal_moves:
                                game_board.push(move)
                                draw_board(game_board)
                                break  # Exit the loop after making the first legal move
                        except ValueError as e:
                            result_label.config(text=f"Invalid move: {e}")
                            print(f"Invalid move: {e}")
                            

                except ValueError as e:
                    result_label.config(text=f"Invalid move: {e}")
                    print(f"Invalid move: {e}")
                    print(f"Error processing assistant_message: {e}")



        except Exception as e:
            print(f"Error in LLM request: {e}")
            chat_window.insert(tk.END, "\nAssistant: Error in LLM request")

def print_history():
    print("\n=== Updated History ===")
    for entry in history:
        print(f"{entry['role']}: {entry['content']}")
    print("=======================")

def clear_history():
    global history, chat_window, game_board, san_moves
    history = []
    chat_window.delete(1.0, tk.END)  # Clear the chat window
    game_board = chess.Board(initial_fen)
    draw_board(game_board)
    san_moves = ""

# Global Variables
user_color = None
history = []
san_moves = ""  # Initialize an empty string to store SAN moves
san_number = 1  # Initialize real number of moves
total_moves = 1

api_url = 'http://127.0.0.1:5000/v1/chat/completions'
headers = {"Content-Type": "application/json"}
initial_fen = chess.STARTING_FEN
game_board = chess.Board(initial_fen)
board = chess.Board()
valid_moves = game_board.legal_moves
board_images = {}
square_size = 60  # Size of a square in the board
selected_square = None
move = None

# Create a dictionary to map piece symbols to image filenames
PIECE_IMAGES = {
    "P": "images/Wp.png",
    "p": "images/bp.png",
    "R": "images/WR.png",
    "r": "images/bR.png",
    "N": "images/WN.png",
    "n": "images/bN.png",
    "B": "images/WB.png",
    "b": "images/bB.png",
    "Q": "images/WQ.png",
    "q": "images/bQ.png",
    "K": "images/WK.png",
    "k": "images/bK.png"
}

# Tkinter Window
window = tk.Tk()
window.title("Chess")

# Tkinter Components
canvas = tk.Canvas(window)
canvas.pack()
draw_board(game_board)

result_label = tk.Label(window)
result_label.place(x=7, y=495)

move_entry = tk.Entry(window)
move_entry.place(x=7, y=520)
move_entry.bind("<Return>", on_enter)

chat_window = tk.Text(window, height=20, width=45, wrap=tk.WORD)
chat_window.place(x=515, y=20)

input_area = tk.Text(window, height=5, width=45, wrap=tk.WORD)
input_area.place(x=515, y=400)
input_area.bind("<Return>", on_enter)

submit_btn = tk.Button(window, text="Player Move", command=submit_move)
submit_btn.place(x=185, y=518)
submit_btn = tk.Button(window, text="Send", command=submit_message)
submit_btn.pack(side=tk.RIGHT, padx=10, pady=10)

clear_history_btn = tk.Button(window, text="Clear History", command=clear_history)
clear_history_btn.place(x=300, y=518)

llm_move_btn = tk.Button(window, text="AI Turn", command=llm_move)
llm_move_btn.place(x=400, y=518)

white_button = tk.Button(window, text="Choose White", command=choose_white)
white_button.place(x=185, y=200)
black_button = tk.Button(window, text="Choose Black", command=choose_black)
black_button.place(x=185, y=240)


window.mainloop()


terminal outputs
(chess) mike@mike-GL63-9SDK:~/Valut/Vault/STEM/Coding/Python/chess$ python3 tkchess_nclk.py

=== Updated History ===
user: White
=======================
e4
SAN Moves: 1.e4 

=== Updated History ===
assistant: 1.e4.... 35 +0.29
assistant: 35 +0.29
assistant: 34 +0.28 1... e5 2.Nf3 Nc6 3.Bb5....
{{assistant}}: 33 +0.27 3.... e4 4
=======================

=== Updated History ===
assistant: 1.e4.... 35 +0.29
assistant: 35 +0.29
assistant: 34 +0.28 1... e5 2.Nf3 Nc6 3.Bb5....
{{assistant}}: 33 +0.27 3.... e4 4
assistant: 34 +0.28 1... e5 2.Nf3 Nc6 3.Bb5....
{{assistant}}: 33 +0.27 3.... e4 4
=======================