Skip to content

Commit

Permalink
Chessboard analitycs (#38)
Browse files Browse the repository at this point in the history
* feat(ai): chessboard analysis
  • Loading branch information
Magulak authored May 27, 2024
1 parent 6484d1c commit 6ce91f4
Show file tree
Hide file tree
Showing 5 changed files with 328 additions and 0 deletions.
Binary file added ai/X_mean.npy
Binary file not shown.
Binary file added ai/X_std.npy
Binary file not shown.
223 changes: 223 additions & 0 deletions ai/ai.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,235 @@
import chess
import numpy as np
import pandas as pd

# import tensorflow as tf

PATH_TO_STOCKFISH_DATA = r'D:\ML WSB Chess\ML-Chess\fen_to_stockfish_evaluation.csv'
READY_DATASET = r'D:\ML WSB Chess\ML-Chess\datasets\dataset.csv'

piece_to_vector = {
'P': [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
'N': [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
'B': [0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
'R': [0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
'Q': [0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0],
'K': [0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0],
'p': [0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
'n': [0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0],
'b': [0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
'r': [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0],
'q': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
'k': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
' ': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
}
fen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR "
fen_for_testing = ["rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR ", "8/8/8/4k3/3R4/8/8/4K3",
"rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPP1PPP/RNBQKBNR",
"r3k2r/ppp1qppp/2n5/8/2b1B3/5Q2/PPP1NPPP/R3K2R"]


def fen_to_onehot(fen):
print(f"FEN TO BE ONEHOTED:{fen}")

# Initialize an empty 8x8x12 array
board = np.zeros((8, 8, 12))

# Parse the FEN string
rows = fen.split('/')

# Iterate over the rows
for i in range(min(8, len(rows))): # Only iterate over the first 8 rows
row = rows[i]

for j, char in enumerate(row):
# If its a digit its empty
if char.isdigit():
# Go to empty squares
j += int(char) - 1
else:
if j < 8:
# Get the one-hot vector for the piece
piece_vector = piece_to_vector[char]
# Set the corresponding values in the one-hot array
board[i, j, :] = piece_vector
# print(board)
return board


def csv_stockfish_into_input_data(csv_stockfish_path, dataset_path, additional_features_enabled):

if additional_features_enabled:
pass
i = 0
temp_str = ''
stockfish_eval = ['']
with open(csv_stockfish_path, 'r') as file:
for line in file:
temp_str = line.strip() + ' ' # clean useless symbols
temp_str = temp_str.split(" ") # split fen string from additional castling/pawn move data.

temp_str, color, castle, stockfish_eval[0] = fen_to_onehot(temp_str[0]), temp_str[1], temp_str[2], temp_str[6]
stockfish_eval = float(stockfish_eval[0])



fen_in_oned_matrix = fen_matrix_into_one_row(temp_str)
x = fen_in_oned_matrix
x = x.astype('float32')
# print(type(x[0]))
# print(x[0])
# print(len(x))

# Change into numpy array so it can be concatenated
stockfish_eval = np.array([stockfish_eval])

# Add a new axis to stockfish_eval to make it a 2D array
stockfish_eval = stockfish_eval[:, np.newaxis]

# Convert fen_in_oned_matrix to a 2D array
fen_in_oned_matrix = fen_in_oned_matrix[np.newaxis, :]

# Concatenate stockfish_eval with fen_in_oned_matrix
x = np.concatenate((fen_in_oned_matrix, stockfish_eval), axis=1)
x = x.reshape(-1)

print(x)
print(f"Flat.len:{len(x)}")
print(type(x))


# TODO dodaj info, o tym czyja teraz kolej b or w, ale najpierw niech dziala w podstawowym stanie

with open(dataset_path, 'a') as output:
# print(len(fen_in_oned_matrix))
np.savetxt(output, [x], fmt='%f', delimiter=' ')





def fen_matrix_into_one_row(matrix):
flattened_matrix = matrix.flatten()
return flattened_matrix


def fen_matrix_into_one_row2(matrix):
# I am just assigning some random initial values
onerow_from_matrix = [99]
matrix_converted_into_one_line = [99]
indexx = 0

# Access each letter in matrix(and a matrix that is inside a matrix)
for bigmatrix in matrix:
indexx = indexx + 1
true1d = []
for smallmatrix in bigmatrix:
for letter in smallmatrix:
if onerow_from_matrix[0] == 99: # If this is first iteration then overwrite the first letter
onerow_from_matrix[0] = int(letter)
onerow_from_matrix.append(int(letter))
if matrix_converted_into_one_line[0] == 99: # If this is first iteration then overwrite the first letter
matrix_converted_into_one_line[0] = onerow_from_matrix

else: # Otherwise append this to a list that will be returned
matrix_converted_into_one_line.append(onerow_from_matrix.copy())
for lists in matrix_converted_into_one_line:
true1d = true1d + lists
return true1d


# for fenn in fen_for_testing:
# print(f"{fen_matrix_into_one_row(fen_to_onehot(fenn))}\n")


class AI:
def __init__(self) -> None:
# TODO: load model while creating the app
for x in self.add_attack_info(fen):
print(x, "\n")

pass

def move(self, board: str) -> chess.Move:
print(board)
# TODO: based on board representation, predict next move and return it
raise Exception("unimplemented")

def add_attack_info(self, fen_str):
# Convert the FEN string to a one-hot encoded array
board = fen_to_onehot(fen_str)

# Initialize an empty 8x8x12 array for the attack information
attack_info = np.zeros((8, 8, 12))

# Define the possible moves for each piece type
pawn_moves = [(1, 1), (1, -1)]
knight_moves = [(2, 1), (2, -1), (1, 2), (1, -2), (-1, 2), (-1, -2), (-2, 1), (-2, -1)]
king_moves = [(1, 0), (0, 1), (-1, 0), (0, -1), (1, 1), (1, -1), (-1, 1), (-1, -1)]
rook_moves = [(0, i) for i in range(-8, 8)] + [(i, 0) for i in range(-8, 8)]
bishop_moves = [(i, i) for i in range(-8, 8)] + [(i, -i) for i in range(-8, 8)]
queen_moves = rook_moves + bishop_moves

# Iterate over all squares on the board
for row in range(8):
for col in range(8):
# Get the one-hot vector for the current square
piece = board[row, col, :]

# Check if the square is occupied by a piece of the current player
if np.sum(piece[:6]) == 1:
# Iterate over all possible moves for the piece type
for move in (pawn_moves if np.sum(piece[:1]) == 1 else
knight_moves if np.sum(piece[1:2]) == 1 else
king_moves if np.sum(piece[4:5]) == 1 else
rook_moves if np.sum(piece[2:3]) == 1 else
bishop_moves if np.sum(piece[3:4]) == 1 else
queen_moves):

# Calculate the target square coordinates
new_row, new_col = row + move[0], col + move[1]

# Check if the target square is within the board bounds
if 0 <= new_row < 8 and 0 <= new_col < 8:
# Check if the target square is occupied by an enemy piece
enemy_piece = board[new_row, new_col, 6:]
if np.sum(enemy_piece) == 1:
# Set the corresponding value in the attack array
attack_info[new_row, new_col, :6] = piece[:6]

# Concatenate the original board and the attack array along the last dimension
new_board = np.concatenate((board, attack_info), axis=2)

return new_board

csv_stockfish_into_input_data(PATH_TO_STOCKFISH_DATA, READY_DATASET, True)







def csv_stockfish_into_input_data2(csv_stockfish_path, dataset_path):
i = 0
temp_str = ''
with open(csv_stockfish_path, 'r') as file:
for line in file:
temp_str = line.strip() + ' ' # clean useless symbols
temp_str = temp_str.split(" ") # split fen string from additional castling/pawn move data.

temp_str, color, castle, stockfish_eval = fen_to_onehot(temp_str[0]), temp_str[1], temp_str[2], temp_str[6]
stockfish_eval = float(stockfish_eval)

fen_in_oned_matrix = fen_matrix_into_one_row(temp_str)

if color == 'w':
color_info = 0
else:
color_info = 1
fen_in_oned_matrix = np.append(fen_in_oned_matrix, color_info)
fen_in_oned_matrix = np.append(fen_in_oned_matrix, stockfish_eval)

with open(dataset_path, 'a') as output:
# print(len(fen_in_oned_matrix))
np.savetxt(output, [fen_in_oned_matrix], fmt='%f', delimiter=' ')
81 changes: 81 additions & 0 deletions ai/model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@

import numpy as np
import tensorflow as tf
import keras.models
# print("Num GPUs Available: ", len(tf.config.list_physical_devices('GPU')))


chunksize = 1000
X, y = [], []

#
# conda create --prefix D:\ML WSB Chess\ML-Chess\ai\conda python=3.10
# Open the CSV file
with open('D:\\ML WSB Chess\\ML-Chess\\datasets\\dataset.csv', 'r') as f:
# Loop through each chunk of data
for i in range(0, int(13e9 / chunksize)):
# Read a chunk of data from the CSV file
data = np.loadtxt(f, dtype=np.float64, delimiter=' ', max_rows=chunksize)

if data.size == 0:
continue

# Extract input features and output label
X_chunk = data[:, :-1]
y_chunk = data[:, -1]

# Normalize the input data to have mean 0 and standard deviation 1
X_mean = X_chunk.mean(axis=0, keepdims=True)
X_std = X_chunk.std(axis=0, keepdims=True)
X_std[X_std == 0] = 1
X_chunk = (X_chunk - X_mean) / X_std

X_std = X_chunk.std(axis=0, keepdims=True) + 1e-8

# Normalize the reward values to have mean 0 and standard deviation 1
y_mean = y_chunk.mean()
y_std = y_chunk.std()
y_chunk = (y_chunk - y_mean) / y_std

# Append input features and output label to lists
X.append(X_chunk)
y.extend(y_chunk)

# Concatenate the list of input features into a single array
X = np.concatenate(X)

# Add a small constant to avoid division by zero
X += 1e-8

# Reshape the input data to match the expected input shape of the model
X = X.reshape((-1, 768, 1))


# Define the architecture of the CNN model
model = tf.keras.Sequential()
model.add(tf.keras.layers.Reshape((768, 1), input_shape=(1, 768)))
model.add(tf.keras.layers.Conv1D(32, 3, activation='relu'))
model.add(tf.keras.layers.MaxPooling1D(2))
model.add(tf.keras.layers.Conv1D(64, 3, activation='relu'))
model.add(tf.keras.layers.MaxPooling1D(2))
model.add(tf.keras.layers.Flatten())
model.add(tf.keras.layers.Dense(64, activation='relu'))
model.add(tf.keras.layers.Dense(1))

# Compile the model
model.compile(optimizer='adam', loss='mean_squared_error')

# Print the shapes of X and y
for i, (x, y_val) in enumerate(zip(X, y)):
print(f"Shape of X[{i}]: {np.shape(x)}")
print(f"Shape of y[{i}]: {np.shape(y_val)}")

if len(X) > 0:
model.fit(X, y, epochs=10, batch_size=32)
else:
print("Error: X is an empty array")

# Save the model to a file
tf.saved_model.save(model, 'first_model_on_13gb_database')
np.save('X_mean.npy', X_mean)
np.save('X_std.npy', X_std)
24 changes: 24 additions & 0 deletions ai/use_model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import numpy as np
import tensorflow as tf

# Wczytaj dane z załączonych plików
X_MEAN,X_STD = np.load('X_mean.npy'),np.load('X_std.npy')

# Wczytaj oraz znormalizuj dane
input_data = "tutaj wkładamy Jednowymiarową tablicę z 768 wartościami ( Mam algorytm do przerabiania fen na taką spłaszczoną tablicę)"
input_data = (input_data - X_MEAN) / X_STD

# Jak mam dane równe 0 to tensorflow narzeka na dzielenie przez 0, więc dodajemy wszędzie malutką wartosc,
input_data += 1e-8

# Przekształć dane aby pasowały pod model 10 - ilość 'batchów danych' 768 - ilość wartości w podanej zmiennej
# ilość wymiarów naszej listy
input_data = input_data.reshape((10, 768, 1))

model = tf.keras.models.load_model('ML_chessCNN1.h5')

# Przewiduj to jak dobry jest ten ruch.. Im wyższa wartość tym lepiej
predictions = model.predict(input_data)
print(predictions)


0 comments on commit 6ce91f4

Please sign in to comment.