diff options
| author | tslil clingman <tslil@posteo.de> | 2023-01-15 16:03:37 +0100 |
|---|---|---|
| committer | tslil <tslil@posteo.de> | 2026-08-28 19:37:41 +0100 |
| commit | ee216c008a188a9436fedb85c70ee5d1719733b1 (patch) | |
| tree | f1d8fa5efd71851dd4f8d3e2b26b1f95a6086cb9 /src | |
| parent | 7cf3a656d0c923dd92025c09747461f2f2d1bed0 (diff) | |
new neural network arch (faster + better) & minor changes + fixes
Gone is the convolutional neural network, for it turns out not only is
it more difficult to train, but all of the extra information about
board layers didn't make much of a difference at this size.
So cnn1986 has been replaced by nn1986, a standard, two-layer, dense
nn configured as a binary classifier and (mis)used in that capacity.
Note: total number of parameters is unchanged.
HARK: this new nn exposes a bug somewhere in ctak. Run ctlm with
self-play to see the completely borked board state at the end.
Diffstat (limited to 'src')
| -rw-r--r-- | src/cnn_train.py | 156 | ||||
| -rw-r--r-- | src/ctlm.c | 54 | ||||
| -rw-r--r-- | src/cttei.c | 26 | ||||
| -rw-r--r-- | src/nn_train.py | 106 | ||||
| -rw-r--r-- | src/pptdb.c | 361 |
5 files changed, 323 insertions, 380 deletions
diff --git a/src/cnn_train.py b/src/cnn_train.py deleted file mode 100644 index 4ba0a0b..0000000 --- a/src/cnn_train.py +++ /dev/null @@ -1,156 +0,0 @@ -# cnn_train.py, train a small CNN to recognise winning Tak positions -# -# Copyright (C) 2021, tslil clingman -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <https://www.gnu.org/licenses/>. - - -# Loading data -import pandas as pd -import numpy as np - -# Output of weights -from tensorflow import transpose - -# Custom activation function -from tensorflow import constant as k -from tensorflow import clip_by_value -from tensorflow.math import add, divide, multiply, square - -# Computing size -from tensorflow.keras.backend import get_value - -# Building models -from tensorflow.keras.models import Model -from tensorflow.keras.layers import Concatenate -from tensorflow.keras.layers import Input -from tensorflow.keras.layers import Dense -from tensorflow.keras.layers import Convolution2D -from tensorflow.keras.layers import Flatten - - -def load_data(size): - shape = (-1, size, size, 6) - # Load training data - tr_fn = "training-"+str(size)+".csv" - training_csv = pd.read_csv(tr_fn) - training_data = training_csv - training_stack_input = np.array(training_data.iloc[:, 2:-1]).reshape(shape, order='F') - training_flats_input = np.array(training_data.iloc[:, 0:2]) - training_input = [training_stack_input, training_flats_input] - training_outcome = training_data.iloc[:, -1:] - # Load validation data - val_fn = "validation-"+str(size)+".csv" - val_data = pd.read_csv(val_fn).tail(20000) - val_stack_input = np.array(val_data.iloc[:, 2:-1]).reshape(shape, order='F') - val_flats_input = np.array(val_data.iloc[:, 0:2]) - val_input = [val_stack_input, val_flats_input] - val_outcome = val_data.iloc[:, -1:] - return [(training_input, training_outcome), (val_input, val_outcome)] - - -# We need something that's close to 2*logistic-1, but cheaper to -# compute: (12+x+50*x/(x*x+10))/12-1, clipped between -1 and 1 as it -# would otherwise exceed this range at +- 4.6 or so -def clipped_pade_logistic(x): - val = add(k(12.0), - add(x, multiply(k(50.0), - divide(x, add(square(x), k(10.0)))))) - return clip_by_value(add(k(-1.0), divide(val, k(12.0))), -1.0, +1.0) - - -def train(size, model, data, iterations=1, epochs=10): - (training_input, training_outcome), (val_input, val_outcome) = data - results = [] - for i in range(0, iterations): - print("Iteration {0}/{1}".format(i+1, iterations)) - model.fit(training_input, training_outcome, epochs=epochs, - validation_data=(val_input, val_outcome), - verbose=True, batch_size=16) - v_loss = model.evaluate(val_input, val_outcome, - verbose=False, batch_size=16) - t_loss = model.evaluate(training_input, training_outcome, - verbose=False, batch_size=32) - results += [(v_loss, t_loss)] - write_weights(model, i+1, str((v_loss, t_loss))) - print("\nScores") - for i in range(len(results)): - print("Iteration {0}: {1}".format(i+1, results[i])) - return results - -def make_model(size, magic=[16, 128, 64, 64, 64]): - # Our model for the stacks, a small CNN - stack_shape = (size, size, 6) - stack_input = Input(shape=stack_shape) - stack_model = Convolution2D(magic[0], kernel_size=(3, 3), strides=(1, 1), - padding='valid', activation="relu", - use_bias=True)(stack_input) - stack_model = Flatten()(stack_model) - stack_model = Model(inputs=stack_input, outputs=stack_model) - # The overall model - flats_input = Input(shape=(2,)) - combn_input = Concatenate()([stack_model.output, flats_input]) - model = Dense(magic[1], activation="relu", use_bias=True)(combn_input) - model = Dense(magic[2], activation="relu", use_bias=True)(model) - model = Dense(1, activation=clipped_pade_logistic, use_bias=True)(model) - model = Model(inputs=[stack_model.input, flats_input], outputs=model) - model.compile(optimizer='adam', loss='mean_squared_error') - model.summary() - return model - - -def write_weights(model, iteration, performance): - def fix(val): - string = str(np.array(val).tolist()) - string = string.replace("[", "{").replace("]", "}") - return string - # Prepare everything in a sane memory order This isn't exactly in - # the correct order that tensorflow uses, because memory access - # out of order is an eyesore. Compared to tensorflow, the C - # implementation has the board reflected about the diagonal. - conv2d_weights = transpose(model.trainable_variables[0], perm=[3, 0, 1, 2]) - conv2d_biases = model.trainable_variables[1] - dense1_weights = transpose(model.trainable_variables[2], perm=[1, 0]) - dense1_biases = model.trainable_variables[3] - dense2_weights = transpose(model.trainable_variables[4], perm=[1, 0]) - dense2_biases = model.trainable_variables[5] - output_weights = transpose(model.trainable_variables[6], perm=[1, 0])[0] - output_bias = model.trainable_variables[7][0] - # Prepare formatting - names = ["conv2d_weights[KERN_NUM][KERN_SIZE][KERN_SIZE][KERN_CHAN]", - "conv2d_biases[KERN_NUM]", - "dense1_weights[DENSE1_NUM][CONV_NUM+2]", - "dense1_biases[DENSE1_NUM]", - "dense2_weights[DENSE2_NUM][DENSE1_NUM]", - "dense2_biases[DENSE2_NUM]", - "output_weights[DENSE2_NUM]", - "output_bias"] - variables = [conv2d_weights, conv2d_biases, - dense1_weights, dense1_biases, - dense2_weights, dense2_biases, - output_weights, output_bias] - # Write to file - f = open("weights-"+str(iteration)+".txt", "w") - f.write("/*\n") - model.summary(print_fn=lambda l: f.write(" * "+l+"\n")) - f.write(" * "+performance+"\n*/\n\n") - f.write("#include \"weights.h\"\n\n") - for (name, val) in zip(names, variables): - f.write("const float "+name+" =\n"+fix(val)+";\n\n") - f.close() - - -data = load_data(5) -model = make_model(5, [12, 11, 8]) -results = train(5, model, data, iterations=20, epochs=10) @@ -235,41 +235,41 @@ static int handle_turn(char *line) { case GAME_END: { // Did it end this turn? if (new_win) { - switch (won) { - case WIN_DRAW: { - end_game(line, "1/2-1/2"); - break; - } - case WIN_FLAT_BLACK: { - end_game(line, "0-F"); - break; - } - case WIN_FLAT_WHITE: { - end_game(line, "F-0"); - break; - } - case WIN_ROAD_BLACK: { - end_game(line, "0-R"); - break; - } - case WIN_ROAD_WHITE: { - end_game(line, "R-0"); - break; - } - } + switch (won) { + case WIN_DRAW: { + end_game(line, "1/2-1/2"); + break; + } + case WIN_FLAT_BLACK: { + end_game(line, "0-F"); + break; + } + case WIN_FLAT_WHITE: { + end_game(line, "F-0"); + break; + } + case WIN_ROAD_BLACK: { + end_game(line, "0-R"); + break; + } + case WIN_ROAD_WHITE: { + end_game(line, "R-0"); + break; + } + } } puts("Enter `new' to play again."); if (!new_win) - return EXIT_FAILURE; + return EXIT_FAILURE; break; } // Valid, append to game log case ACT_OK: { append_to_gamelog(line, 0); if (auto_board) - print_board(); + print_board(); if (auto_info) - print_info(); + print_info(); break; } } @@ -383,7 +383,7 @@ static int negamax_turn(void) { static int input_is_not_turn(const char *line) { if (!strcmp(line, "help")) { - puts("Valid commands: auto (board|info), board, depth [0-9], eval,\ + puts("Valid commands: auto (board|info), board, depth [0-9], eval, \ help, info, load <file.ptn>, log, new, play (b|w), self-play, square\ <col><row>, tps, <PTN>."); } else if (!strcmp(line, "board")) { @@ -391,7 +391,7 @@ help, info, load <file.ptn>, log, new, play (b|w), self-play, square\ } else if (!strcmp(line, "info")) { print_info(); } else if (!strcmp(line, "eval")) { - float eval = cnn1986_evaluate_black_win() * 100; + float eval = nn1986_evaluate_black_win() * 100; if (ply & 1) { printf("Black heuristic chance: %s%.2f%s\n", blk, eval, rst); } else { diff --git a/src/cttei.c b/src/cttei.c index aae1045..534faef 100644 --- a/src/cttei.c +++ b/src/cttei.c @@ -28,8 +28,8 @@ // Set up output function for negamax inline void negamax_display_progress(const uint8_t cur_depth, - const uint8_t init_depth, - const uint32_t length) { + const uint8_t init_depth, + const uint32_t length) { (void)(cur_depth); (void)(init_depth); (void)(length); @@ -81,7 +81,7 @@ handle_tei(char *line) { enum ACT_RESULT r = do_ptn(negamax_ptn); if (r != ACT_OK && r != GAME_END) return TEI_FAILURE; printf("info score cp %f pv %s\nbestmove %s\n", - minimax, negamax_ptn, negamax_ptn); + minimax, negamax_ptn, negamax_ptn); } else if (!strncmp(line, "position", 8)) { return parse_position_string(line + 9); } else if (!strncmp(line, "teinewgame", 10)) { @@ -126,7 +126,7 @@ int main(int argc, char **argv) { line = NULL; // Identify ourselves, and send the options - puts("id name cttei"); + puts("id name cttei_dense"); puts("id author tslil clingman"); puts("option name Depth type spin default 4 min 2 max 6"); puts("teiok"); @@ -141,15 +141,15 @@ int main(int argc, char **argv) { if ((read = getline(&line, &alloc_size, stdin)) > 0) { line[read-1] = 0; switch (handle_tei(line)) { - case TEI_FAILURE: return EXIT_FAILURE; - case TEI_QUIT: playing = 0; // fall-through - case TEI_OK: { - if (line) { - free(line); - line = NULL; - } - break; - } + case TEI_FAILURE: return EXIT_FAILURE; + case TEI_QUIT: playing = 0; // fall-through + case TEI_OK: { + if (line) { + free(line); + line = NULL; + } + break; + } } } else { break; diff --git a/src/nn_train.py b/src/nn_train.py new file mode 100644 index 0000000..12ce868 --- /dev/null +++ b/src/nn_train.py @@ -0,0 +1,106 @@ +# cnn_train.py, train a small CNN to recognise winning Tak positions +# +# Copyright (C) 2021, tslil clingman +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + + +# Loading data +import pandas as pd +import numpy as np + +# Output of weights +from tensorflow import transpose + +# Building models +from tensorflow.keras.models import Model +from tensorflow.keras.layers import Input, Dense + + +def load_data(size): + output_len = 2 + # Load training data + tr_fn = "data/training-"+str(size)+".csv" + training_csv = pd.read_csv(tr_fn) + training_data = training_csv + training_input = np.array(training_data.iloc[:, 0:-output_len]) + training_outcome = np.array(training_data.iloc[:, -output_len:]) + # Load validation data + val_fn = "data/validation-"+str(size)+".csv" + val_data = pd.read_csv(val_fn) + val_input = np.array(val_data.iloc[:, 0:-output_len]) + val_outcome = np.array(val_data.iloc[:, -output_len:]) + return ((training_input, training_outcome), (val_input, val_outcome)) + + +def train(size, model, data, iterations=1, epochs=10, batch=None): + (tra_input, tra_outcome), (val_input, val_outcome) = data + results = [] + for i in range(0, iterations): + print("Iteration {0}/{1}".format(i+1, iterations)) + model.fit(tra_input, tra_outcome, epochs=epochs, + validation_data=(val_input, val_outcome), + verbose=True, batch_size=batch) + val_res = model.evaluate(val_input, val_outcome, verbose=False) + tra_res = model.evaluate(tra_input, tra_outcome, verbose=False) + results.append((tra_res, val_res)) + print(val_res) + write_weights(model, i+1, (tra_res, val_res)) + print("\nScores") + for i, data in enumerate(results): + print(f"Iteration {i}: {data}") + return results + + +def make_model(size, magic): + inputs = Input(shape=(size * size + 3,)) + model = inputs + model = Dense(magic, activation="relu", use_bias=True)(model) + model = Dense(2, activation="relu", use_bias=True)(model) + model = Model(inputs=inputs, outputs=model) + model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy']) + model.summary() + return model + + +def write_weights(model, iteration, performance): + def fix(val): + string = str(np.array(val).tolist()) + string = string.replace("[", "{").replace("]", "}") + return string + dense1_weights = transpose(model.trainable_variables[0], perm=[1, 0]) + dense1_biases = model.trainable_variables[1] + output_weights = transpose(model.trainable_variables[2], perm=[1, 0]) + output_bias = model.trainable_variables[3] + # Prepare output + to_output = [("dense1_weights[DENSE_NUM][INP_NUM]",dense1_weights) + ("dense1_biases[DENSE_NUM]", dense1_biases), + ("output_weights[2][DENSE_NUM]", output_weights), + ("output_bias[2]", output_bias)] + # Write to file + f = open("weights-"+str(iteration)+".txt", "w") + f.write("/*\n") + model.summary(print_fn=lambda l: f.write(" * "+l+"\n")) + f.write(" * "+str(performance)+"\n*/\n\n") + f.write("#include \"weights.h\"\n\n") + for (name, val) in to_output: + f.write("const float "+name+" =\n"+fix(val)+";\n\n") + f.close() + + +data = load_data(5) +model = make_model(5, 64) + +print("Before training", model.evaluate(data[1][0], data[1][1], verbose=False, batch_size=16)) +results = train(5, model, data, iterations=1, epochs=10, batch=None) diff --git a/src/pptdb.c b/src/pptdb.c index eeebf2a..42e8706 100644 --- a/src/pptdb.c +++ b/src/pptdb.c @@ -27,125 +27,119 @@ int generate; uint64_t heights[16]; FILE *training_fh = NULL; -float max_flats, outcome_black; +float max_flats; +uint8_t outcome_black; static void write_input(const int dx, const int dy, const uint8_t swap) { - // Two numbers for flats remaining - fprintf(training_fh,"%.8f,%.8f,", - (float)(white_count & 127)/max_flats, - (float)(black_count & 127)/max_flats); - - // Write the board layers - float val; - int col, row; - for (uint8_t depth = 0; depth < board_size + 1; depth++) { + // Two numbers for flats remaining + fprintf(training_fh,"%d,%.8f,%.8f,", + ply & 1 ? 1 : -1, + (float)(white_count & 127)/max_flats, + (float)(black_count & 127)/max_flats); + + // Write the board layers + float val; + int col, row; row = (dy>0)?-1:board_size; for (int i = 0; i < board_size; i++) { - row += dy; - col = (dx>0)?-1:board_size; - for (int j = 0; j < board_size; j++) { - col += dx; - const uint8_t k = - (swap) ? THE_COORDS(row, col) : THE_COORDS(col, row); - val = 0; - if (COUNT_AT(k)>depth) { - if (depth == 0) { - // Top layer of stacks is handled differently to indicate - // stone type - if (STONE_AT(k) == STONE_STANDING) { - val = (colours[k] & 1) ? +0.25 : -0.25; - } else if (STONE_AT(k) == STONE_CAPSTONE) { - val = (colours[k] & 1) ? +1.00 : -1.00; - } else { - val = (colours[k] & 1) ? +0.75 : -0.75; - } - } else { - // Layers underneath - val = (colours[k] & (1<<depth)) ? +0.75 : -0.75; - } - } - fprintf(training_fh,"%.2f,", val); - } + row += dy; + col = (dx>0)?-1:board_size; + for (int j = 0; j < board_size; j++) { + col += dx; + const uint8_t k = + (swap) ? THE_COORDS(row, col) : THE_COORDS(col, row); + val = 0; + if (COUNT_AT(k)>0) { + // Top layer of stacks is handled differently to indicate + // stone type + if (STONE_AT(k) == STONE_STANDING) { + val = (colours[k] & 1) ? +0.25 : -0.25; + } else if (STONE_AT(k) == STONE_CAPSTONE) { + val = (colours[k] & 1) ? +1.00 : -1.00; + } else { + val = (colours[k] & 1) ? +0.50 : -0.50; + } } + fprintf(training_fh,"%.2f,", val); + } } - } - fprintf(training_fh,"%.1f\n", outcome_black); + fprintf(training_fh, "%d,%d\n", outcome_black ? 1 : 0, outcome_black ? 0 : 1); } // Warning: performs _no_ checks on input whatsoever static enum ACT_RESULT parse_line(const char *pt, const ssize_t read) { - ssize_t idx; - enum ACT_RESULT r; - int total_plies = 0; - - for (idx=0;idx<read;idx++) { - if (pt[idx]==',') total_plies++; - } - for(idx=0;;) { - if (pt[idx] == 'P') { - // P [A-F][1-6] [CF]?, - idx+=2; - enum STONE_VARIANT stone; - const uint8_t col = pt[idx]-'A', row = pt[idx+1]-'1'; - - if (idx + 3 < read) { - switch (pt[idx+3]) { - case 'W': { stone = STONE_STANDING; break; } - case 'C': { stone = STONE_CAPSTONE; break; } - default: { stone = STONE_FLAT; break; } - } - } else { - stone = STONE_FLAT; - } - - r = try_place(THE_COORDS(col,row), current_colour, stone); - if (r != ACT_OK) return r; - } else if (pt[idx] == 'M') { - // M [A-F][1-6] [A-F][1-6]( [1-6])+, - idx+=2; - uint8_t drops[board_size]; - const uint8_t s_col =pt[idx]-'A', s_row=pt[idx+1]-'1', - d_col=pt[idx+3]-'A', d_row=pt[idx+4]-'1'; - idx+=4; - - enum MOVE_DIRECTION dir = M_RIGHT; - if (s_col < d_col) dir=M_RIGHT; - else if (s_col > d_col) dir=M_LEFT; - else if (s_row < d_row) dir=M_UP; - else if (s_row > d_row) dir=M_DOWN; - - uint8_t steps = 0; - do { - idx+=2; - drops[steps++] = pt[idx] - '0'; - } while (idx+2<read && pt[idx+1] != ','); - - r = try_move(THE_COORDS(s_col, s_row), dir, steps, drops); - - if (r != ACT_OK) return r; - - if (generate == 0) { - // Measure height of stacks exceeding 1 - for (int k = 0; k < board_size * board_size; k++) { - if (COUNT_AT(k)>1) heights[COUNT_AT(k)]+=1; - } - } + ssize_t idx; + enum ACT_RESULT r; + int total_plies = 0; + + for (idx=0;idx<read;idx++) { + if (pt[idx]==',') total_plies++; } - // Generate training data, not too early in the game and not at - // the end, under all eight symmetries of the board - if (generate && ply < total_plies && ply + 2 >= total_plies) { - write_input(+1, +1, 1); write_input(+1, +1, 0); - write_input(+1, -1, 1); write_input(+1, -1, 0); - write_input(-1, +1, 1); write_input(-1, +1, 0); - write_input(-1, -1, 1); write_input(-1, -1, 0); + for(idx=0;;) { + if (pt[idx] == 'P') { + // P [A-F][1-6] [CF]?, + idx+=2; + enum STONE_VARIANT stone; + const uint8_t col = pt[idx]-'A', row = pt[idx+1]-'1'; + + if (idx + 3 < read) { + switch (pt[idx+3]) { + case 'W': { stone = STONE_STANDING; break; } + case 'C': { stone = STONE_CAPSTONE; break; } + default: { stone = STONE_FLAT; break; } + } + } else { + stone = STONE_FLAT; + } + + r = try_place(THE_COORDS(col,row), current_colour, stone); + if (r != ACT_OK) return r; + } else if (pt[idx] == 'M') { + // M [A-F][1-6] [A-F][1-6]( [1-6])+, + idx+=2; + uint8_t drops[board_size]; + const uint8_t s_col =pt[idx]-'A', s_row=pt[idx+1]-'1', + d_col=pt[idx+3]-'A', d_row=pt[idx+4]-'1'; + idx+=4; + + enum MOVE_DIRECTION dir = M_RIGHT; + if (s_col < d_col) dir=M_RIGHT; + else if (s_col > d_col) dir=M_LEFT; + else if (s_row < d_row) dir=M_UP; + else if (s_row > d_row) dir=M_DOWN; + + uint8_t steps = 0; + do { + idx+=2; + drops[steps++] = pt[idx] - '0'; + } while (idx+2<read && pt[idx+1] != ','); + + r = try_move(THE_COORDS(s_col, s_row), dir, steps, drops); + + if (r != ACT_OK) return r; + + if (generate == 0) { + // Measure height of stacks exceeding 1 + for (int k = 0; k < board_size * board_size; k++) { + if (COUNT_AT(k)>1) heights[COUNT_AT(k)]+=1; + } + } + } + // Generate training data, not too early in the game and not at + // the end, under all eight symmetries of the board + if (generate && ply > 7 && ply < total_plies && ply + 10 >= total_plies) { + write_input(+1, +1, 1); write_input(+1, +1, 0); + write_input(+1, -1, 1); write_input(+1, -1, 0); + write_input(-1, +1, 1); write_input(-1, +1, 0); + write_input(-1, -1, 1); write_input(-1, -1, 0); + } + // Parse next action + while (idx<read && pt[idx++]!=','); + if (idx>=read) return ACT_OK; + next_ply(); } - // Parse next action - while (idx<read && pt[idx++]!=','); - if (idx>=read) return ACT_OK; - next_ply(); - } - return ACT_OK; + return ACT_OK; } const char* license = "pptdb, generate neural network training data from a playtak.com database dump\n\ @@ -155,92 +149,91 @@ Copyright (C) 2021, tslil clingman\n\ This program comes with ABSOLUTELY NO WARRANTY; and is made available under the terms of the GNU GPL v3 license. This is free software, and you are welcome to redistribute it under certain conditions; see COPYING for details.\n"; int main(int argc, char **argv) { - (void)(argc); - - enum ACT_RESULT r; - enum WIN_TYPE win; - uint32_t games = 0, overflow=0, illegal = 0; - uint32_t road_wins=0, flat_wins=0, road_turns=0, flat_turns=0, - white_wins = 0, black_wins = 0; - - for (int k = 0; k < 16; k++) heights[k] = 0; - - size_t len = 0; - ssize_t read = 0; - FILE *playtak_fh = NULL; - char *line = NULL, td_fn[65]; - - const uint8_t size = argv[1][0]-'0'; - - playtak_fh = fopen(argv[2], "r"); - if (playtak_fh == NULL) exit(EXIT_FAILURE); - - if (argc > 3 && (!strncmp("generate", argv[3], 8))) { - generate=1; - max_flats = (size == 5) ? 21.0 : 30.0; - snprintf(td_fn, 64, "data/training-%d.csv",size); - training_fh = fopen(td_fn, "w"); - if (training_fh == NULL) exit(EXIT_FAILURE); - } else generate=0; - - while ((read = getline(&line, &len, playtak_fh)) != -1) { - // Reset everything - reset_state(size); - // Store the outcome of this game. Black win = 1 - if (line[read-4] == '0') outcome_black = 0.9; - else outcome_black = -0.9; - // Parse the line - r = parse_line(line,read-4); - // Adjust counts if we're not generating training data - if (generate == 0) { - if (r == ACT_ILLEGAL) { - illegal++; - printf("Illegal:\n%s",line); - } else if (r == ACT_OVERFLOW) { - printf("Overflow:\n%s",line); - overflow++; - } else { - win = check_win(); - if (win == WIN_FLAT_BLACK - || win == WIN_FLAT_WHITE - || win == WIN_DRAW) { - flat_wins++; - flat_turns += ply/2+1; - } else { - road_wins++; - road_turns += ply/2+1; - } - if (win == WIN_FLAT_BLACK || win == WIN_ROAD_BLACK) - black_wins++; - else if (win == WIN_FLAT_WHITE || win == WIN_ROAD_WHITE) - white_wins++; - } + (void)(argc); + + enum ACT_RESULT r; + enum WIN_TYPE win; + uint32_t games = 0, overflow=0, illegal = 0; + uint32_t road_wins=0, flat_wins=0, road_turns=0, flat_turns=0, + white_wins = 0, black_wins = 0; + + for (int k = 0; k < 16; k++) heights[k] = 0; + + size_t len = 0; + ssize_t read = 0; + FILE *playtak_fh = NULL; + char *line = NULL, td_fn[65]; + + const uint8_t size = argv[1][0]-'0'; + + playtak_fh = fopen(argv[2], "r"); + if (playtak_fh == NULL) exit(EXIT_FAILURE); + + if (argc > 3 && (!strncmp("generate", argv[3], 8))) { + generate=1; + max_flats = (size == 5) ? 21.0 : 30.0; + snprintf(td_fn, 64, "data/parsed-%d.csv",size); + training_fh = fopen(td_fn, "w"); + if (training_fh == NULL) exit(EXIT_FAILURE); + } else generate=0; + + while ((read = getline(&line, &len, playtak_fh)) != -1) { + // Reset everything + reset_state(size); + // Store the outcome of this game. Black win = 1 + outcome_black = (line[read-4] == '0'); + // Parse the line + r = parse_line(line,read-4); + // Adjust counts if we're not generating training data + if (generate == 0) { + if (r == ACT_ILLEGAL) { + illegal++; + printf("Illegal:\n%s",line); + } else if (r == ACT_OVERFLOW) { + printf("Overflow:\n%s",line); + overflow++; + } else { + win = check_win(); + if (win == WIN_FLAT_BLACK + || win == WIN_FLAT_WHITE + || win == WIN_DRAW) { + flat_wins++; + flat_turns += ply/2+1; + } else { + road_wins++; + road_turns += ply/2+1; + } + if (win == WIN_FLAT_BLACK || win == WIN_ROAD_BLACK) + black_wins++; + else if (win == WIN_FLAT_WHITE || win == WIN_ROAD_WHITE) + white_wins++; + } + } + games++; } - games++; - } - fclose(playtak_fh); - if (generate) fclose(training_fh); - if (line) free(line); + fclose(playtak_fh); + if (generate) fclose(training_fh); + if (line) free(line); - if (illegal || overflow) putchar('\n'); - printf("Read %d games\n",games); + if (illegal || overflow) putchar('\n'); + printf("Read %d games\n",games); - if (generate==0) { - printf("Illegals: %d\nOverflows: %d\n\ + if (generate==0) { + printf("Illegals: %d\nOverflows: %d\n\ Black wins: %.3f%%\n\ Road wins: %d\nFlat wins: %d\n\ Average turns to road win: %.3f\n\ Average turns to flat win: %.3f\n", - illegal, overflow, - (double)black_wins / (double)(black_wins+white_wins) * 100, - road_wins, flat_wins, - (double)(road_turns)/(double)(road_wins), - (double)(flat_turns)/(double)(flat_wins)); - for (int k = 2; k < 16; k++) { - printf("Height %2d: %7ld\n",k,heights[k]); + illegal, overflow, + (double)black_wins / (double)(black_wins+white_wins) * 100, + road_wins, flat_wins, + (double)(road_turns)/(double)(road_wins), + (double)(flat_turns)/(double)(flat_wins)); + for (int k = 2; k < 16; k++) { + printf("Height %2d: %7ld\n",k,heights[k]); + } } - } - exit(EXIT_SUCCESS); + exit(EXIT_SUCCESS); } |
