i’m trying to add ai LLM to a python chess game. here’s the logic i’m trying to accomplish
-two main functions: chess game amd LLM chat
- Chess Game
- User or chess ai makes a chess move
- the game logic is applied
- if it was an ai chess move, add it to the history of the assistant
- if it was a user move, add it to the history of the user
- repeat this logic for gameplay loop, so the LLM is always updated with the moves in it’s history.
- LLM Chat
- User input area where a user can enter a message
- when a message is entered, it is rendered in the chat area, added to the history and sent to the LLM
- the LLM sends a response back, and the response is rendered in the text area

This is a prelim code to see how the lLM chat would work with pygame. It functions properly. i can send a message to the llm, it replies, and we can do a multi turn chat.

import requests
import pygame
import sys

# Initialize PyGame
pygame.init()

# Constants
WIDTH, HEIGHT = 800, 600
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
FONT_SIZE = 20

# Create a PyGame window
window = pygame.display.set_mode((WIDTH, HEIGHT))

# Set the window title
pygame.display.set_caption(“Chat GUI”)

# Fonts
font = pygame.font.Font(None, FONT_SIZE)

# Chat variables
user_input = “”
chat_history = []

# API Configuration
api_url = ‘http://127.0.0.1:5000/v1/chat/completions’
headers = {“Content-Type”: “application/json”}

def send_message():
global chat_history, user_input

user_message = user_input.strip()
if user_message:
# Add user message to chat history
chat_history.append({“role”: “user”, “content”: user_message})

# Display user message in the chat area
draw_text(window, f’User: {user_message}‘, BLACK, (410, 10))

# Send message to assistant
data = {
“mode”: “chat”,
“character”: “Assistant”,
“messages”: chat_history
}

try:
response = requests.post(api_url, headers=headers, json=data, verify=False)
assistant_message = response.json()[‘choices’][0][‘message’][‘content’]

chat_history.append({“role”: “assistant”, “content”: assistant_message})

# Clear user input after sending a message
user_input = “”

except requests.RequestException as e:
chat_history.append({“role”: “Error”, “content”: str(e)})


def draw_text(surface, text, color, pos):
text_surface = font.render(text, True, color)
surface.blit(text_surface, pos)

# Game loop
clock = pygame.time.Clock()
running = True

while running:
window.fill(WHITE)

for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_RETURN:
send_message()
elif event.key == pygame.K_BACKSPACE:
user_input = user_input[:-1]
else:
user_input += event.unicode

# Draw chat history
y = 10
for message in chat_history:
draw_text(window, f’{message[“role”]}: {message[“content”]}‘, BLACK, (410, y))
y += FONT_SIZE + 5

# Draw user input
draw_text(window, f’User: {user_input}’, BLACK, (410, HEIGHT - FONT_SIZE - 10))

pygame.display.update()
clock.tick(30)

pygame.quit()
sys.exit()

This is the chess python program. It runs, but when I try to make the first move, it crashes with an error. how do i fix this?

"""
Main driver file.
Handling user input.
Displaying current GameStatus object.
"""
import pygame as p
import ChessEngine, ChessAI
import sys
from multiprocessing import Process, Queue
import requests

BOARD_WIDTH = BOARD_HEIGHT = 512
MOVE_LOG_PANEL_WIDTH = 250
MOVE_LOG_PANEL_HEIGHT = BOARD_HEIGHT
CHAT_PANEL_WIDTH = 250
CHAT_PANEL_HEIGHT = BOARD_HEIGHT
DIMENSION = 8
SQUARE_SIZE = BOARD_HEIGHT // DIMENSION
MAX_FPS = 15
IMAGES = {}

user_input = ""
chat_history = []
api_url = 'http://127.0.0.1:5000/v1/chat/completions'
headers = {"Content-Type": "application/json"}


def loadImages():
    """
    Initialize a global directory of images.
    This will be called exactly once in the main.
    """
    pieces = ['wp', 'wR', 'wN', 'wB', 'wK', 'wQ', 'bp', 'bR', 'bN', 'bB', 'bK', 'bQ']
    for piece in pieces:
        IMAGES[piece] = p.transform.scale(p.image.load("images/" + piece + ".png"), (SQUARE_SIZE, SQUARE_SIZE))


