-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat(ai): chessboard analysis
- Loading branch information
Showing
5 changed files
with
328 additions
and
0 deletions.
There are no files selected for viewing
Binary file not shown.
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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=' ') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
|
||
|