commit 2fc01f0a8ff7da1fafbcf537702e54c2cf94711e
Author: Dimitrije Dobrota <mail@dimitrijedobrota.com>
Date: Mon, 5 Sep 2022 22:32:50 +0200
Initial commit
Diffstat:
A | .clang-format | | | 178 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | .gitignore | | | 58 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | Makefile | | | 69 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | include/anki.h | | | 34 | ++++++++++++++++++++++++++++++++++ |
A | include/board.h | | | 61 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | include/display.h | | | 70 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | include/move.h | | | 17 | +++++++++++++++++ |
A | src/anki.c | | | 248 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | src/board.c | | | 310 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | src/display.c | | | 189 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | src/main.c | | | 234 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | src/move.c | | | 47 | +++++++++++++++++++++++++++++++++++++++++++++++ |
12 files changed, 1515 insertions(+), 0 deletions(-)
diff --git a/.clang-format b/.clang-format
@@ -0,0 +1,178 @@
+---
+Language: Cpp
+# BasedOnStyle: LLVM
+AccessModifierOffset: -2
+AlignAfterOpenBracket: true
+AlignArrayOfStructures: Right
+AlignConsecutiveMacros: true
+AlignConsecutiveAssignments: None
+AlignConsecutiveBitFields: None
+AlignConsecutiveDeclarations: true
+AlignEscapedNewlines: Right
+AlignOperands: Align
+AlignTrailingComments: true
+AllowAllArgumentsOnNextLine: true
+AllowAllConstructorInitializersOnNextLine: true
+AllowAllParametersOfDeclarationOnNextLine: true
+AllowShortEnumsOnASingleLine: true
+AllowShortBlocksOnASingleLine: true
+AllowShortCaseLabelsOnASingleLine: false
+AllowShortFunctionsOnASingleLine: All
+AllowShortLambdasOnASingleLine: All
+AllowShortIfStatementsOnASingleLine: Never
+AllowShortLoopsOnASingleLine: false
+AlwaysBreakAfterDefinitionReturnType: None
+AlwaysBreakAfterReturnType: None
+AlwaysBreakBeforeMultilineStrings: false
+AlwaysBreakTemplateDeclarations: MultiLine
+AttributeMacros:
+ - __capability
+BinPackArguments: true
+BinPackParameters: true
+BraceWrapping:
+ AfterCaseLabel: false
+ AfterClass: false
+ AfterControlStatement: Never
+ AfterEnum: false
+ AfterFunction: false
+ AfterNamespace: false
+ AfterObjCDeclaration: false
+ AfterStruct: false
+ AfterUnion: false
+ AfterExternBlock: false
+ BeforeCatch: false
+ BeforeElse: false
+ BeforeLambdaBody: false
+ BeforeWhile: false
+ IndentBraces: false
+ SplitEmptyFunction: true
+ SplitEmptyRecord: true
+ SplitEmptyNamespace: true
+BreakBeforeBinaryOperators: None
+BreakBeforeConceptDeclarations: true
+BreakBeforeBraces: Attach
+BreakBeforeInheritanceComma: false
+BreakInheritanceList: BeforeColon
+BreakBeforeTernaryOperators: true
+BreakConstructorInitializersBeforeComma: false
+BreakConstructorInitializers: BeforeColon
+BreakAfterJavaFieldAnnotations: false
+BreakStringLiterals: true
+ColumnLimit: 80
+CommentPragmas: '^ IWYU pragma:'
+CompactNamespaces: false
+ConstructorInitializerAllOnOneLineOrOnePerLine: false
+ConstructorInitializerIndentWidth: 4
+ContinuationIndentWidth: 4
+Cpp11BracedListStyle: true
+DeriveLineEnding: true
+DerivePointerAlignment: false
+DisableFormat: false
+EmptyLineAfterAccessModifier: Never
+EmptyLineBeforeAccessModifier: LogicalBlock
+ExperimentalAutoDetectBinPacking: false
+FixNamespaceComments: true
+ForEachMacros:
+ - foreach
+ - Q_FOREACH
+ - BOOST_FOREACH
+IfMacros:
+ - KJ_IF_MAYBE
+IncludeBlocks: Preserve
+IncludeCategories:
+ - Regex: '^"(llvm|llvm-c|clang|clang-c)/'
+ Priority: 2
+ SortPriority: 0
+ CaseSensitive: false
+ - Regex: '^(<|"(gtest|gmock|isl|json)/)'
+ Priority: 3
+ SortPriority: 0
+ CaseSensitive: false
+ - Regex: '.*'
+ Priority: 1
+ SortPriority: 0
+ CaseSensitive: false
+IncludeIsMainRegex: '(Test)?$'
+IncludeIsMainSourceRegex: ''
+IndentAccessModifiers: false
+IndentCaseLabels: false
+IndentCaseBlocks: false
+IndentGotoLabels: true
+IndentPPDirectives: None
+IndentExternBlock: AfterExternBlock
+IndentRequires: false
+IndentWidth: 2
+IndentWrappedFunctionNames: false
+InsertTrailingCommas: None
+JavaScriptQuotes: Leave
+JavaScriptWrapImports: true
+KeepEmptyLinesAtTheStartOfBlocks: true
+LambdaBodyIndentation: Signature
+MacroBlockBegin: ''
+MacroBlockEnd: ''
+MaxEmptyLinesToKeep: 1
+NamespaceIndentation: None
+ObjCBinPackProtocolList: Auto
+ObjCBlockIndentWidth: 2
+ObjCBreakBeforeNestedBlockParam: true
+ObjCSpaceAfterProperty: false
+ObjCSpaceBeforeProtocolList: true
+PenaltyBreakAssignment: 2
+PenaltyBreakBeforeFirstCallParameter: 19
+PenaltyBreakComment: 300
+PenaltyBreakFirstLessLess: 120
+PenaltyBreakString: 1000
+PenaltyBreakTemplateDeclaration: 10
+PenaltyExcessCharacter: 1000000
+PenaltyReturnTypeOnItsOwnLine: 60
+PenaltyIndentedWhitespace: 0
+PointerAlignment: Right
+PPIndentWidth: -1
+ReferenceAlignment: Pointer
+ReflowComments: true
+ShortNamespaceLines: 1
+SortIncludes: CaseSensitive
+SortJavaStaticImport: Before
+SortUsingDeclarations: true
+SpaceAfterCStyleCast: false
+SpaceAfterLogicalNot: false
+SpaceAfterTemplateKeyword: true
+SpaceBeforeAssignmentOperators: true
+SpaceBeforeCaseColon: false
+SpaceBeforeCpp11BracedList: false
+SpaceBeforeCtorInitializerColon: true
+SpaceBeforeInheritanceColon: true
+SpaceBeforeParens: ControlStatements
+SpaceAroundPointerQualifiers: Default
+SpaceBeforeRangeBasedForLoopColon: true
+SpaceInEmptyBlock: false
+SpaceInEmptyParentheses: false
+SpacesBeforeTrailingComments: 1
+SpacesInAngles: Never
+SpacesInConditionalStatement: false
+SpacesInContainerLiterals: true
+SpacesInCStyleCastParentheses: false
+SpacesInLineCommentPrefix:
+ Minimum: 1
+ Maximum: -1
+SpacesInParentheses: false
+SpacesInSquareBrackets: false
+SpaceBeforeSquareBrackets: false
+BitFieldColonSpacing: Both
+Standard: Latest
+StatementAttributeLikeMacros:
+ - Q_EMIT
+StatementMacros:
+ - Q_UNUSED
+ - QT_REQUIRE_VERSION
+TabWidth: 8
+UseCRLF: false
+UseTab: Never
+WhitespaceSensitiveMacros:
+ - STRINGIZE
+ - PP_STRINGIZE
+ - BOOST_PP_STRINGIZE
+ - NS_SWIFT_NAME
+ - CF_SWIFT_NAME
+...
+
diff --git a/.gitignore b/.gitignore
@@ -0,0 +1,58 @@
+.ccls-cache/
+.ccls
+docs/*
+bin
+bin/*
+
+# Prerequisites
+*.d
+
+# Object files
+*.o
+*.ko
+*.obj
+*.elf
+
+# Linker output
+*.ilk
+*.map
+*.exp
+
+# Precompiled Headers
+*.gch
+*.pch
+
+# Libraries
+*.lib
+*.a
+*.la
+*.lo
+
+# Shared objects (inc. Windows DLLs)
+*.dll
+*.so
+*.so.*
+*.dylib
+
+# Executables
+*.exe
+*.out
+*.app
+*.i*86
+*.x86_64
+*.hex
+
+# Debug files
+*.dSYM/
+*.su
+*.idb
+*.pdb
+
+# Kernel Module Compile Results
+*.mod*
+*.cmd
+.tmp_versions/
+modules.order
+Module.symvers
+Mkfile.old
+dkms.conf
diff --git a/Makefile b/Makefile
@@ -0,0 +1,69 @@
+# GNU Makefile for Chess Anki Trainer
+#
+# Usage: make [-f path\Makefile] [DEBUG=Y] [NO_UNICODE=Y] [NO_MOUSE=Y] target
+
+NAME = chess
+CC = gcc
+
+CFLAGS = -I include
+LDFLAGS =-lcurl -ljson-c -lncursesw -lm
+
+SRC = src
+OBJ = obj
+BINDIR = bin
+
+BIN = bin/$(NAME)
+SRCS=$(wildcard $(SRC)/*.c)
+OBJS=$(patsubst $(SRC)/%.c, $(OBJ)/%.o, $(SRCS))
+
+ifeq ($(OS),Windows_NT)
+ RM = del
+ NAME := $(NAME).exe
+ DEL_CLEAN = $(subst /,\,$(BIN)) $(subst /,\,$(OBJS))
+else
+ RM = rm -f
+ DEL_CLEAN = $(BIN) $(OBJS)
+endif
+
+ifeq ($(DEBUG),Y)
+ CFLAGS += -lciid -ldisplayd
+ CFLAGS += -Wall -Wextra -Werror -Wpedantic -fsanitize=address -ggdb
+else
+ CFLAGS += -lcii -ldisplay
+endif
+ifeq ($(NO_UNICODE),Y)
+ CFLAGS += -D NO_UNICODE
+endif
+
+ifeq ($(NO_MOUSE),Y)
+ CFLAGS += -D NO_MOUSE
+endif
+
+all: $(BIN)
+
+$(BIN): $(OBJS)
+ $(CC) $^ $(CFLAGS) $(LDFLAGS) -o $@
+
+$(OBJ)/%.o: $(SRC)/%.c
+ $(CC) -c $< -o $@ $(CFLAGS) $(LDFLAGS)
+
+clean:
+ -$(RM) $(DEL_CLEAN)
+
+help:
+ @echo "Game of Life Simulation"
+ @echo
+ @echo "Usage: make [-f path\Makefile] [DEBUG=Y] [NO_UNICODE=Y] target"
+ @echo
+ @echo "Target rules:"
+ @echo " all - Compiles binary file [Default]"
+ @echo " clean - Clean the project by removing binaries"
+ @echo " help - Prints a help message with target rules"
+ @echo
+ @echo "Optional parameters:"
+ @echo " DEBUG - Compile binary file with debug flags enabled"
+ @echo " NO_UNICODE - Compile binary file that does not use Unicode characters"
+ @echo " NO_MOUSE - Compile binary file that does not have mouse support even if terminal supports it"
+ @echo
+
+.PHONY: all clean help docs
diff --git a/include/anki.h b/include/anki.h
@@ -0,0 +1,34 @@
+#ifndef ANKI_H
+#define ANKI_H
+
+#include <except.h>
+#include <stddef.h>
+
+#define Move_T card_T
+typedef struct Move_T *Move_T;
+
+extern const Except_T ANKIE_CONNECT, ANKIE_CURL, ANKIE_DECK, ANKIE_DECK_SELECT,
+ ANKIE_OK, ANKIE_PROFILE, ANKIE_PROFILE_SELECT;
+
+void anki_start(void);
+void anki_stop(void);
+
+Move_T anki_current_card(void);
+void anki_grade(int pass);
+void anki_suspend(Move_T self);
+
+char *card_fen(Move_T self);
+char *card_game(Move_T self);
+char *card_start(Move_T self);
+char *card_name(Move_T self);
+char *card_pgn(Move_T self);
+char *card_player(Move_T self);
+char *card_id(Move_T self);
+
+const char **anki_get_profiles(size_t *size);
+const char **anki_get_decks(size_t *size);
+void anki_load_profile(char *name);
+void anki_load_deck(char *name);
+
+#undef Move_T
+#endif
diff --git a/include/board.h b/include/board.h
@@ -0,0 +1,61 @@
+#ifndef BOARD_H
+#define BOARD_H
+
+#include "anki.h"
+#include "move.h"
+
+#define Move_T Board_T
+#define G Grave_T
+
+typedef struct Move_T *Move_T;
+typedef struct G *G;
+
+enum AC { AC_QUIT = -3, AC_SUSPEND, AC_INDEX };
+
+#define BUFF_SIZE 10
+
+typedef void (*review_f)(void);
+typedef struct game_T *game_T;
+struct game_T {
+ card_T card;
+ move_T *moves;
+ review_f review_next;
+
+ int pass;
+ int fail;
+
+ size_t display_current;
+ size_t move_current;
+
+ size_t move_start;
+ size_t move_fail;
+
+ char buffer[BUFF_SIZE + 1];
+ int buffer_crnt;
+
+ size_t moves_size;
+ size_t boards_size;
+ Move_T boards[];
+};
+
+Move_T Board_new(void);
+Move_T Board_from_FEN(char *fen);
+void Board_free(Move_T *self);
+
+Move_T Board_play(Move_T self, char *m);
+void Board_print(Move_T self);
+
+char Board_atIndex(Move_T self, int i, int j);
+
+int piece_get_index(char l);
+
+G Board_grave(Move_T self, char player);
+char Grave_atIndex(G self, int index);
+int Grave_size(G self);
+
+/* GAME */
+game_T game_new(size_t moves);
+
+#undef Move_T
+#undef G
+#endif
diff --git a/include/display.h b/include/display.h
@@ -0,0 +1,70 @@
+#ifndef DISPAY_H
+#define DISPAY_H
+
+#include <pane.h>
+#include <widgetList.h>
+
+#include "board.h"
+#include "except.h"
+
+#define MAX_READ 5
+
+void display_start(void);
+void display_stop(void);
+
+/* void display_board(Board_T self); */
+void display_moves(int halfmoves);
+void display_title(char *name);
+void display_error(char *err);
+
+void display_fail(void);
+void display_pass(void);
+void display_clear(void);
+
+int display_input(char *buf, int *index);
+
+char *display_select_menu(const char **list, int size);
+char display_getch(void);
+
+typedef struct boardInfo_T *boardInfo_T;
+struct boardInfo_T {
+ int something;
+};
+
+typedef struct boardStyle_T *boardStyle_T;
+struct boardStyle_T {
+ int square_light;
+ int square_dark;
+ int border;
+ int piece;
+ int annotation;
+};
+
+typedef struct movesInfo_T *movesInfo_T;
+struct movesInfo_T {
+ widgetList_T list;
+};
+
+typedef struct movesStyle_T *movesStyle_T;
+struct movesStyle_T {
+ int padding;
+ int foreground;
+ int background;
+ int active;
+};
+
+typedef struct titleInfo_T *titleInfo_T;
+struct titleInfo_T {
+ widget_T subwidget;
+};
+
+typedef struct titleStyle_T *titleStyle_T;
+struct titleStyle_T {
+ int something;
+};
+
+void board_display(widget_T widget);
+void moves_display(widget_T widget);
+int game_handleInput(data_T data, struct tb_event ev);
+
+#endif
diff --git a/include/move.h b/include/move.h
@@ -0,0 +1,17 @@
+#ifndef MOVE_H
+#define MOVE_H
+
+#include <stddef.h>
+
+typedef struct move_T *move_T;
+struct move_T {
+ char turn[10];
+ char white[10];
+ char black[10];
+};
+
+move_T *Move_list(char *pgn, size_t *size);
+char *Move_halfmove(move_T *moves, int halfturn);
+
+#undef Board_T
+#endif
diff --git a/src/anki.c b/src/anki.c
@@ -0,0 +1,248 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <curl/curl.h>
+#include <json-c/json.h>
+
+#include "anki.h"
+#include "assert.h"
+#include "display.h"
+#include "except.h"
+#include "mem.h"
+
+#define Move_T card_T
+
+struct card_T {
+ char *id;
+ char *name;
+ char *game;
+ char *fen;
+ char *pgn;
+ char *player;
+ char *start;
+};
+
+Move_T card;
+
+#define ANKI_URL "127.0.0.1:8765"
+#define ANKI_CURRENT_CARD "{\"version\": 6, \"action\": \"guiCurrentCard\"}"
+#define ANKI_EXIT "{\"version\": 6, \"action\": \"guiExitAnki\"}"
+#define ANKI_INIT "{\"version\": 6, \"action\": \"requestPermission\"}"
+#define ANKI_SYNC "{\"version\": 6, \"action\": \"sync\"}"
+
+#define ANKI_PROFILES "{\"version\": 6, \"action\": \"getProfiles\"}"
+#define ANKI_DECKS "{\"version\": 6, \"action\": \"deckNames\"}"
+
+#define ANKI_GRADE_FAIL \
+ "{\"version\": 6, \"action\": \"multi\", \"params\": { \"actions\": [ " \
+ "{\"action\": \"guiShowAnswer\"}, { \"action\": \"guiAnswerCard\", " \
+ "\"params\": {\"ease\": 1}}]}}"
+#define ANKI_GRADE_PASS \
+ "{\"version\": 6, \"action\": \"multi\", \"params\": { \"actions\": [ " \
+ "{\"action\": \"guiShowAnswer\"}, { \"action\": \"guiAnswerCard\", " \
+ "\"params\": {\"ease\": 3}}]}}"
+
+#define JSON_PRINT_FLAGS JSON_C_TO_STRING_SPACED | JSON_C_TO_STRING_PRETTY
+#define JSON_PRINT(x) \
+ fprintf(stderr, "%s\n---\n", \
+ json_object_to_json_string_ext((x), JSON_PRINT_FLAGS));
+#define JSON_STRING_ASSERT(x, value) \
+ strstr(json_object_get_string((x)), (value)) != NULL
+
+CURL *curl;
+CURLcode res;
+
+struct json_object *result;
+size_t got_data(void *buffer, size_t itemsize, size_t nitems, void *ignore);
+
+const Except_T ANKIE_CONNECT = {"Anki: Couldn't connect to the server"},
+ ANKIE_CURL = {"Anki: Couldn't initialize libcurl"},
+ ANKIE_DECK = {"Anki: Invalid deck"},
+ ANKIE_DECK_SELECT = {"Anki: Invalid deck selection"},
+ ANKIE_GRADE = {"Anki: Unable to grade current card"},
+ ANKIE_OK = {"everything is ok"},
+ ANKIE_PROFILE = {"Anki: Invalid profile"},
+ ANKIE_PROFILE_SELECT = {"Anki: Invalid profile selection"},
+ ANKIE_SUSPEND = {"Anki: Unable to suspend current card"};
+
+void curl_do(char *action) {
+ curl_easy_setopt(curl, CURLOPT_URL, ANKI_URL);
+ curl_easy_setopt(curl, CURLOPT_POSTFIELDS, action);
+ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, got_data);
+
+ res = curl_easy_perform(curl);
+
+ switch (res) {
+ case CURLE_OK:
+ break;
+ case CURLE_COULDNT_CONNECT:
+ RAISE(ANKIE_CONNECT);
+ default:
+ RAISE(ANKIE_CONNECT);
+ }
+
+ /* JSON_PRINT(result); */
+}
+
+size_t got_data(void *buffer, size_t itemsize, size_t nitems, void *ignore) {
+ (void)ignore;
+
+ size_t bytes = itemsize * nitems;
+ free(result);
+ struct json_object *parsed_json = json_tokener_parse(buffer);
+ json_object_object_get_ex(parsed_json, "result", &result);
+ return bytes;
+}
+
+void anki_connect(void) {
+ struct json_object *permission;
+ curl_do(ANKI_INIT);
+ json_object_object_get_ex(result, "permission", &permission);
+
+ if (!(JSON_STRING_ASSERT(permission, "granted")))
+ RAISE(ANKIE_CONNECT);
+}
+
+void card_dtor(Move_T self) {
+ FREE(self->fen);
+ FREE(self->game);
+ FREE(self->id);
+ FREE(self->name);
+ FREE(self->pgn);
+ FREE(self->player);
+ FREE(self->start);
+}
+
+void card_set_field(Move_T self, char *key, const char *name) {
+ char **field;
+
+ if (strcmp(key, "id") == 0)
+ field = &self->id;
+ else if (strcmp(key, "name") == 0)
+ field = &self->name;
+ else if (strcmp(key, "FEN") == 0)
+ field = &self->fen;
+ else if (strcmp(key, "PGN") == 0)
+ field = &self->pgn;
+ else if (strcmp(key, "player") == 0)
+ field = &self->player;
+ else if (strcmp(key, "game") == 0)
+ field = &self->game;
+ else if (strcmp(key, "start") == 0)
+ field = &self->start;
+ else
+ return;
+
+ *field = ALLOC(strlen(name) * sizeof(char) + 1);
+ strcpy(*field, name);
+}
+
+Move_T anki_current_card(void) {
+ struct json_object *fields, *card_id;
+ /* FREE(card); */
+ NEW(card);
+
+ curl_do(ANKI_CURRENT_CARD);
+ if (result == NULL)
+ return NULL;
+
+ json_object_object_get_ex(result, "fields", &fields);
+ json_object_object_get_ex(result, "cardId", &card_id);
+
+ json_object_object_foreach(fields, key, val_t) {
+ struct json_object *value;
+ json_object_object_get_ex(val_t, "value", &value);
+ card_set_field(card, key, json_object_get_string(value));
+ }
+
+ card_set_field(card, "id", json_object_get_string(card_id));
+
+ if (!(card->name && card->pgn)) {
+ printf("Card info incomplete. Suspending it...\n");
+ anki_suspend(card);
+ return 0;
+ }
+
+ return card;
+}
+
+void anki_start(void) {
+ if (!(curl = curl_easy_init()))
+ RAISE(ANKIE_CURL);
+ anki_connect();
+}
+
+const char **anki_list(size_t *size) {
+ size_t len = json_object_array_length(result), i;
+ const char **list;
+
+ list = ALLOC(len * sizeof(const char *));
+ for (i = 0; i < len; i++)
+ list[i] = json_object_get_string(json_object_array_get_idx(result, i));
+
+ *size = len;
+ return list;
+}
+
+const char **anki_get_profiles(size_t *size) {
+ curl_do(ANKI_PROFILES);
+ return anki_list(size);
+}
+
+void anki_load_profile(char *name) {
+ static char request[100];
+ sprintf(request,
+ "{\"version\": 6, \"action\": \"loadProfile\", \"params\": { "
+ "\"name\": \"%s\" }}",
+ name);
+ curl_do(request);
+ if (!(JSON_STRING_ASSERT(result, "true")))
+ RAISE(ANKIE_PROFILE);
+}
+
+const char **anki_get_decks(size_t *size) {
+ curl_do(ANKI_DECKS);
+ return anki_list(size);
+}
+
+void anki_load_deck(char *name) {
+ static char request[1000];
+
+ if (!name)
+ RAISE(ANKIE_DECK_SELECT);
+ sprintf(request,
+ "{\"version\": 6, \"action\": \"guiDeckReview\", \"params\": { "
+ "\"name\": \"%s\" }}",
+ name);
+ curl_do(request);
+
+ if (!(JSON_STRING_ASSERT(result, "true")))
+ RAISE(ANKIE_DECK);
+}
+
+void anki_grade(int pass) {
+ curl_do((pass) ? ANKI_GRADE_PASS : ANKI_GRADE_FAIL);
+ if (!(JSON_STRING_ASSERT(result, "true")))
+ RAISE(ANKIE_GRADE);
+}
+
+void anki_suspend(Move_T self) {
+ static char request[100];
+ sprintf(request,
+ "{\"action\": \"suspend\", \"version\": 6, \"params\": { \"cards\": "
+ "[%s] }}",
+ self->id);
+ curl_do(request);
+ if (!(JSON_STRING_ASSERT(result, "true")))
+ RAISE(ANKIE_SUSPEND);
+}
+
+void anki_stop(void) { curl_easy_cleanup(curl); }
+char *card_name(Move_T self) { return self->name; }
+char *card_pgn(Move_T self) { return self->pgn; }
+char *card_start(Move_T self) { return self->start; }
+char *card_game(Move_T self) { return self->game; }
+char *card_player(Move_T self) { return self->player; }
+char *card_fen(Move_T self) { return self->fen; }
+char *card_id(Move_T self) { return self->id; }
diff --git a/src/board.c b/src/board.c
@@ -0,0 +1,310 @@
+#include "board.h"
+#include "assert.h"
+#include "mem.h"
+
+#include <ctype.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "move.h"
+
+#define T Board_T
+#define G Grave_T
+typedef struct G *G;
+
+#define MAX_PLAY 1024
+
+const char def_board[8][8] = {"rnbqkbnr", "pppppppp", " ", " ",
+ " ", " ", "PPPPPPPP", "RNBQKBNR"};
+static char piece_lookup[] = {'K', 'Q', 'R', 'B', 'N', 'P',
+ 'k', 'q', 'r', 'b', 'n', 'p'};
+static int piece_value[] = {100, 8, 5, 3, 3, 1, 100, 8, 5, 3, 3, 1};
+
+struct G {
+ char arr[20];
+ int size;
+ int value;
+};
+
+struct T {
+ int turn;
+ char *board;
+ G white;
+ G black;
+};
+
+typedef struct {
+ int x[8];
+ int y[8];
+ int size;
+ int limited;
+} move_offset_t;
+
+move_offset_t piece_offset[] = {
+ { {0, 0, 1, -1, -1, -1, 1, 1}, {-1, 1, 0, 0, -1, 1, 1, -1}, 8, 1},
+ { {0, 0, 1, -1, -1, -1, 1, 1}, {-1, 1, 0, 0, -1, 1, 1, -1}, 8, 0},
+ { {0, 0, 1, -1, 0, 0, 0, 0}, {-1, 1, 0, 0, 0, 0, 0, 0}, 4, 0},
+ { {-1, -1, 1, 1, 0, 0, 0, 0}, {-1, 1, 1, -1, 0, 0, 0, 0}, 4, 0},
+ {{-2, -1, 1, 2, 2, 1, -1, -2}, {1, 2, 2, 1, -1, -2, -2, -1}, 8, 1}
+};
+
+void __grave_copy(G self, G new) {
+ new->size = self->size;
+ new->value = self->value;
+ for (int i = 0; i < self->size; i++)
+ new->arr[i] = self->arr[i];
+}
+
+void __board_send_grave(T self, char piece, char color) {
+ G g = (color == 'w') ? self->white : self->black;
+
+ g->arr[g->size++] = piece;
+ g->value += piece_value[piece_get_index(piece)];
+}
+
+char __board_get_at(T self, char file, int rank) {
+ if (file > 'h' || file < 'a' || rank < 1 || rank > 8)
+ return '!';
+ return self->board[(8 - rank) * 8 + file - 'a'];
+}
+
+void __board_set_at(T self, char file, int rank, char to) {
+ self->board[(8 - rank) * 8 + file - 'a'] = to;
+}
+
+typedef int (*__board_find_next_f)(T, char, char *, int *);
+
+int __board_find_next_rank(T self, char piece, char *file, int *rank) {
+ for (; *file <= 'h'; (*file)++)
+ if (__board_get_at(self, *file, *rank) == piece)
+ return 1;
+ return 0;
+}
+
+int __board_find_next_file(T self, char piece, char *file, int *rank) {
+ for (; *rank <= 8; (*rank)++)
+ if (__board_get_at(self, *file, *rank) == piece)
+ return 1;
+ return 0;
+}
+
+int __board_find_next(T self, char piece, char *file, int *rank) {
+ for (; *file <= 'h'; (*file)++) {
+ for (; *rank <= 8; (*rank)++)
+ if (__board_get_at(self, *file, *rank) == piece)
+ return 1;
+ *rank = 1;
+ }
+ return 0;
+}
+
+void __board_copy(T self, T new) {
+ __grave_copy(self->white, new->white);
+ __grave_copy(self->black, new->black);
+ new->turn = self->turn;
+ for (int i = 0; i < 8; i++)
+ for (int j = 0; j < 8; j++)
+ new->board[i * 8 + j] = self->board[i * 8 + j];
+}
+
+int __board_can_move(T self, char piece, char f_s, int r_s, char f_e, int r_e,
+ int take) {
+
+ char piece_e = __board_get_at(self, f_e, r_e);
+ if (take && !(piece_e != ' ' || (piece == 'P')))
+ return 0;
+
+ if (toupper(piece) == 'P') {
+ int mul = (piece == 'P') ? 1 : -1;
+ int step = ((piece == 'P' && r_s == 2) || r_s == 7) ? 2 : 1;
+ if (!take) {
+ if (f_s != f_e)
+ return 0;
+ for (int i = 1; i <= step; i++) {
+ char piece_c = __board_get_at(self, f_s, r_s + i * mul);
+ if (piece_c != ' ')
+ return 0;
+ if (r_s + i * mul == r_e)
+ return 1;
+ }
+ } else {
+ return (r_s + mul == r_e && (f_s + 1 == f_e || f_s - 1 == f_e));
+ }
+ } else {
+ piece = toupper(piece);
+ move_offset_t *move = piece_offset + piece_get_index(piece);
+
+ for (int i = 0; i < move->size; i++) {
+ int rank = r_s, file = f_s;
+ do {
+ file += move->y[i];
+ rank += move->x[i];
+ if (rank == r_e && file == f_e)
+ return 1;
+ } while (!move->limited && __board_get_at(self, file, rank) == ' ');
+ }
+ }
+ return 0;
+}
+
+G grave_new(void) {
+ G p;
+ NEW(p);
+ p->size = 0;
+ p->value = 0;
+ return p;
+}
+
+char Grave_atIndex(G self, int index) {
+ assert(self);
+ return self->arr[index];
+}
+
+int Grave_size(G self) {
+ assert(self);
+ return self->size;
+}
+
+G Board_grave(T self, char player) {
+ assert(self);
+ printf("%c\n", player);
+ return (player == 'b') ? self->white : self->black;
+}
+
+T Board_new(void) {
+ T board;
+ NEW(board);
+ board->white = grave_new();
+ board->black = grave_new();
+ board->board = ALLOC(64 * sizeof(char) + 1);
+ for (int i = 0; i < 8; i++)
+ for (int j = 0; j < 8; j++)
+ board->board[i * 8 + j] = def_board[i][j];
+
+ board->turn = 0;
+ return board;
+}
+
+T Board_from_FEN(char *fen) {
+ T board = Board_new();
+
+ int j = 0;
+ do {
+ if (isalpha(*fen))
+ board->board[j++] = *fen;
+ else if (isdigit(*fen))
+ for (int i = 0; i < *fen - '0'; i++)
+ board->board[j++] = ' ';
+ } while (*fen++ != '\0' && *fen != ' ');
+ return board;
+}
+
+void Board_free(T *self) {
+ assert(self);
+ FREE((*self)->board);
+ FREE(*self);
+}
+
+T Board_play(T self, char *m) {
+ assert(self);
+ assert(m);
+
+ static char mover = 'b';
+ mover = (mover == 'b') ? 'w' : 'b';
+
+ T new = Board_new();
+ __board_copy(self, new);
+
+ int (*conv)(int) = (mover == 'w') ? toupper : tolower;
+ int castle_rank = (mover == 'w') ? 1 : 8;
+
+ if (strcmp(m, "O-O") == 0) {
+ __board_set_at(new, 'e', castle_rank, ' ');
+ __board_set_at(new, 'f', castle_rank, conv('R'));
+ __board_set_at(new, 'g', castle_rank, conv('K'));
+ __board_set_at(new, 'h', castle_rank, ' ');
+ } else if (strcmp(m, "O-O-O") == 0) {
+ __board_set_at(new, 'a', castle_rank, ' ');
+ __board_set_at(new, 'c', castle_rank, conv('K'));
+ __board_set_at(new, 'd', castle_rank, conv('R'));
+ __board_set_at(new, 'e', castle_rank, ' ');
+ } else {
+ // Decoding move MESS Start
+ int l = strlen(m);
+ if (m[l - 1] == '+' || m[l - 1] == '#')
+ l--;
+
+ char file = m[l - 2];
+ int rank = m[l - 1] - '0';
+
+ int take = 0;
+ char select = '\0', moved;
+ if (l == 4 && m[1] == 'x')
+ take = 1;
+ else if (l >= 4) {
+ select = m[1];
+ take = m[2] == 'x';
+ }
+
+ if (isupper(m[0]))
+ moved = conv(m[0]);
+ else {
+ moved = conv('P');
+ select = m[0];
+ }
+ // MESS End
+
+ char file_s = (isalpha(select)) ? select : 'a';
+ int rank_s = (isdigit(select)) ? select - '0' : 1;
+
+ __board_find_next_f find_f;
+ if (select)
+ find_f =
+ (isalpha(select)) ? __board_find_next_file : __board_find_next_rank;
+ else
+ find_f = __board_find_next;
+
+ while (find_f(self, moved, &file_s, &rank_s)) {
+ if (__board_can_move(self, moved, file_s, rank_s, file, rank, take)) {
+ if (take)
+ __board_send_grave(new, __board_get_at(self, file, rank), mover);
+ __board_set_at(new, file_s, rank_s, ' ');
+ __board_set_at(new, file, rank, moved);
+ break;
+ }
+ rank_s++;
+ }
+ }
+ return new;
+}
+
+int piece_get_index(char l) {
+ for (size_t k = 0; k < sizeof(piece_lookup); k++)
+ if (piece_lookup[k] == l)
+ return k;
+
+ return -1;
+}
+
+char Board_atIndex(T self, int i, int j) {
+ assert(i >= 0 && i < 9 && j >= 0 && j < 9);
+ return self->board[8 * i + j];
+}
+
+/* GAME */
+
+game_T game_new(size_t moves_size) {
+ game_T game = ALLOC(sizeof(*game) + moves_size * sizeof(struct move_T));
+ game->moves_size = moves_size;
+ game->display_current = 0;
+ game->buffer_crnt = 0;
+ game->pass = 0;
+ game->fail = 0;
+ memset(game->buffer, '\0', BUFF_SIZE);
+ game->buffer[BUFF_SIZE] = '\0';
+
+ return game;
+}
+
+#undef G
diff --git a/src/display.c b/src/display.c
@@ -0,0 +1,189 @@
+#include <ctype.h>
+#include <stddef.h>
+#include <string.h>
+
+#include <pane.h>
+#include <utils.h>
+
+#include "anki.h"
+#include "display.h"
+#include "move.h"
+
+#define USTART L'\u2654'
+
+wchar_t convert(char l) {
+ if (l == ' ')
+ return ' ';
+ int p = piece_get_index(l);
+ return (p >= 0) ? p + USTART : L'!';
+}
+
+void board_display(widget_T widget) {
+ size_t i, j;
+ Pane_T pane = widget->pane;
+ boardInfo_T info = (boardInfo_T)widget->info;
+ boardStyle_T style = (boardStyle_T)widget->style;
+ game_T game = (game_T)widget->data->payload;
+ Board_T board = game->boards[game->display_current];
+
+ UNUSED(info);
+
+ int bg_color, fg_color;
+ size_t x_start = centerHorisontal(pane, 16);
+ size_t y_start = centerVertical(pane, 8);
+ for (i = 0; i < 8; i++) {
+ for (j = 0; j < 8; j++) {
+ bg_color = ((i + j) % 2) ? style->square_dark : style->square_light;
+ fg_color = style->piece;
+ tb_set_cell(x_start + 2 * i, y_start + j,
+ convert(Board_atIndex(board, j, i)), fg_color, bg_color);
+ tb_set_cell(x_start + 2 * i + 1, y_start + j, ' ', fg_color, bg_color);
+ }
+ }
+
+ /* display_grave(Board_grave(board, 'w'), y++, startx); */
+ /* display_grave(Board_grave(board, 'b'), y++, startx); */
+
+ if (style->annotation) {
+ char *files = "A B C D E F G H";
+ char *ranks = "87654321";
+ if (style->annotation & 8)
+ tb_print(x_start, y_start - 1, TB_GREEN, 0, files);
+ if (style->annotation & 2)
+ tb_print(x_start, y_start + 8, TB_GREEN, 0, files);
+ for (i = 0; i < 8; i++) {
+ if (style->annotation & 1)
+ tb_printf(x_start - 2, y_start + i, TB_GREEN, 0, "%c", ranks[i]);
+ if (style->annotation & 4)
+ tb_printf(x_start + 17, y_start + i, TB_GREEN, 0, "%c", ranks[i]);
+ }
+ }
+
+ /* draw_border(x_start - 1, y_start - 1, x_start + 16, y_start + 8, TB_RED);
+ */
+ if (style->border) {
+ tb_printf(x_start, y_start - 1, 0, style->border, " ");
+ tb_printf(x_start, y_start + 8, 0, style->border, " ");
+ for (i = y_start - 1; i < y_start + 9; i++) {
+ tb_printf(x_start - 2, i, 0, style->border, " ");
+ tb_printf(x_start + 16, i, 0, style->border, " ");
+ }
+ }
+
+ /* if (game->pass) */
+ /* tb_printf(x_start, y_start - 2, TB_GREEN, 0, "PASS"); */
+ /* else if (game->fail) */
+ /* tb_printf(x_start, y_start - 2, TB_GREEN, 0, "FAIL"); */
+ /* else */
+ /* tb_printf(x_start, y_start - 2, TB_GREEN, 0, " "); */
+
+ tb_printf(x_start, pane_y(pane) + pane_height(pane) - 1, TB_GREEN, 0, "%*s",
+ BUFF_SIZE, game->buffer);
+
+ tb_present();
+}
+
+int game_handleInput(data_T data, struct tb_event ev) {
+ game_T game = (game_T)data->payload;
+
+ /* if() */
+
+ switch (ev.key) {
+ case TB_KEY_ARROW_LEFT:
+ if (game->display_current > 0)
+ game->display_current--;
+ return 1;
+ case TB_KEY_ARROW_RIGHT:
+ if (game->display_current < game->move_current)
+ game->display_current++;
+ return 1;
+ case TB_KEY_ARROW_UP:
+ game->display_current = 0;
+ return 1;
+ case TB_KEY_ARROW_DOWN:
+ game->display_current = game->move_current;
+ return 1;
+ case TB_KEY_ENTER:
+ if (game->move_current == game->boards_size) {
+ anki_grade(game->pass);
+ game->review_next();
+ return 1;
+ }
+
+ if (game->buffer_crnt) {
+ if (strcmp(game->buffer,
+ Move_halfmove(game->moves, game->move_current)) != 0) {
+ game->move_fail = game->move_current + 1;
+ game->move_current = game->boards_size;
+ game->fail = 1;
+ } else {
+ game->display_current = ++game->move_current;
+
+ if (game->move_current == game->boards_size)
+ game->pass = 1;
+
+ memset(game->buffer, '\0', BUFF_SIZE);
+ game->buffer_crnt = 0;
+ }
+ }
+
+ return 1;
+ case TB_KEY_BACKSPACE:
+ case TB_KEY_BACKSPACE2:
+ CLAMP(game->buffer_crnt, 1, BUFF_SIZE + 1);
+ game->buffer[--game->buffer_crnt] = '\0';
+ return 1;
+ default:
+ if (!isalnum(ev.ch) && ev.ch != '+' && ev.ch != '#' && ev.ch != '-')
+ break;
+
+ if (game->buffer_crnt >= BUFF_SIZE)
+ break;
+
+ game->buffer[game->buffer_crnt++] = ev.ch;
+ return 1;
+ }
+
+ return 0;
+}
+
+void move_display(game_T game, movesStyle_T style, size_t move_index,
+ char *move, int x, int y) {
+ int color, background;
+ color = (game->move_start == move_index) ? TB_GREEN : style->foreground;
+ color = (game->move_fail == move_index) ? TB_RED : color;
+ background =
+ (game->display_current == move_index) ? style->active : style->background;
+ tb_printf(x, y, color, background, "%*s", style->padding, move);
+}
+
+void moves_display(widget_T widget) {
+ size_t i;
+ Pane_T pane = widget->pane;
+ movesInfo_T info = (movesInfo_T)widget->info;
+ movesStyle_T style = (movesStyle_T)widget->style;
+ game_T game = (game_T)widget->data->payload;
+
+ if (!widget->inited) {
+ widget->inited = 1;
+ info->list = widgetList_new(widget->pane, game->moves_size);
+ }
+
+ widgetList_T list = info->list;
+ int display_row =
+ ACLAMP(((int)game->display_current - 1) / 2, 0, ((int)game->moves_size));
+ widgetList_cacl(list, display_row);
+
+ pane_clear(pane, 0);
+ int x = centerHorisontal(pane, 4 * style->padding), y = pane_y(pane);
+ size_t index_end =
+ MIN(list->index_start + list->display_num, (game->move_current + 1) / 2);
+ for (i = list->index_start; i < index_end; i++, y++) {
+ move_T move = game->moves[i];
+ move_display(game, style, -1, move->turn, x, y);
+ move_display(game, style, i * 2 + 1, move->white, x + style->padding, y);
+ if (i * 2 + 2 <= game->move_current)
+ move_display(game, style, i * 2 + 2, move->black, x + 2 * style->padding,
+ y);
+ }
+}
diff --git a/src/main.c b/src/main.c
@@ -0,0 +1,234 @@
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define TB_IMPL
+#define TB_OPT_TRUECOLOR
+
+#include <except.h>
+#include <mem.h>
+#include <menu.h>
+#include <pane.h>
+#include <utils.h>
+#include <widgetList.h>
+
+#include "anki.h"
+#include "board.h"
+#include "display.h"
+#include "move.h"
+
+void set_selectDeck(void);
+void set_selectProfile(void);
+
+void start(void);
+void stop(void);
+
+struct menuInfo_T mainMenuInfo;
+struct menuStyle_T mainMenuStyle = {
+ .separator = "<------------->",
+ .padding = 2,
+ .spacing = 1,
+};
+struct widget_T mainMenu_widget = {
+ .pane = NULL,
+ .callback = pane_menu,
+ .title = "Main Menu",
+ .style = &mainMenuStyle,
+ .info = &mainMenuInfo,
+};
+
+menu_T anki_to_menu(const char **(*getter)(size_t *), menuItem_select_f select,
+ menu_back_f back) {
+ size_t i, size;
+ const char **profiles = getter(&size);
+
+ menu_T menu = menu_new(size, back);
+ for (i = 0; i < size; i++) {
+ menu->items[i].select_f = select;
+ menu->items[i].name = profiles[i];
+ }
+
+ return menu;
+}
+
+struct boardInfo_T boardInfo;
+struct boardStyle_T boardStyle = {
+ .square_dark = TB_GREEN,
+ .square_light = TB_WHITE,
+ .piece = TB_BLACK,
+ .border = 0,
+ .annotation = 3,
+};
+struct widget_T board_widget = {
+ .pane = NULL,
+ .callback = board_display,
+ .title = "Board",
+ .style = &boardStyle,
+ .info = &boardInfo,
+};
+
+#define MOVE_PADDING 6
+
+struct movesInfo_T movesInfo;
+struct movesStyle_T movesStyle = {
+ .padding = MOVE_PADDING,
+ .foreground = TB_WHITE,
+ .background = 0,
+ .active = TB_BLUE,
+};
+struct widget_T moves_widget = {
+ .pane = NULL,
+ .callback = moves_display,
+ .title = "Moves",
+ .style = &movesStyle,
+ .info = &movesInfo,
+};
+
+void title_callback(widget_T widget) {
+ game_T game = (game_T)widget->data->payload;
+ titleInfo_T info = (titleInfo_T)widget->info;
+ titleStyle_T style = (titleStyle_T)widget->style;
+
+ data_T data = data_new(card_name(game->card), NULL);
+ widget_setData(info->subwidget, data);
+ info->subwidget->pane = widget->pane;
+ info->subwidget->callback(info->subwidget);
+
+ UNUSED(style);
+}
+
+struct widget_T center_widget = {
+ .pane = NULL,
+ .callback = widgetCenter_print,
+ .title = "Title",
+};
+
+struct titleInfo_T titleInfo = {
+ .subwidget = ¢er_widget,
+};
+
+struct titleStyle_T titleStyle;
+
+struct widget_T title_widget = {
+ .pane = NULL,
+ .callback = title_callback,
+ .title = "Title",
+ .style = &titleStyle,
+ .info = &titleInfo,
+};
+
+void review_card() {
+ char *fen;
+ size_t moves_size, i;
+
+ card_T card = anki_current_card();
+ move_T *moves = Move_list(card_pgn(card), &moves_size);
+ game_T game = game_new(moves_size);
+
+ game->boards[0] = (fen = card_fen(card)) ? Board_from_FEN(fen) : Board_new();
+ for (i = 0; i < moves_size; i++) {
+ int base = i * 2;
+ game->boards[base + 1] = Board_play(game->boards[base], moves[i]->white);
+ game->boards[base + 2] =
+ Board_play(game->boards[base + 1], moves[i]->black);
+ }
+ game->boards_size =
+ moves_size * 2 - (strcmp(moves[moves_size - 1]->black, "*") == 0);
+
+ game->card = card;
+ game->review_next = review_card;
+ game->move_start = game->move_current = game->display_current =
+ atoi(card_start(card));
+ game->moves = moves;
+ /* game->pass = 1; */
+
+ data_T data = data_new(game, game_handleInput);
+ widget_setData(&board_widget, data);
+ widget_setData(&moves_widget, data);
+ widget_setData(&title_widget, data);
+
+ /* widgetCenter_print(widgetTitle, card_name(card)); */
+}
+
+void deck_selected(char *name, int ignore) {
+ UNUSED(ignore);
+ Pane_T *children1, *children2;
+
+ anki_load_deck(name);
+
+ pane_clear(MAIN, 1);
+ children1 = pane_vsplit(MAIN, 2, -5, 1);
+ children2 = pane_split(children1[1], 2, -(MOVE_PADDING * 3 + 4), 2);
+
+ review_card();
+ widget_activate(&title_widget, children1[0]);
+ widget_activate(&moves_widget, children2[0]);
+ widget_activate(&board_widget, children2[1]);
+}
+
+void profile_selected(char *name, int ignore) {
+ UNUSED(ignore);
+ anki_load_profile(name);
+ set_selectDeck();
+}
+
+void set_selectProfile() {
+ menu_T menu = anki_to_menu(anki_get_profiles, profile_selected, stop);
+ data_T data = data_new(menu, menu_hadleInput);
+ widget_setData(&mainMenu_widget, data);
+}
+
+void set_selectDeck() {
+ menu_T menu = anki_to_menu(anki_get_decks, deck_selected, set_selectProfile);
+ data_T data = data_new(menu, menu_hadleInput);
+ widget_setData(&mainMenu_widget, data);
+}
+
+int main(void) {
+ start();
+ {
+ set_selectProfile();
+ widget_activate(&mainMenu_widget, MAIN);
+ pane_event_loop();
+ }
+ stop();
+
+ return 0;
+}
+
+void stop(void) {
+ anki_stop();
+ pane_stop();
+ exit(1);
+}
+
+struct widget_T error_widget = {
+ .pane = NULL,
+ .callback = widgetCenter_print,
+ .title = "Error",
+};
+
+void start(void) {
+ struct tb_event ev;
+ char message[] = "Can't start anki, make sure it's running and try again";
+
+ pane_start(0);
+ data_T data = data_new(message, NULL);
+ widget_setData(&error_widget, data);
+ widget_activate(&error_widget, MAIN);
+
+ TRY { anki_start(); }
+ EXCEPT(ANKIE_CONNECT) {
+ error_widget.callback(&error_widget);
+ tb_poll_event(&ev);
+ if (ev.ch == 'q' || ev.key == TB_KEY_ESC)
+ stop();
+ start();
+ }
+ ELSE {
+ stop();
+ RERAISE;
+ }
+ END_TRY;
+}
diff --git a/src/move.c b/src/move.c
@@ -0,0 +1,47 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "mem.h"
+#include "move.h"
+
+#define MAX_PLAY 1024
+
+move_T Move_new(char *turn, char *white, char *black) {
+ move_T m;
+ NEW0(m);
+
+ strcpy(m->turn, turn);
+ strcpy(m->white, white);
+ strcpy(m->black, black);
+ return m;
+}
+
+move_T *Move_list(char *pgn, size_t *size) {
+ char *turn, *white, *black, *crnt;
+ char **test[] = {&turn, &white, &black};
+ size_t moves_size = 0, cnt = 0;
+
+ move_T *moves = malloc(MAX_PLAY * sizeof(*moves));
+ for (crnt = strtok(pgn, " "); crnt; crnt = strtok(NULL, " ")) {
+ *test[cnt++] = crnt;
+ if (cnt == 3) {
+ moves[moves_size++] = Move_new(turn, white, black);
+ *test[1] = NULL;
+ cnt = 0;
+ }
+ }
+
+ if (*test[1])
+ moves[moves_size++] = Move_new(turn, white, "");
+
+ if (size)
+ *size = moves_size;
+
+ return moves;
+}
+
+char *Move_halfmove(move_T *moves, int halfturn) {
+ return (halfturn % 2) ? moves[halfturn / 2]->black
+ : moves[halfturn / 2]->white;
+}