def main():
    """
    The main driver for our code.
    This will handle user input and updating the graphics.
    """
    p.init()
    screen = p.display.set_mode((BOARD_WIDTH + MOVE_LOG_PANEL_WIDTH, BOARD_HEIGHT))
    clock = p.time.Clock()
    screen.fill(p.Color("white"))
    game_state = ChessEngine.GameState()
    valid_moves = game_state.getValidMoves()
    move_made = False  # flag variable for when a move is made
    animate = False  # flag variable for when we should animate a move
    loadImages()  # do this only once before while loop
    running = True
    square_selected = ()  # no square is selected initially, this will keep track of the last click of the user (tuple(row,col))
    player_clicks = []  # this will keep track of player clicks (two tuples)
    game_over = False
    ai_thinking = False
    move_undone = False
    move_finder_process = None
    move_log_font = p.font.SysFont("Arial", 14, False, False)
    player_one = True  # if a human is playing white, then this will be True, else False
    player_two = False  # if a hyman is playing white, then this will be True, else False
    global chat_history, user_input  # Declare user_input as global

    while running:
        human_turn = (game_state.white_to_move and player_one) or (not game_state.white_to_move and player_two)
        for e in p.event.get():
            if e.type == p.QUIT:
                p.quit()
                sys.exit()
            # mouse handler
            elif e.type == p.MOUSEBUTTONDOWN:
                if not game_over:
                    location = p.mouse.get_pos()  # (x, y) location of the mouse
                    col = location[0] // SQUARE_SIZE
                    row = location[1] // SQUARE_SIZE
                    if square_selected == (row, col) or col >= 8:  # user clicked the same square twice
                        square_selected = ()  # deselect
                        player_clicks = []  # clear clicks
                    else:
                        square_selected = (row, col)
                        player_clicks.append(square_selected)  # append for both 1st and 2nd click
                    if len(player_clicks) == 2 and human_turn:  # after 2nd click
                        move = ChessEngine.Move(player_clicks[0], player_clicks[1], game_state.board)
                        for i in range(len(valid_moves)):
                            if move == valid_moves[i]:
                                game_state.makeMove(valid_moves[i])
                                move_made = True
                                animate = True
                                square_selected = ()  # reset user clicks
                                player_clicks = []
                        if not move_made:
                            player_clicks = [square_selected]

            # key handler
            elif e.type == p.KEYDOWN:
                if e.key == p.K_z:  # undo when 'z' is pressed
                    game_state.undoMove()
                    move_made = True
                    animate = False
                    game_over = False
                    if ai_thinking:
                        move_finder_process.terminate()
                        ai_thinking = False
                    move_undone = True
                if e.key == p.K_r:  # reset the game when 'r' is pressed
                    game_state = ChessEngine.GameState()
                    valid_moves = game_state.getValidMoves()
                    square_selected = ()
                    player_clicks = []
                    move_made = False
                    animate = False
                    game_over = False
                    if ai_thinking:
                        move_finder_process.terminate()
                        ai_thinking = False
                    move_undone = True

        # AI move finder
        if not game_over and not human_turn and not move_undone:
            if not ai_thinking:
                ai_thinking = True
                return_queue = Queue()  # used to pass data between threads
                move_finder_process = Process(target=ChessAI.findBestMove, args=(game_state, valid_moves, return_queue))
                move_finder_process.start()

            if not move_finder_process.is_alive():
                ai_move = return_queue.get()
                if ai_move is None:
                    ai_move = ChessAI.findRandomMove(valid_moves)
                game_state.makeMove(ai_move)
                move_made = True
                animate = True
                ai_thinking = False

        if move_made:
            if animate:
                animateMove(game_state.move_log[-1], screen, game_state.board, clock)
            valid_moves = game_state.getValidMoves()
            move_made = False
            animate = False
            move_undone = False

        drawGameState(screen, game_state, valid_moves, square_selected)
        if game_state.move_log:
            # Send the move to LLM and receive AI's response
            user_move = str(game_state.move_log[-1])
            ai_response = send_move_to_llm(user_move)

            # Update chat history with player move and AI response
            chat_history.append(("user", user_move))
            chat_history.append(("Assistant", ai_response))


        if not game_over:
            drawMoveLog(screen, game_state, chat_history, user_input, move_log_font)

        if game_state.checkmate:
            game_over = True
            if game_state.white_to_move:
                drawEndGameText(screen, "Black wins by checkmate")
            else:
                drawEndGameText(screen, "White wins by checkmate")

        elif game_state.stalemate:
            game_over = True
            drawEndGameText(screen, "Stalemate")

        for event in p.event.get():
            if event.type == p.KEYDOWN:
                if event.key == p.K_RETURN:
                    send_message()
                elif event.key == p.K_BACKSPACE:
                    user_input = user_input[:-1]
                else:
                    user_input += event.unicode

        clock.tick(MAX_FPS)
        p.display.flip()

def send_message():
    global chat_history, user_input

    user_message = user_input.strip()
    if user_message:
        # Add user message to chat history
        chat_history.append({"role": "user", "content": user_message})

        # Display user message in the chat area
        draw_text(window, f'User: {user_message}', BLACK, (410, 10))

        # Send message to assistant
        data = {
            "mode": "chat",
            "character": "Assistant",
            "messages": chat_history
        }

        try:
            response = requests.post(api_url, headers=headers, json=data, verify=False)
            assistant_message = response.json()['choices'][0]['message']['content']

            chat_history.append({"role": "assistant", "content": assistant_message})

            # Clear user input after sending a message
            user_input = ""

        except requests.RequestException as e:
            chat_history.append({"role": "Error", "content": str(e)})

def send_move_to_llm(move):
    global chat_history, user_input  # Declare user_input as global
    # Construct the data to send to LLM
    data = {
            "mode": "chat",
            "character": "Assistant",
            "messages": chat_history
    }
    try:
        response = requests.post(api_url, headers=headers, json=data, verify=False)
        assistant_message = response.json()['choices'][0]['message']['content']
        chat_history.append({"role": "Assistant", "content": assistant_message})

        # Clear user input after sending a message
        user_input = ""

        ai_reply = response.json()["choices"][0]["text"]
        return ai_reply
    except requests.RequestException as e:
        chat_history.append(("Error", str(e)))   


def drawGameState(screen, game_state, valid_moves, square_selected):
    """
    Responsible for all the graphics within current game state.
    """
    drawBoard(screen)  # draw squares on the board
    highlightSquares(screen, game_state, valid_moves, square_selected)
    drawPieces(screen, game_state.board)  # draw pieces on top of those squares


def drawBoard(screen):
    """
    Draw the squares on the board.
    The top left square is always light.
    """
    global colors
    colors = [p.Color("white"), p.Color("gray")]
    for row in range(DIMENSION):
        for column in range(DIMENSION):
            color = colors[((row + column) % 2)]
            p.draw.rect(screen, color, p.Rect(column * SQUARE_SIZE, row * SQUARE_SIZE, SQUARE_SIZE, SQUARE_SIZE))


def highlightSquares(screen, game_state, valid_moves, square_selected):
    """
    Highlight square selected and moves for piece selected.
    """
    if (len(game_state.move_log)) > 0:
        last_move = game_state.move_log[-1]
        s = p.Surface((SQUARE_SIZE, SQUARE_SIZE))
        s.set_alpha(100)
        s.fill(p.Color('green'))
        screen.blit(s, (last_move.end_col * SQUARE_SIZE, last_move.end_row * SQUARE_SIZE))
    if square_selected != ():
        row, col = square_selected
        if game_state.board[row][col][0] == (
                'w' if game_state.white_to_move else 'b'):  # square_selected is a piece that can be moved
            # highlight selected square
            s = p.Surface((SQUARE_SIZE, SQUARE_SIZE))
            s.set_alpha(100)  # transparency value 0 -> transparent, 255 -> opaque
            s.fill(p.Color('blue'))
            screen.blit(s, (col * SQUARE_SIZE, row * SQUARE_SIZE))
            # highlight moves from that square
            s.fill(p.Color('yellow'))
            for move in valid_moves:
                if move.start_row == row and move.start_col == col:
                    screen.blit(s, (move.end_col * SQUARE_SIZE, move.end_row * SQUARE_SIZE))


def drawPieces(screen, board):
    """
    Draw the pieces on the board using the current game_state.board
    """
    for row in range(DIMENSION):
        for column in range(DIMENSION):
            piece = board[row][column]
            if piece != "--":
                screen.blit(IMAGES[piece], p.Rect(column * SQUARE_SIZE, row * SQUARE_SIZE, SQUARE_SIZE, SQUARE_SIZE))

def draw_text(surface, text, color, pos, font):
    text_surface = font.render(text, True, color)
    surface.blit(text_surface, pos)


def drawChat(screen, chat_history, user_input, font):
    """
    Draws the chat panel with chat history and user input.
    """
    chat_panel_rect = p.Rect(BOARD_WIDTH, 0, CHAT_PANEL_WIDTH, CHAT_PANEL_HEIGHT)
    p.draw.rect(screen, p.Color('black'), chat_panel_rect)

    # Draw chat history
    text_y = 5
    for message in chat_history:
        text = f'{message["role"]}: {message["content"]}'
        text_object = font.render(text, True, p.Color('white'))
        text_location = chat_panel_rect.move(5, text_y)
        screen.blit(text_object, text_location)
        text_y += text_object.get_height() + 2

    # Draw user input
    draw_text(screen, f'User: {user_input}', p.Color('white'), (BOARD_WIDTH + 5, CHAT_PANEL_HEIGHT - 25), font)

# the chat panel
def drawMoveLog(screen, game_state, chat_history, user_input, font):
    """
    Draws the move log and chat panel.
    """
    move_log_rect = p.Rect(BOARD_WIDTH, 0, MOVE_LOG_PANEL_WIDTH, MOVE_LOG_PANEL_HEIGHT)
    p.draw.rect(screen, p.Color('black'), move_log_rect)
    
    # Draw move log
    move_log = game_state.move_log
    move_texts = []
    for i in range(0, len(move_log), 2):
        move_string = str(i // 2 + 1) + '. ' + str(move_log[i]) + " "
        if i + 1 < len(move_log):
            move_string += str(move_log[i + 1]) + "  "
        move_texts.append(move_string)

    moves_per_row = 3
    padding = 5
    line_spacing = 2
    text_y = padding
    for i in range(0, len(move_texts), moves_per_row):
        text = ""
        for j in range(moves_per_row):
            if i + j < len(move_texts):
                text += move_texts[i + j]

        text_object = font.render(text, True, p.Color('white'))
        text_location = move_log_rect.move(padding, text_y)
        screen.blit(text_object, text_location)
        text_y += text_object.get_height() + line_spacing

    # Draw user input
    draw_text(screen, f'User: {user_input}', p.Color('white'), (BOARD_WIDTH + 5, CHAT_PANEL_HEIGHT - 25), font)
    drawChat(screen, chat_history, user_input, font)

def drawEndGameText(screen, text):
    font = p.font.SysFont("Helvetica", 32, True, False)
    text_object = font.render(text, False, p.Color("gray"))
    text_location = p.Rect(0, 0, BOARD_WIDTH, BOARD_HEIGHT).move(BOARD_WIDTH / 2 - text_object.get_width() / 2,
                                                                 BOARD_HEIGHT / 2 - text_object.get_height() / 2)
    screen.blit(text_object, text_location)
    text_object = font.render(text, False, p.Color('black'))
    screen.blit(text_object, text_location.move(2, 2))


def animateMove(move, screen, board, clock):
    """
    Animating a move
    """
    global colors
    d_row = move.end_row - move.start_row
    d_col = move.end_col - move.start_col
    frames_per_square = 10  # frames to move one square
    frame_count = (abs(d_row) + abs(d_col)) * frames_per_square
    for frame in range(frame_count + 1):
        row, col = (move.start_row + d_row * frame / frame_count, move.start_col + d_col * frame / frame_count)
        drawBoard(screen)
        drawPieces(screen, board)
        # erase the piece moved from its ending square
        color = colors[(move.end_row + move.end_col) % 2]
        end_square = p.Rect(move.end_col * SQUARE_SIZE, move.end_row * SQUARE_SIZE, SQUARE_SIZE, SQUARE_SIZE)
        p.draw.rect(screen, color, end_square)
        # draw captured piece onto rectangle
        if move.piece_captured != '--':
            if move.is_enpassant_move:
                enpassant_row = move.end_row + 1 if move.piece_captured[0] == 'b' else move.end_row - 1
                end_square = p.Rect(move.end_col * SQUARE_SIZE, enpassant_row * SQUARE_SIZE, SQUARE_SIZE, SQUARE_SIZE)
            screen.blit(IMAGES[move.piece_captured], end_square)
        # draw moving piece
        screen.blit(IMAGES[move.piece_moved], p.Rect(col * SQUARE_SIZE, row * SQUARE_SIZE, SQUARE_SIZE, SQUARE_SIZE))
        p.display.flip()
        clock.tick(60)


if __name__ == "__main__":
    main()


this the the error that is occuring

/chess/chess-engine/chess/ChessMain.py", line 226, in send_move_to_llm
    ai_reply = response.json()["choices"][0]["text"]
KeyError: 'text'