diff --git a/applications/external/chess/LICENSE b/applications/external/chess/LICENSE new file mode 100644 index 000000000..61361ecc7 --- /dev/null +++ b/applications/external/chess/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Struan Clark + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/applications/external/chess/application.fam b/applications/external/chess/application.fam new file mode 100644 index 000000000..066ad5053 --- /dev/null +++ b/applications/external/chess/application.fam @@ -0,0 +1,19 @@ +App( + appid="chess", + name="Chess", + apptype=FlipperAppType.EXTERNAL, + entry_point="flipchess_app", + requires=[ + "gui", + ], + stack_size=4 * 1024, + order=10, + fap_icon="flipchess_10px.png", + fap_icon_assets="icons", + fap_icon_assets_symbol="flipchess", + fap_category="Games", + fap_author="Struan Clark (xtruan)", + fap_weburl="https://github.com/xtruan/flipper-chess", + fap_version=(1, 8), + fap_description="Chess for Flipper", +) \ No newline at end of file diff --git a/applications/external/chess/chess/smallchesslib.h b/applications/external/chess/chess/smallchesslib.h new file mode 100644 index 000000000..61b0aecfd --- /dev/null +++ b/applications/external/chess/chess/smallchesslib.h @@ -0,0 +1,3492 @@ +#ifndef SMALLCHESSLIB_H +#define SMALLCHESSLIB_H + +/** + @file smallchesslib.h + + Small and simple single header C99 public domain chess library and engine. + + author: Miloslav Ciz (drummyfish) + license: CC0 1.0 (public domain) + found at https://creativecommons.org/publicdomain/zero/1.0/ + + additional waiver of all IP + version: 0.8d + + Default notation format for this library is a coordinate one, i.e. + + squarefrom squareto [promotedpiece] + + e.g.: e2e4 or A2A1q + + This work's goal is to never be encumbered by any exclusive intellectual + property rights. The work is therefore provided under CC0 1.0 + additional + WAIVER OF ALL INTELLECTUAL PROPERTY RIGHTS that waives the rest of + intellectual property rights not already waived by CC0 1.0. The WAIVER OF ALL + INTELLECTUAL PROPERTY RGHTS is as follows: + + Each contributor to this work agrees that they waive any exclusive rights, + including but not limited to copyright, patents, trademark, trade dress, + industrial design, plant varieties and trade secrets, to any and all ideas, + concepts, processes, discoveries, improvements and inventions conceived, + discovered, made, designed, researched or developed by the contributor either + solely or jointly with others, which relate to this work or result from this + work. Should any waiver of such right be judged legally invalid or + ineffective under applicable law, the contributor hereby grants to each + affected person a royalty-free, non transferable, non sublicensable, non + exclusive, irrevocable and unconditional license to this right. +*/ + +#include + +#ifndef SCL_DEBUG_AI +/** AI will print out a Newick-like tree of searched moves. */ +#define SCL_DEBUG_AI 0 +#endif + +/** + Maximum number of moves a chess piece can have (a queen in the middle of the + board). +*/ +#define SCL_CHESS_PIECE_MAX_MOVES 25 +#define SCL_BOARD_SQUARES 64 + +typedef uint8_t (*SCL_RandomFunction)(void); + +#if SCL_COUNT_EVALUATED_POSITIONS +uint32_t SCL_positionsEvaluated = 0; /**< If enabled by + SCL_COUNT_EVALUATED_POSITIONS, this + will increment with every + dynamically evaluated position (e.g. + when AI computes its move). */ +#endif + +#ifndef SCL_CALL_WDT_RESET +#define SCL_CALL_WDT_RESET \ + 0 /**< Option that should be enabled on some + Arduinos. If 1, call to watchdog timer + reset will be performed during dynamic + evaluation (without it if AI takes long the + program will reset). */ +#endif + +/** + Returns a pseudorandom byte. This function has a period 256 and returns each + possible byte value exactly once in the period. +*/ +uint8_t SCL_randomSimple(void); +void SCL_randomSimpleSeed(uint8_t seed); + +/** + Like SCL_randomSimple, but internally uses a 16 bit value, so the period is + 65536. +*/ +uint8_t SCL_randomBetter(void); +void SCL_randomBetterSeed(uint16_t seed); + +#ifndef SCL_EVALUATION_FUNCTION +/** + If defined, AI will always use the static evaluation function with this + name. This helps avoid pointers to functions and can be faster but the + function can't be changed at runtime. + */ +#define SCL_EVALUATION_FUNCTION +#undef SCL_EVALUATION_FUNCTION +#endif + +#ifndef SCL_960_CASTLING +/** + If set, chess 960 (Fisher random) castling will be considered by the library + rather than normal castling. 960 castling is slightly different (e.g. + requires the inital rook positions to be stored in board state). The + castling move is performed as "capturing own rook". + */ +#define SCL_960_CASTLING 0 +#endif + +#ifndef SCL_ALPHA_BETA +/** + Turns alpha-beta pruning (AI optimization) on or off. This can gain + performance and should normally be turned on. AI behavior should not + change at all. + */ +#define SCL_ALPHA_BETA 1 +#endif + +/** + A set of game squares as a bit array, each bit representing one game square. + Useful for representing e.g. possible moves. To easily iterate over the set + use provided macros (SCL_SQUARE_SET_ITERATE, ...). +*/ +typedef uint8_t SCL_SquareSet[8]; + +#define SCL_SQUARE_SET_EMPTY \ + { 0, 0, 0, 0, 0, 0, 0, 0 } + +void SCL_squareSetClear(SCL_SquareSet squareSet); +void SCL_squareSetAdd(SCL_SquareSet squareSet, uint8_t square); +uint8_t SCL_squareSetContains(const SCL_SquareSet squareSet, uint8_t square); +uint8_t SCL_squareSetSize(const SCL_SquareSet squareSet); +uint8_t SCL_squareSetEmpty(const SCL_SquareSet squareSet); + +/** + Returns a random square from a square set. +*/ +uint8_t SCL_squareSetGetRandom(const SCL_SquareSet squareSet, SCL_RandomFunction randFunc); + +#define SCL_SQUARE_SET_ITERATE_BEGIN(squareSet) \ + { \ + uint8_t iteratedSquare = 0; \ + uint8_t iterationEnd = 0; \ + for(int8_t _i = 0; _i < 8 && !iterationEnd; ++_i) { \ + uint8_t _row = squareSet[_i]; \ + if(_row == 0) { \ + iteratedSquare += 8; \ + continue; \ + } \ + \ + for(uint8_t _j = 0; _j < 8 && !iterationEnd; ++_j) { \ + if(_row & 0x01) { +/* + Between SCL_SQUARE_SET_ITERATE_BEGIN and _END iteratedSquare variable + represents the next square contained in the set. To break out of the + iteration set iterationEnd to 1. +*/ + +#define SCL_SQUARE_SET_ITERATE_END \ + } \ + _row >>= 1; \ + iteratedSquare++; \ + } \ + } /*for*/ \ + } + +#define SCL_SQUARE_SET_ITERATE(squareSet, command) \ + SCL_SQUARE_SET_ITERATE_BEGIN(squareSet){command} SCL_SQUARE_SET_ITERATE_END + +#define SCL_BOARD_STATE_SIZE 69 + +/** + Represents chess board state as a string in this format: + - First 64 characters represent the chess board (A1, B1, ... H8), each field + can be either a piece (PRNBKQprnbkq) or empty ('.'). I.e. the state looks + like this: + + 0 (A1) RNBQKBNR + PPPPPPPP + ........ + ........ + ........ + ........ + pppppppp + rnbqkbnr 63 (H8) + + - After this more bytes follow to represent global state, these are: + - 64: bits holding en-passant and castling related information: + - bits 0-3 (lsb): Column of the pawn that can, in current turn, be + taken by en-passant (0xF means no pawn can be taken this way). + - bit 4: Whether white is not prevented from short castling by previous + king or rook movement. + - bit 5: Same as 4, but for long castling. + - bit 6: Same as 4, but for black. + - bit 7: Same as 4, but for black and long castling. + - 65: Number saying the number of ply (half-moves) that have already been + played, also determining whose turn it currently is. + - 66: Move counter used in the 50 move rule, says the number of ply since + the last pawn move or capture. + - 67: Extra byte, left for storing additional info in variants. For normal + chess this byte should always be 0. + - 68: The last byte is always 0 to properly terminate the string in case + someone tries to print it. + - The state is designed so as to be simple and also print-friendly, i.e. you + can simply print it with line break after 8 characters to get a human + readable representation of the board. + + NOTE: there is a much more compact representation which however trades some + access speed which would affect the AI performance and isn't print friendly, + so we don't use it. In it each square takes 4 bits, using 15 out of 16 + possible values (empty square and W and B pieces including 2 types of pawns, + one "en-passant takeable"). Then only one extra byte needed is for castling + info (4 bits) and ply count (4 bits). +*/ +typedef char SCL_Board[SCL_BOARD_STATE_SIZE]; + +#define SCL_BOARD_ENPASSANT_CASTLE_BYTE 64 +#define SCL_BOARD_PLY_BYTE 65 +#define SCL_BOARD_MOVE_COUNT_BYTE 66 +#define SCL_BOARD_EXTRA_BYTE 67 + +#if SCL_960_CASTLING +#define _SCL_EXTRA_BYTE_VALUE (0 | (7 << 3)) // rooks on classic positions +#else +#define _SCL_EXTRA_BYTE_VALUE 0 +#endif + +#define SCL_BOARD_START_STATE \ + { \ + 82, 78, 66, 81, 75, 66, 78, 82, 80, 80, 80, 80, 80, 80, 80, 80, 46, 46, 46, 46, 46, 46, \ + 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, \ + 46, 46, 46, 46, 46, 112, 112, 112, 112, 112, 112, 112, 112, 114, 110, 98, 113, 107, \ + 98, 110, 114, (char)0xff, 0, 0, _SCL_EXTRA_BYTE_VALUE, 0 \ + } + +#define SCL_FEN_START "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" + +#define SCL_FEN_HORDE "ppp2ppp/pppppppp/pppppppp/pppppppp/3pp3/8/PPPPPPPP/RNBQKBNR w KQ - 0 1" + +#define SCL_FEN_UPSIDE_DOWN "RNBKQBNR/PPPPPPPP/8/8/8/8/pppppppp/rnbkqbnr w - - 0 1" + +#define SCL_FEN_PEASANT_REVOLT "1nn1k1n1/4p3/8/8/8/8/PPPPPPPP/4K3 w - - 0 1" + +#define SCL_FEN_ENDGAME "4k3/pppppppp/8/8/8/8/PPPPPPPP/4K3 w - - 0 1" + +#define SCL_FEN_KNIGHTS "N6n/1N4n1/2N2n2/3Nn3/k2nN2K/2n2N2/1n4N1/n6N w - - 0 1" + +/** + Holds an info required to undo a single move. +*/ +typedef struct { + uint8_t squareFrom; ///< start square + uint8_t squareTo; ///< target square + char enPassantCastle; ///< previous en passant/castle byte + char moveCount; ///< previous values of the move counter byte + uint8_t other; /**< lowest 7 bits: previous value of target square, + highest bit: if 1 then the move was promotion or + en passant */ +} SCL_MoveUndo; + +#define SCL_GAME_STATE_PLAYING 0x00 +#define SCL_GAME_STATE_WHITE_WIN 0x01 +#define SCL_GAME_STATE_BLACK_WIN 0x02 +#define SCL_GAME_STATE_DRAW 0x10 ///< further unspecified draw +#define SCL_GAME_STATE_DRAW_STALEMATE 0x11 ///< draw by stalemate +#define SCL_GAME_STATE_DRAW_REPETITION 0x12 ///< draw by repetition +#define SCL_GAME_STATE_DRAW_50 0x13 ///< draw by 50 move rule +#define SCL_GAME_STATE_DRAW_DEAD 0x14 ///< draw by dead position +#define SCL_GAME_STATE_END 0xff ///< end without known result + +/** + Converts square in common notation (e.g. 'c' 8) to square number. Only accepts + lowercase column. +*/ +#define SCL_SQUARE(colChar, rowInt) (((rowInt)-1) * 8 + ((colChar) - 'a')) +#define SCL_S(c, r) SCL_SQUARE(c, r) + +void SCL_boardInit(SCL_Board board); +void SCL_boardCopy(const SCL_Board boardFrom, SCL_Board boardTo); + +/** + Initializes given chess 960 (Fisher random) position. If SCL_960_CASTLING + is not set, castling will be disabled by this function. +*/ +void SCL_boardInit960(SCL_Board board, uint16_t positionNumber); + +void SCL_boardDisableCastling(SCL_Board board); + +uint32_t SCL_boardHash32(const SCL_Board board); + +#define SCL_PHASE_OPENING 0 +#define SCL_PHASE_MIDGAME 1 +#define SCL_PHASE_ENDGAME 2 + +/** + Estimates the game phase: opening, midgame or endgame. +*/ +uint8_t SCL_boardEstimatePhase(SCL_Board board); + +/** + Sets the board position. The input string should be 64 characters long zero + terminated C string representing the board as squares A1, A2, ..., H8 with + each char being either a piece (RKBKQPrkbkqp) or an empty square ('.'). +*/ +void SCL_boardSetPosition( + SCL_Board board, + const char* pieces, + uint8_t castlingEnPassant, + uint8_t moveCount, + uint8_t ply); + +uint8_t SCL_boardsDiffer(SCL_Board b1, SCL_Board b2); + +/** + Gets a random move on given board for the player whose move it is. +*/ +void SCL_boardRandomMove( + SCL_Board board, + SCL_RandomFunction randFunc, + uint8_t* squareFrom, + uint8_t* squareTo, + char* resultProm); + +#define SCL_FEN_MAX_LENGTH 90 + +/** + Converts a position to FEN (Forsyth–Edwards Notation) string. The string has + to have at least SCL_FEN_MAX_LENGTH bytes allocated to guarantee the + function won't write to unallocated memory. The string will be terminated by + 0 (this is included in SCL_FEN_MAX_LENGTH). The number of bytes written + (including the terminating 0) is returned. +*/ +uint8_t SCL_boardToFEN(SCL_Board board, char* string); + +/** + Loads a board from FEN (Forsyth–Edwards Notation) string. Returns 1 on + success, 0 otherwise. XFEN isn't supported fully but a start position in + chess960 can be loaded with this function. +*/ +uint8_t SCL_boardFromFEN(SCL_Board board, const char* string); + +/** + Returns an approximate/heuristic board rating as a number, 0 meaning equal + chances for both players, positive favoring white, negative favoring black. +*/ +typedef int16_t (*SCL_StaticEvaluationFunction)(SCL_Board); + +/* + NOTE: int8_t as a return value was tried for evaluation function, which would + be simpler, but it fails to capture important non-material position + differences counted in fractions of pawn values, hence we have to use int16_t. +*/ + +/** + Basic static evaluation function. WARNING: this function supposes a standard + chess game, for non-standard positions it may either not work well or even + crash the program. You should use a different function for non-standard games. +*/ +int16_t SCL_boardEvaluateStatic(SCL_Board board); + +/** + Dynamic evaluation function (search), i.e. unlike SCL_boardEvaluateStatic, + this one performs a recursive search for deeper positions to get a more + accurate score. Of course, this is much slower and hugely dependent on + baseDepth (you mostly want to keep this under 5). +*/ +int16_t SCL_boardEvaluateDynamic( + SCL_Board board, + uint8_t baseDepth, + uint8_t extensionExtraDepth, + SCL_StaticEvaluationFunction evalFunction); + +#define SCL_EVALUATION_MAX_SCORE 32600 // don't increase this, we need a margin + +/** + Checks if the board position is dead, i.e. mate is impossible (e.g. due to + insufficient material), which by the rules results in a draw. WARNING: This + function may fail to detect some dead positions as this is a non-trivial task. +*/ +uint8_t SCL_boardDead(SCL_Board board); + +/** + Tests whether given player is in check. +*/ +uint8_t SCL_boardCheck(SCL_Board board, uint8_t white); + +/** + Checks whether given move resets the move counter (used in the 50 move rule). +*/ +uint8_t SCL_boardMoveResetsCount(SCL_Board board, uint8_t squareFrom, uint8_t squareTo); + +uint8_t SCL_boardMate(SCL_Board board); + +/** + Performs a move on a board WITHOUT checking if the move is legal. Returns an + info with which the move can be undone. +*/ +SCL_MoveUndo + SCL_boardMakeMove(SCL_Board board, uint8_t squareFrom, uint8_t squareTo, char promotePiece); + +void SCL_boardUndoMove(SCL_Board board, SCL_MoveUndo moveUndo); + +/** + Checks if the game is over, i.e. the current player to move has no legal + moves, the game is in dead position etc. +*/ +uint8_t SCL_boardGameOver(SCL_Board board); + +/** + Checks if given move is legal. +*/ +uint8_t SCL_boardMoveIsLegal(SCL_Board board, uint8_t squareFrom, uint8_t squareTo); + +/** + Checks if the player to move has at least one legal move. +*/ +uint8_t SCL_boardMovePossible(SCL_Board board); + +#define SCL_POSITION_NORMAL 0x00 +#define SCL_POSITION_CHECK 0x01 +#define SCL_POSITION_MATE 0x02 +#define SCL_POSITION_STALEMATE 0x03 +#define SCL_POSITION_DEAD 0x04 + +uint8_t SCL_boardGetPosition(SCL_Board board); + +/** + Returns 1 if the square is attacked by player of given color. This is used to + examine checks, so for performance reasons the functions only checks whether + or not the square is attacked (not the number of attackers). +*/ +uint8_t SCL_boardSquareAttacked(SCL_Board board, uint8_t square, uint8_t byWhite); + +/** + Gets pseudo moves of a piece: all possible moves WITHOUT eliminating moves + that lead to own check. To get only legal moves use SCL_boardGetMoves. +*/ +void SCL_boardGetPseudoMoves( + SCL_Board board, + uint8_t pieceSquare, + uint8_t checkCastling, + SCL_SquareSet result); + +/** + Gets all legal moves of given piece. +*/ +void SCL_boardGetMoves(SCL_Board board, uint8_t pieceSquare, SCL_SquareSet result); + +static inline uint8_t SCL_boardWhitesTurn(SCL_Board board); + +static inline uint8_t SCL_pieceIsWhite(char piece); +static inline uint8_t SCL_squareIsWhite(uint8_t square); +char SCL_pieceToColor(uint8_t piece, uint8_t toWhite); + +/** + Converts square coordinates to square number. Each coordinate must be a number + <1,8>. Validity of the coordinates is NOT checked. +*/ +static inline uint8_t SCL_coordsToSquare(uint8_t row, uint8_t column); + +#ifndef SCL_VALUE_PAWN +#define SCL_VALUE_PAWN 256 +#endif + +#ifndef SCL_VALUE_KNIGHT +#define SCL_VALUE_KNIGHT 768 +#endif + +#ifndef SCL_VALUE_BISHOP +#define SCL_VALUE_BISHOP 800 +#endif + +#ifndef SCL_VALUE_ROOK +#define SCL_VALUE_ROOK 1280 +#endif + +#ifndef SCL_VALUE_QUEEN +#define SCL_VALUE_QUEEN 2304 +#endif + +#ifndef SCL_VALUE_KING +#define SCL_VALUE_KING 0 +#endif + +#define SCL_ENDGAME_MATERIAL_LIMIT \ + (2 * \ + (SCL_VALUE_PAWN * 4 + SCL_VALUE_QUEEN + SCL_VALUE_KING + SCL_VALUE_ROOK + SCL_VALUE_KNIGHT)) + +#define SCL_START_MATERIAL \ + (16 * SCL_VALUE_PAWN + 4 * SCL_VALUE_ROOK + 4 * SCL_VALUE_KNIGHT + 4 * SCL_VALUE_BISHOP + \ + 2 * SCL_VALUE_QUEEN + 2 * SCL_VALUE_KING) + +#ifndef SCL_RECORD_MAX_LENGTH +#define SCL_RECORD_MAX_LENGTH 256 +#endif + +#define SCL_RECORD_MAX_SIZE (SCL_RECORD_MAX_LENGTH * 2) + +/** + Records a single chess game. The format is following: + + Each record item consists of 2 bytes which record a single move (ply): + + abxxxxxx cdyyyyyy + + xxxxxx Start square of the move, counted as A0, A1, ... + yyyyyy End square of the move in the same format as the start square. + ab 00 means this move isn't the last move of the game, other possible + values are 01: white wins, 10: black wins, 11: draw or end for + other reasons. + cd In case of pawn promotion move this encodes the promoted piece as + 00: queen, 01: rook, 10: bishop, 11: knight (pawn isn't allowed by + chess rules). + + Every record should be ended by an ending move (ab != 00), empty record should + have one move where xxxxxx == yyyyyy == 0 and ab == 11. +*/ +typedef uint8_t SCL_Record[SCL_RECORD_MAX_SIZE]; + +#define SCL_RECORD_CONT 0x00 +#define SCL_RECORD_W_WIN 0x40 +#define SCL_RECORD_B_WIN 0x80 +#define SCL_RECORD_END 0xc0 + +#define SCL_RECORD_PROM_Q 0x00 +#define SCL_RECORD_PROM_R 0x40 +#define SCL_RECORD_PROM_B 0x80 +#define SCL_RECORD_PROM_N 0xc0 + +#define SCL_RECORD_ITEM(s0, s1, p, e) ((e) | (s0)), ((p) | (s1)) + +void SCL_recordInit(SCL_Record r); + +void SCL_recordCopy(SCL_Record recordFrom, SCL_Record recordTo); + +/** + Represents a complete game of chess (or a variant with different staring + position). This struct along with associated functions allows to easily + implement a chess game that allows undoing moves, detecting draws, recording + the moves etc. On platforms with extremely little RAM one can reduce + SCL_RECORD_MAX_LENGTH to reduce the size of this struct (which will however + possibly limit how many moves can be undone). +*/ +typedef struct { + SCL_Board board; + SCL_Record record; /**< Holds the game record. This record is here + firstly because games are usually recorded and + secondly this allows undoing moves up to the + beginning of the game. This infinite undoing will + only work as long as the record is able to hold + the whole game; if the record is full, undoing is + no longet possible. */ + uint16_t state; + uint16_t ply; ///< ply count (board ply counter is only 8 bit) + + uint32_t prevMoves[14]; ///< stores last moves, for repetition detection + + const char* startState; /**< Optional pointer to the starting board state. + If this is null, standard chess start position is + assumed. This is needed for undoing moves with + game record. */ +} SCL_Game; + +/** + Initializes a new chess game. The startState parameter is optional and allows + for setting up chess variants that differ by starting positions, setting this + to 0 will assume traditional starting position. WARNING: if startState is + provided, the pointed to board mustn't be deallocated afterwards, the string + is not internally copied (for memory saving reasons). +*/ +void SCL_gameInit(SCL_Game* game, const SCL_Board startState); + +void SCL_gameMakeMove(SCL_Game* game, uint8_t squareFrom, uint8_t squareTo, char promoteTo); + +uint8_t SCL_gameUndoMove(SCL_Game* game); + +/** + Gets a move which if played now would cause a draw by repetition. Returns 1 + if such move exists, 0 otherwise. The results parameters can be set to 0 in + which case they will be ignored and only the existence of a draw move will be + tested. +*/ +uint8_t SCL_gameGetRepetiotionMove(SCL_Game* game, uint8_t* squareFrom, uint8_t* squareTo); + +/** + Leads a game record from PGN string. The function will probably not strictly + adhere to the PGN input format, but should accept most sanely written PGN + strings. +*/ +void SCL_recordFromPGN(SCL_Record r, const char* pgn); + +uint16_t SCL_recordLength(const SCL_Record r); + +/** + Gets the move out of a game record, returns the end state of the move + (SCL_RECORD_CONT, SCL_RECORD_END etc.) +*/ +uint8_t SCL_recordGetMove( + const SCL_Record r, + uint16_t index, + uint8_t* squareFrom, + uint8_t* squareTo, + char* promotedPiece); + +/** + Adds another move to the game record. Terminating the record is handled so + that the last move is always marked with end flag, endState is here to only + indicate possible game result (otherwise pass SCL_RECORD_CONT). Returns 1 if + the item was added, otherwise 0 (replay was already of maximum size). +*/ +uint8_t SCL_recordAdd( + SCL_Record r, + uint8_t squareFrom, + uint8_t squareTo, + char promotePiece, + uint8_t endState); + +/** + Removes the last move from the record, returns 1 if the replay is non-empty + after the removal, otherwise 0. +*/ +uint8_t SCL_recordRemoveLast(SCL_Record r); + +/** + Applies given number of half-moves (ply) to a given board (the board is + automatically initialized at the beginning). +*/ +void SCL_recordApply(const SCL_Record r, SCL_Board b, uint16_t moves); + +int16_t SCL_pieceValue(char piece); +int16_t SCL_pieceValuePositive(char piece); + +#define SCL_PRINT_FORMAT_NONE 0 +#define SCL_PRINT_FORMAT_NORMAL 1 +#define SCL_PRINT_FORMAT_COMPACT 2 +#define SCL_PRINT_FORMAT_UTF8 3 +#define SCL_PRINT_FORMAT_COMPACT_UTF8 4 + +/** + Gets the best move for the currently moving player as computed by AI. The + return value is the value of the move (with the same semantics as the value + of an evaluation function). baseDepth is depth in plys to which all moves will + be checked. If baseDepth 0 is passed, the function makes a random move and + returns the evaluation of the board. extensionExtraDepth is extra depth for + checking specific situations like exchanges and checks. endgameExtraDepth is + extra depth which is added to baseDepth in the endgame. If the randomness + function is 0, AI will always make the first best move it finds, if it is + not 0 and randomness is 0, AI will randomly pick between the equally best + moves, if it is not 0 and randomness is positive, AI will randomly choose + between best moves with some bias (may not pick the best rated move). +*/ +int16_t SCL_getAIMove( + SCL_Board board, + uint8_t baseDepth, + uint8_t extensionExtraDepth, + uint8_t endgameExtraDepth, + SCL_StaticEvaluationFunction evalFunc, + SCL_RandomFunction randFunc, + uint8_t randomness, + uint8_t repetitionMoveFrom, + uint8_t repetitionMoveTo, + uint8_t* resultFrom, + uint8_t* resultTo, + char* resultProm); + +/** + Function that prints out a single character. This is passed to printing + functions. +*/ +typedef void (*SCL_PutCharFunction)(char); + +/** + Prints given chessboard using given format and an abstract printing function. +*/ +void SCL_printBoard( + SCL_Board board, + SCL_PutCharFunction putCharFunc, + SCL_SquareSet highlightSquares, + uint8_t selectSquare, + uint8_t format, + uint8_t offset, + uint8_t labels, + uint8_t blackDown); + +void SCL_printBoardSimple( + SCL_Board board, + SCL_PutCharFunction putCharFunc, + uint8_t selectSquare, + uint8_t format); + +void SCL_printSquareUTF8(uint8_t square, SCL_PutCharFunction putCharFunc); +void SCL_printPGN(SCL_Record r, SCL_PutCharFunction putCharFunc, SCL_Board initialState); + +/** + Reads a move from string (the notation format is described at the top of this + file). The function is safe as long as the string is 0 terminated. Returns 1 + on success or 0 on fail (invalid move string). +*/ +uint8_t SCL_stringToMove( + const char* moveString, + uint8_t* resultFrom, + uint8_t* resultTo, + char* resultPromotion); + +char* SCL_moveToString(SCL_Board board, uint8_t s0, uint8_t s1, char promotion, char* string); + +/** + Function used in drawing, it is called to draw the next pixel. The first + parameter is the pixel color, the second one if the sequential number of the + pixel. +*/ +typedef void (*SCL_PutPixelFunction)(uint8_t, uint16_t); + +#define SCL_BOARD_PICTURE_WIDTH 64 + +/** + Draws a simple 1bit 64x64 pixels board using a provided abstract function for + drawing pixels. The function renders from top left to bottom right, i.e. no + frame buffer is required. +*/ +void SCL_drawBoard( + SCL_Board board, + SCL_PutPixelFunction putPixel, + uint8_t selectedSquare, + SCL_SquareSet highlightSquares, + uint8_t blackDown); + +/** + Converts square number to string representation (e.g. "d2"). This function + will modify exactly the first two bytes of the provided string. +*/ +static inline char* SCL_squareToString(uint8_t square, char* string); + +/** + Converts a string, such as "A1" or "b4", to square number. The string must + start with a letter (lower or upper case) and be followed by a number <1,8>. + Validity of the string is NOT checked. +*/ +uint8_t SCL_stringToSquare(const char* square); + +//============================================================================= +// privates: + +#define SCL_UNUSED(v) (void)(v) + +uint8_t SCL_currentRandom8 = 0; + +uint16_t SCL_currentRandom16 = 0; + +void SCL_randomSimpleSeed(uint8_t seed) { + SCL_currentRandom8 = seed; +} + +uint8_t SCL_randomSimple(void) { + SCL_currentRandom8 *= 13; + SCL_currentRandom8 += 7; + return SCL_currentRandom8; +} + +uint8_t SCL_randomBetter(void) { + SCL_currentRandom16 *= 13; + SCL_currentRandom16 += 7; + return (SCL_currentRandom16 % 256) ^ (SCL_currentRandom16 / 256); +} + +void SCL_randomBetterSeed(uint16_t seed) { + SCL_currentRandom16 = seed; +} + +void SCL_squareSetClear(SCL_SquareSet squareSet) { + for(uint8_t i = 0; i < 8; ++i) squareSet[i] = 0; +} + +uint8_t SCL_stringToSquare(const char* square) { + return (square[1] - '1') * 8 + + (square[0] - ((square[0] >= 'A' && square[0] <= 'Z') ? 'A' : 'a')); +} + +char* SCL_moveToString(SCL_Board board, uint8_t s0, uint8_t s1, char promotion, char* string) { + char* result = string; + + SCL_squareToString(s0, string); + string += 2; + string = SCL_squareToString(s1, string); + string += 2; + + char c = board[s0]; + + if(c == 'p' || c == 'P') { + uint8_t rank = s1 / 8; + + if(rank == 0 || rank == 7) { + *string = promotion; + string++; + } + } + + *string = 0; + + return result; +} + +uint8_t SCL_boardWhitesTurn(SCL_Board board) { + return (board[SCL_BOARD_PLY_BYTE] % 2) == 0; +} + +uint8_t SCL_coordsToSquare(uint8_t row, uint8_t column) { + return row * 8 + column; +} + +uint8_t SCL_pieceIsWhite(char piece) { + return piece < 'a'; +} + +char* SCL_squareToString(uint8_t square, char* string) { + string[0] = 'a' + square % 8; + string[1] = '1' + square / 8; + + return string; +} + +uint8_t SCL_squareIsWhite(uint8_t square) { + return (square % 2) != ((square / 8) % 2); +} + +char SCL_pieceToColor(uint8_t piece, uint8_t toWhite) { + return (SCL_pieceIsWhite(piece) == toWhite) ? piece : (piece + (toWhite ? -32 : 32)); +} + +/** + Records the rook starting positions in the board state. This is required in + chess 960 in order to be able to correctly perform castling (castling rights + knowledge isn't enough as one rook might have moved to the other side and we + wouldn't know which one can castle and which not). +*/ +void _SCL_board960RememberRookPositions(SCL_Board board) { + uint8_t pos = 0; + uint8_t rooks = 2; + + while(pos < 8 && rooks != 0) { + if(board[pos] == 'R') { + board[SCL_BOARD_EXTRA_BYTE] = rooks == 2 ? pos : + (board[SCL_BOARD_EXTRA_BYTE] | (pos << 3)); + + rooks--; + } + + pos++; + } +} + +void SCL_boardInit(SCL_Board board) { + /* + We might use SCL_BOARD_START_STATE and copy it to the board, but that might + waste RAM on Arduino, so we init the board by code. + */ + + char* b = board; + + *b = 'R'; + b++; + *b = 'N'; + b++; + *b = 'B'; + b++; + *b = 'Q'; + b++; + *b = 'K'; + b++; + *b = 'B'; + b++; + *b = 'N'; + b++; + *b = 'R'; + b++; + + char* b2 = board + 48; + + for(uint8_t i = 0; i < 8; ++i, b++, b2++) { + *b = 'P'; + *b2 = 'p'; + } + + for(uint8_t i = 0; i < 32; ++i, b++) *b = '.'; + + b += 8; + + *b = 'r'; + b++; + *b = 'n'; + b++; + *b = 'b'; + b++; + *b = 'q'; + b++; + *b = 'k'; + b++; + *b = 'b'; + b++; + *b = 'n'; + b++; + *b = 'r'; + b++; + + for(uint8_t i = 0; i < SCL_BOARD_STATE_SIZE - SCL_BOARD_SQUARES; ++i, ++b) *b = 0; + + board[SCL_BOARD_ENPASSANT_CASTLE_BYTE] = (char)0xff; + +#if SCL_960_CASTLING + _SCL_board960RememberRookPositions(board); +#endif +} + +void _SCL_boardPlaceOnNthAvailable(SCL_Board board, uint8_t pos, char piece) { + char* c = board; + + while(1) { + if(*c == '.') { + if(pos == 0) break; + + pos--; + } + + c++; + } + + *c = piece; +} + +void SCL_boardInit960(SCL_Board board, uint16_t positionNumber) { + SCL_Board b; + + SCL_boardInit(b); + + for(uint8_t i = 0; i < SCL_BOARD_STATE_SIZE; ++i) + board[i] = ((i >= 8 && i < 56) || i >= 64) ? b[i] : '.'; + + uint8_t helper = positionNumber % 16; + + board[(helper / 4) * 2] = 'B'; + board[1 + (helper % 4) * 2] = 'B'; + + helper = positionNumber / 16; + + // maybe there's a simpler way :) + + _SCL_boardPlaceOnNthAvailable(board, helper % 6, 'Q'); + _SCL_boardPlaceOnNthAvailable(board, 0, helper <= 23 ? 'N' : 'R'); + + _SCL_boardPlaceOnNthAvailable( + board, 0, (helper >= 7 && helper <= 23) ? 'R' : (helper > 41 ? 'K' : 'N')); + + _SCL_boardPlaceOnNthAvailable( + board, + 0, + (helper <= 5 || helper >= 54) ? + 'R' : + (((helper >= 12 && helper <= 23) || (helper >= 30 && helper <= 41)) ? 'K' : 'N')); + + _SCL_boardPlaceOnNthAvailable( + board, + 0, + (helper <= 11 || (helper <= 29 && helper >= 24)) ? + 'K' : + (((helper >= 18 && helper <= 23) || (helper >= 36 && helper <= 41) || + (helper >= 48 && helper <= 53)) ? + 'R' : + 'N')); + + uint8_t rooks = 0; + + for(uint8_t i = 0; i < 8; ++i) + if(board[i] == 'R') rooks++; + + _SCL_boardPlaceOnNthAvailable(board, 0, rooks == 2 ? 'N' : 'R'); + + for(uint8_t i = 0; i < 8; ++i) board[56 + i] = SCL_pieceToColor(board[i], 0); + +#if SCL_960_CASTLING + _SCL_board960RememberRookPositions(board); +#else + SCL_boardDisableCastling(board); +#endif +} + +uint8_t SCL_boardsDiffer(SCL_Board b1, SCL_Board b2) { + const char *p1 = b1, *p2 = b2; + + while(p1 < b1 + SCL_BOARD_STATE_SIZE) { + if(*p1 != *p2) return 1; + + p1++; + p2++; + } + + return 0; +} + +void SCL_recordInit(SCL_Record r) { + r[0] = 0 | SCL_RECORD_END; + r[1] = 0; +} + +void SCL_recordFromPGN(SCL_Record r, const char* pgn) { + SCL_Board board; + + SCL_boardInit(board); + + SCL_recordInit(r); + + uint8_t state = 0; + uint8_t evenMove = 0; + + while(*pgn != 0) { + switch(state) { + case 0: // skipping tags and spaces, outside [] + if(*pgn == '1') + state = 2; + else if(*pgn == '[') + state = 1; + + break; + + case 1: // skipping tags and spaces, inside [] + if(*pgn == ']') state = 0; + + break; + + case 2: // reading move number + if(*pgn == '{') + state = 3; + else if((*pgn >= 'a' && *pgn <= 'h') || (*pgn >= 'A' && *pgn <= 'Z')) { + state = 4; + pgn--; + } + + break; + + case 3: // initial comment + if(*pgn == '}') state = 2; + + break; + + case 4: // reading move + { + char piece = 'p'; + char promoteTo = 'q'; + uint8_t castle = 0; + uint8_t promotion = 0; + + int8_t coords[4]; + + uint8_t ranks = 0, files = 0; + + for(uint8_t i = 0; i < 4; ++i) coords[i] = -1; + + while(*pgn != ' ' && *pgn != '\n' && *pgn != '\t' && *pgn != '{' && *pgn != 0) { + if(*pgn == '=') promotion = 1; + if(*pgn == 'O' || *pgn == '0') castle++; + if(*pgn >= 'A' && *pgn <= 'Z') { + if(promotion) + promoteTo = *pgn; + else + piece = *pgn; + } else if(*pgn >= 'a' && *pgn <= 'h') { + coords[files * 2] = *pgn - 'a'; + files++; + } else if(*pgn >= '1' && *pgn <= '8') { + coords[1 + ranks * 2] = *pgn - '1'; + ranks++; + } + + pgn++; + } + + if(castle) { + piece = 'K'; + + coords[0] = 4; + coords[1] = 0; + coords[2] = castle < 3 ? 6 : 2; + coords[3] = 0; + + if(evenMove) { + coords[1] = 7; + coords[3] = 7; + } + } + + piece = SCL_pieceToColor(piece, evenMove == 0); + + if(coords[2] < 0) { + coords[2] = coords[0]; + coords[0] = -1; + } + + if(coords[3] < 0) { + coords[3] = coords[1]; + coords[1] = -1; + } + + uint8_t squareTo = coords[3] * 8 + coords[2]; + + if(coords[0] < 0 || coords[1] < 0) { + // without complete starting coords we have to find the piece + + for(int i = 0; i < SCL_BOARD_SQUARES; ++i) + if(board[i] == piece) { + SCL_SquareSet s; + + SCL_squareSetClear(s); + + SCL_boardGetMoves(board, i, s); + + if(SCL_squareSetContains(s, squareTo) && + (coords[0] < 0 || coords[0] == i % 8) && + (coords[1] < 0 || coords[1] == i / 8)) { + coords[0] = i % 8; + coords[1] = i / 8; + break; + } + } + } + + uint8_t squareFrom = coords[1] * 8 + coords[0]; + + SCL_boardMakeMove(board, squareFrom, squareTo, promoteTo); + + // for some reason tcc bugs here, the above line sets squareFrom to 0 lol + // can be fixed with doing "squareFrom = coords[1] * 8 + coords[0];" again + + SCL_recordAdd(r, squareFrom, squareTo, promoteTo, SCL_RECORD_CONT); + + while(*pgn == ' ' || *pgn == '\n' || *pgn == '\t' || *pgn == '{') { + if(*pgn == '{') + while(*pgn != '}') pgn++; + + pgn++; + } + + if(*pgn == 0) return; + + pgn--; + + if(evenMove) state = 2; + + evenMove = !evenMove; + + break; + } + + default: + break; + } + + pgn++; + } +} + +uint16_t SCL_recordLength(const SCL_Record r) { + if((r[0] & 0x3f) == (r[1] & 0x3f)) // empty record that's only terminator + return 0; + + uint16_t result = 0; + + while((r[result] & 0xc0) == 0) result += 2; + + return (result / 2) + 1; +} + +uint8_t SCL_recordGetMove( + const SCL_Record r, + uint16_t index, + uint8_t* squareFrom, + uint8_t* squareTo, + char* promotedPiece) { + index *= 2; + + uint8_t b = r[index]; + + *squareFrom = b & 0x3f; + uint8_t result = b & 0xc0; + + index++; + + b = r[index]; + + *squareTo = b & 0x3f; + + b &= 0xc0; + + switch(b) { + case SCL_RECORD_PROM_Q: + *promotedPiece = 'q'; + break; + case SCL_RECORD_PROM_R: + *promotedPiece = 'r'; + break; + case SCL_RECORD_PROM_B: + *promotedPiece = 'b'; + break; + case SCL_RECORD_PROM_N: + default: + *promotedPiece = 'n'; + break; + } + + return result; +} + +uint8_t SCL_recordAdd( + SCL_Record r, + uint8_t squareFrom, + uint8_t squareTo, + char promotePiece, + uint8_t endState) { + uint16_t l = SCL_recordLength(r); + + if(l >= SCL_RECORD_MAX_LENGTH) return 0; + + l *= 2; + + if(l != 0) r[l - 2] &= 0x3f; // remove the end flag from previous item + + if(endState == SCL_RECORD_CONT) endState = SCL_RECORD_END; + + r[l] = squareFrom | endState; + + uint8_t p; + + switch(promotePiece) { + case 'n': + case 'N': + p = SCL_RECORD_PROM_N; + break; + case 'b': + case 'B': + p = SCL_RECORD_PROM_B; + break; + case 'r': + case 'R': + p = SCL_RECORD_PROM_R; + break; + case 'q': + case 'Q': + default: + p = SCL_RECORD_PROM_Q; + break; + } + + l++; + + r[l] = squareTo | p; + + return 1; +} + +uint8_t SCL_recordRemoveLast(SCL_Record r) { + uint16_t l = SCL_recordLength(r); + + if(l == 0) return 0; + + if(l == 1) + SCL_recordInit(r); + else { + l = (l - 2) * 2; + + r[l] = (r[l] & 0x3f) | SCL_RECORD_END; + } + + return 1; +} + +void SCL_recordApply(const SCL_Record r, SCL_Board b, uint16_t moves) { + SCL_boardInit(b); + + uint16_t l = SCL_recordLength(r); + + if(moves > l) moves = l; + + for(uint16_t i = 0; i < moves; ++i) { + uint8_t s0, s1; + char p; + + SCL_recordGetMove(r, i, &s0, &s1, &p); + SCL_boardMakeMove(b, s0, s1, p); + } +} + +void SCL_boardUndoMove(SCL_Board board, SCL_MoveUndo moveUndo) { +#if SCL_960_CASTLING + char squareToNow = board[moveUndo.squareTo]; +#endif + + board[moveUndo.squareFrom] = board[moveUndo.squareTo]; + board[moveUndo.squareTo] = moveUndo.other & 0x7f; + board[SCL_BOARD_PLY_BYTE]--; + board[SCL_BOARD_ENPASSANT_CASTLE_BYTE] = moveUndo.enPassantCastle; + board[SCL_BOARD_MOVE_COUNT_BYTE] = moveUndo.moveCount; + + if(moveUndo.other & 0x80) { + moveUndo.squareTo /= 8; + + if(moveUndo.squareTo == 0 || moveUndo.squareTo == 7) + board[moveUndo.squareFrom] = SCL_pieceIsWhite(board[moveUndo.squareFrom]) ? 'P' : 'p'; + // ^ was promotion + else + board[(moveUndo.squareFrom / 8) * 8 + (moveUndo.enPassantCastle & 0x0f)] = + (board[moveUndo.squareFrom] == 'P') ? 'p' : 'P'; // was en passant + } +#if !SCL_960_CASTLING + else if( + board[moveUndo.squareFrom] == 'k' && // black castling + moveUndo.squareFrom == 60) { + if(moveUndo.squareTo == 58) { + board[59] = '.'; + board[56] = 'r'; + } else if(moveUndo.squareTo == 62) { + board[61] = '.'; + board[63] = 'r'; + } + } else if( + board[moveUndo.squareFrom] == 'K' && // white castling + moveUndo.squareFrom == 4) { + if(moveUndo.squareTo == 2) { + board[3] = '.'; + board[0] = 'R'; + } else if(moveUndo.squareTo == 6) { + board[5] = '.'; + board[7] = 'R'; + } + } +#else // 960 castling + else if( + ((moveUndo.other & 0x7f) == 'r') && // black castling + (squareToNow == '.' || !SCL_pieceIsWhite(squareToNow))) { + board[moveUndo.squareTo < moveUndo.squareFrom ? 59 : 61] = '.'; + board[moveUndo.squareTo < moveUndo.squareFrom ? 58 : 62] = '.'; + + board[moveUndo.squareFrom] = 'k'; + board[moveUndo.squareTo] = 'r'; + } else if( + ((moveUndo.other & 0x7f) == 'R') && // white castling + (squareToNow == '.' || SCL_pieceIsWhite(squareToNow))) { + board[moveUndo.squareTo < moveUndo.squareFrom ? 3 : 5] = '.'; + board[moveUndo.squareTo < moveUndo.squareFrom ? 2 : 6] = '.'; + + board[moveUndo.squareFrom] = 'K'; + board[moveUndo.squareTo] = 'R'; + } +#endif +} + +/** + Potentially disables castling rights according to whether something moved from + or to a square with a rook. +*/ +void _SCL_handleRookActivity(SCL_Board board, uint8_t rookSquare) { +#if !SCL_960_CASTLING + switch(rookSquare) { + case 0: + board[SCL_BOARD_ENPASSANT_CASTLE_BYTE] &= (uint8_t)~0x20; + break; + case 7: + board[SCL_BOARD_ENPASSANT_CASTLE_BYTE] &= (uint8_t)~0x10; + break; + case 56: + board[SCL_BOARD_ENPASSANT_CASTLE_BYTE] &= (uint8_t)~0x80; + break; + case 63: + board[SCL_BOARD_ENPASSANT_CASTLE_BYTE] &= (uint8_t)~0x40; + break; + default: + break; + } +#else // 960 castling + if(rookSquare == (board[SCL_BOARD_EXTRA_BYTE] & 0x07)) + board[SCL_BOARD_ENPASSANT_CASTLE_BYTE] &= (uint8_t)~0x20; + else if(rookSquare == (board[SCL_BOARD_EXTRA_BYTE] >> 3)) + board[SCL_BOARD_ENPASSANT_CASTLE_BYTE] &= (uint8_t)~0x10; + else if(rookSquare == 56 + (board[SCL_BOARD_EXTRA_BYTE] & 0x07)) + board[SCL_BOARD_ENPASSANT_CASTLE_BYTE] &= (uint8_t)~0x80; + else if(rookSquare == 56 + (board[SCL_BOARD_EXTRA_BYTE] >> 3)) + board[SCL_BOARD_ENPASSANT_CASTLE_BYTE] &= (uint8_t)~0x40; +#endif +} + +SCL_MoveUndo + SCL_boardMakeMove(SCL_Board board, uint8_t squareFrom, uint8_t squareTo, char promotePiece) { + char s = board[squareFrom]; + + SCL_MoveUndo moveUndo; + + moveUndo.squareFrom = squareFrom; + moveUndo.squareTo = squareTo; + moveUndo.moveCount = board[SCL_BOARD_MOVE_COUNT_BYTE]; + moveUndo.enPassantCastle = board[SCL_BOARD_ENPASSANT_CASTLE_BYTE]; + moveUndo.other = board[squareTo]; + + // reset the en-passant state + board[SCL_BOARD_ENPASSANT_CASTLE_BYTE] |= 0x0f; + + if(SCL_boardMoveResetsCount(board, squareFrom, squareTo)) + board[SCL_BOARD_MOVE_COUNT_BYTE] = 0; + else + board[SCL_BOARD_MOVE_COUNT_BYTE]++; + +#if SCL_960_CASTLING + uint8_t castled = 0; +#endif + + if((s == 'k') || (s == 'K')) { +#if !SCL_960_CASTLING + if((squareFrom == 4) || (squareFrom == 60)) // check castling + { + int8_t difference = squareTo - squareFrom; + + char rook = SCL_pieceToColor('r', SCL_pieceIsWhite(s)); + + if(difference == 2) // short + { + board[squareTo - 1] = rook; + board[squareTo + 1] = '.'; + } else if(difference == -2) // long + { + board[squareTo - 2] = '.'; + board[squareTo + 1] = rook; + } + } +#else // 960 castling + uint8_t isWhite = SCL_pieceIsWhite(s); + char rook = SCL_pieceToColor('r', isWhite); + + if(board[squareTo] == rook) { + castled = 1; + + board[squareFrom] = '.'; + board[squareTo] = '.'; + + if(squareTo > squareFrom) // short + { + board[isWhite ? 6 : (56 + 6)] = s; + board[isWhite ? 5 : (56 + 5)] = rook; + } else // long + { + board[isWhite ? 2 : (56 + 2)] = s; + board[isWhite ? 3 : (56 + 3)] = rook; + } + } +#endif + + // after king move disable castling + board[SCL_BOARD_ENPASSANT_CASTLE_BYTE] &= ~(0x03 << ((s == 'K') ? 4 : 6)); + } else if((s == 'p') || (s == 'P')) { + uint8_t row = squareTo / 8; + + int8_t rowDiff = squareFrom / 8 - row; + + if(rowDiff == 2 || rowDiff == -2) // record en passant column + { + board[SCL_BOARD_ENPASSANT_CASTLE_BYTE] = + (board[SCL_BOARD_ENPASSANT_CASTLE_BYTE] & 0xf0) | (squareFrom % 8); + } + + if(row == 0 || row == 7) { + // promotion + s = SCL_pieceToColor(promotePiece, SCL_pieceIsWhite(s)); + + moveUndo.other |= 0x80; + } else { + // check en passant move + + int8_t columnDiff = (squareTo % 8) - (squareFrom % 8); + + if((columnDiff != 0) && (board[squareTo] == '.')) { + board[squareFrom + columnDiff] = '.'; + moveUndo.other |= 0x80; + } + } + } else if((s == 'r') || (s == 'R')) + _SCL_handleRookActivity(board, squareFrom); + + char taken = board[squareTo]; + + // taking a rook may also disable castling: + + if(taken == 'R' || taken == 'r') _SCL_handleRookActivity(board, squareTo); + +#if SCL_960_CASTLING + if(!castled) +#endif + { + board[squareTo] = s; + board[squareFrom] = '.'; + } + + board[SCL_BOARD_PLY_BYTE]++; // increase ply count + + return moveUndo; +} + +void SCL_boardSetPosition( + SCL_Board board, + const char* pieces, + uint8_t castlingEnPassant, + uint8_t moveCount, + uint8_t ply) { + for(uint8_t i = 0; i < SCL_BOARD_SQUARES; ++i, pieces++) + if(*pieces != 0) + board[i] = *pieces; + else + break; + + board[SCL_BOARD_ENPASSANT_CASTLE_BYTE] = castlingEnPassant; + board[SCL_BOARD_PLY_BYTE] = ply; + board[SCL_BOARD_MOVE_COUNT_BYTE] = moveCount; + board[SCL_BOARD_STATE_SIZE - 1] = 0; +} + +void SCL_squareSetAdd(SCL_SquareSet squareSet, uint8_t square) { + squareSet[square / 8] |= 0x01 << (square % 8); +} + +uint8_t SCL_squareSetContains(const SCL_SquareSet squareSet, uint8_t square) { + return squareSet[square / 8] & (0x01 << (square % 8)); +} + +uint8_t SCL_squareSetSize(const SCL_SquareSet squareSet) { + uint8_t result = 0; + + for(uint8_t i = 0; i < 8; ++i) { + uint8_t byte = squareSet[i]; + + for(uint8_t j = 0; j < 8; ++j) { + result += byte & 0x01; + byte >>= 1; + } + } + + return result; +} + +uint8_t SCL_squareSetEmpty(const SCL_SquareSet squareSet) { + for(uint8_t i = 0; i < 8; ++i) + if(squareSet[i] != 0) return 0; + + return 1; +} + +uint8_t SCL_squareSetGetRandom(const SCL_SquareSet squareSet, SCL_RandomFunction randFunc) { + uint8_t size = SCL_squareSetSize(squareSet); + + if(size == 0) return 0; + + uint8_t n = (randFunc() % size) + 1; + uint8_t i = 0; + + while(i < SCL_BOARD_SQUARES) { + if(SCL_squareSetContains(squareSet, i)) { + n--; + + if(n == 0) break; + } + + ++i; + } + + return i; +} + +void SCL_boardCopy(const SCL_Board boardFrom, SCL_Board boardTo) { + for(uint8_t i = 0; i < SCL_BOARD_STATE_SIZE; ++i) boardTo[i] = boardFrom[i]; +} + +uint8_t SCL_boardSquareAttacked(SCL_Board board, uint8_t square, uint8_t byWhite) { + const char* currentSquare = board; + + /* We need to place a temporary piece on the tested square in order to test if + the square is attacked (consider testing if attacked by a pawn). */ + + char previous = board[square]; + + board[square] = SCL_pieceToColor('r', !byWhite); + + for(uint8_t i = 0; i < SCL_BOARD_SQUARES; ++i, ++currentSquare) { + char s = *currentSquare; + + if((s == '.') || (SCL_pieceIsWhite(s) != byWhite)) continue; + + SCL_SquareSet moves; + SCL_boardGetPseudoMoves(board, i, 0, moves); + + if(SCL_squareSetContains(moves, square)) { + board[square] = previous; + return 1; + } + } + + board[square] = previous; + return 0; +} + +uint8_t SCL_boardCheck(SCL_Board board, uint8_t white) { + const char* square = board; + char kingChar = white ? 'K' : 'k'; + + for(uint8_t i = 0; i < SCL_BOARD_SQUARES; ++i, ++square) + if((*square == kingChar && SCL_boardSquareAttacked(board, i, !white))) return 1; + + return 0; +} + +uint8_t SCL_boardGameOver(SCL_Board board) { + uint8_t position = SCL_boardGetPosition(board); + + return (position == SCL_POSITION_MATE) || (position == SCL_POSITION_STALEMATE) || + (position == SCL_POSITION_DEAD); +} + +uint8_t SCL_boardMovePossible(SCL_Board board) { + uint8_t white = SCL_boardWhitesTurn(board); + + for(uint8_t i = 0; i < SCL_BOARD_SQUARES; ++i) { + char s = board[i]; + + if((s != '.') && (SCL_pieceIsWhite(s) == white)) { + SCL_SquareSet moves; + + SCL_boardGetMoves(board, i, moves); + + if(SCL_squareSetSize(moves) != 0) return 1; + } + } + + return 0; +} + +uint8_t SCL_boardMate(SCL_Board board) { + return SCL_boardGetPosition(board) == SCL_POSITION_MATE; +} + +void SCL_boardGetPseudoMoves( + SCL_Board board, + uint8_t pieceSquare, + uint8_t checkCastling, + SCL_SquareSet result) { + char piece = board[pieceSquare]; + + SCL_squareSetClear(result); + + uint8_t isWhite = SCL_pieceIsWhite(piece); + int8_t horizontalPosition = pieceSquare % 8; + int8_t pawnOffset = -8; + + switch(piece) { + case 'P': + pawnOffset = 8; + /* FALLTHROUGH */ + case 'p': { + uint8_t square = pieceSquare + pawnOffset; + uint8_t verticalPosition = pieceSquare / 8; + + if(board[square] == '.') // forward move + { + SCL_squareSetAdd(result, square); + + if(verticalPosition == (1 + (piece == 'p') * 5)) // start position? + { + uint8_t square2 = square + pawnOffset; + + if(board[square2] == '.') SCL_squareSetAdd(result, square2); + } + } + +#define checkDiagonal(hor, add) \ + if(horizontalPosition != hor) { \ + uint8_t square2 = square + add; \ + char c = board[square2]; \ + if(c != '.' && SCL_pieceIsWhite(c) != isWhite) SCL_squareSetAdd(result, square2); \ + } + + // diagonal moves + checkDiagonal(0, -1) checkDiagonal(7, 1) + + uint8_t enPassantRow = 4; + uint8_t enemyPawn = 'p'; + + if(piece == 'p') { + enPassantRow = 3; + enemyPawn = 'P'; + } + + // en-passant moves + if(verticalPosition == enPassantRow) { + uint8_t enPassantColumn = board[SCL_BOARD_ENPASSANT_CASTLE_BYTE] & 0x0f; + uint8_t column = pieceSquare % 8; + + for(int8_t offset = -1; offset < 2; offset += 2) + if((enPassantColumn == column + offset) && + (board[pieceSquare + offset] == enemyPawn)) { + SCL_squareSetAdd(result, pieceSquare + pawnOffset + offset); + break; + } + } + +#undef checkDiagonal + } break; + + case 'r': // rook + case 'R': + case 'b': // bishop + case 'B': + case 'q': // queen + case 'Q': { + const int8_t offsets[8] = {-8, 1, 8, -1, -7, 9, -9, 7}; + const int8_t columnDirs[8] = {0, 1, 0, -1, 1, 1, -1, -1}; + + uint8_t from = (piece == 'b' || piece == 'B') * 4; + uint8_t to = 4 + (piece != 'r' && piece != 'R') * 4; + + for(uint8_t i = from; i < to; ++i) { + int8_t offset = offsets[i]; + int8_t columnDir = columnDirs[i]; + int8_t square = pieceSquare; + int8_t col = horizontalPosition; + + while(1) { + square += offset; + col += columnDir; + + if(square < 0 || square > 63 || col < 0 || col > 7) break; + + char squareC = board[square]; + + if(squareC == '.') + SCL_squareSetAdd(result, square); + else { + if(SCL_pieceIsWhite(squareC) != isWhite) SCL_squareSetAdd(result, square); + + break; + } + } + } + } break; + + case 'n': // knight + case 'N': { + const int8_t offsets[4] = {6, 10, 15, 17}; + const int8_t columnsMinus[4] = {2, -2, 1, -1}; + const int8_t columnsPlus[4] = {-2, 2, -1, 1}; + const int8_t *off, *col; + +#define checkOffsets(op, comp, limit, dir) \ + off = offsets; \ + col = columns##dir; \ + for(uint8_t i = 0; i < 4; ++i, ++off, ++col) { \ + int8_t square = pieceSquare op(*off); \ + if(square comp limit) /* out of board? */ \ + break; \ + int8_t horizontalCheck = horizontalPosition + (*col); \ + if(horizontalCheck < 0 || horizontalCheck >= 8) continue; \ + char squareC = board[square]; \ + if((squareC == '.') || (SCL_pieceIsWhite(squareC) != isWhite)) \ + SCL_squareSetAdd(result, square); \ + } + + checkOffsets(-, <, 0, Minus) checkOffsets(+, >=, SCL_BOARD_SQUARES, Plus) + +#undef checkOffsets + } break; + + case 'k': // king + case 'K': { + uint8_t verticalPosition = pieceSquare / 8; + + uint8_t u = verticalPosition != 0, d = verticalPosition != 7, l = horizontalPosition != 0, + r = horizontalPosition != 7; + + uint8_t square2 = pieceSquare - 9; + +#define checkSquare(cond, add) \ + if(cond && ((board[square2] == '.') || (SCL_pieceIsWhite(board[square2])) != isWhite)) \ + SCL_squareSetAdd(result, square2); \ + square2 += add; + + checkSquare(l && u, 1) checkSquare(u, 1) checkSquare(r && u, 6) checkSquare(l, 2) + checkSquare(r, 6) checkSquare(l && d, 1) checkSquare(d, 1) checkSquare(r && d, 0) +#undef checkSquare + + // castling: + + if(checkCastling) { + uint8_t bitShift = 4 + 2 * (!isWhite); + + if((board[SCL_BOARD_ENPASSANT_CASTLE_BYTE] & (0x03 << bitShift)) && + !SCL_boardSquareAttacked(board, pieceSquare, !isWhite)) // no check? + { +#if !SCL_960_CASTLING + // short castle: + pieceSquare++; + + if((board[SCL_BOARD_ENPASSANT_CASTLE_BYTE] & (0x01 << bitShift)) && + (board[pieceSquare] == '.') && (board[pieceSquare + 1] == '.') && + (board[pieceSquare + 2] == SCL_pieceToColor('r', isWhite)) && + !SCL_boardSquareAttacked(board, pieceSquare, !isWhite)) + SCL_squareSetAdd(result, pieceSquare + 1); + + /* note: don't check the final square for check, it will potentially + be removed later (can't end up in check) */ + + // long castle: + pieceSquare -= 2; + + if((board[SCL_BOARD_ENPASSANT_CASTLE_BYTE] & (0x02 << bitShift)) && + (board[pieceSquare] == '.') && (board[pieceSquare - 1] == '.') && + (board[pieceSquare - 2] == '.') && + (board[pieceSquare - 3] == SCL_pieceToColor('r', isWhite)) && + !SCL_boardSquareAttacked(board, pieceSquare, !isWhite)) + SCL_squareSetAdd(result, pieceSquare - 1); +#else // 960 castling + for(int i = 0; i < 2; ++i) // short and long + if(board[SCL_BOARD_ENPASSANT_CASTLE_BYTE] & ((i + 1) << bitShift)) { + uint8_t rookPos = board[SCL_BOARD_EXTRA_BYTE] >> 3, targetPos = 5; + + if(i == 1) { + rookPos = board[SCL_BOARD_EXTRA_BYTE] & 0x07, targetPos = 3; + } + + if(!isWhite) { + rookPos += 56; + targetPos += 56; + } + + uint8_t ok = board[rookPos] == SCL_pieceToColor('r', isWhite); + + if(!ok) continue; + + int8_t inc = 1 - 2 * (targetPos > rookPos); + + while(targetPos != rookPos) // check vacant squares for the rook + { + if(board[targetPos] != '.' && targetPos != pieceSquare) { + ok = 0; + break; + } + + targetPos += inc; + } + + if(!ok) continue; + + targetPos = i == 0 ? 6 : 2; + + if(!isWhite) targetPos += 56; + + inc = 1 - 2 * (targetPos > pieceSquare); + + while(targetPos != pieceSquare) // check squares for the king + { + if((board[targetPos] != '.' && targetPos != rookPos) || + SCL_boardSquareAttacked(board, targetPos, !isWhite)) { + ok = 0; + break; + } + + targetPos += inc; + } + + if(ok) SCL_squareSetAdd(result, rookPos); + } +#endif + } + } + } break; + + default: + break; + } +} + +void SCL_printSquareSet(SCL_SquareSet set, SCL_PutCharFunction putCharFunc) { + uint8_t first = 1; + + putCharFunc('('); + + for(uint8_t i = 0; i < SCL_BOARD_SQUARES; ++i) { + if(!SCL_squareSetContains(set, i)) continue; + + if(!first) + putCharFunc(','); + else + first = 0; + + putCharFunc('A' + i % 8); + putCharFunc('1' + i / 8); + } + + putCharFunc(')'); +} + +void SCL_printSquareUTF8(uint8_t square, SCL_PutCharFunction putCharFunc) { + uint32_t val = 0; + + switch(square) { + case 'r': + val = 0x9c99e200; + break; + case 'n': + val = 0x9e99e200; + break; + case 'b': + val = 0x9d99e200; + break; + case 'q': + val = 0x9b99e200; + break; + case 'k': + val = 0x9a99e200; + break; + case 'p': + val = 0x9f99e200; + break; + case 'R': + val = 0x9699e200; + break; + case 'N': + val = 0x9899e200; + break; + case 'B': + val = 0x9799e200; + break; + case 'Q': + val = 0x9599e200; + break; + case 'K': + val = 0x9499e200; + break; + case 'P': + val = 0x9999e200; + break; + case '.': + val = 0x9296e200; + break; + case ',': + val = 0x9196e200; + break; + default: + putCharFunc(square); + return; + break; + } + + uint8_t count = 4; + + while((val % 256 == 0) && (count > 0)) { + val /= 256; + count--; + } + + while(count > 0) { + putCharFunc(val % 256); + val /= 256; + count--; + } +} + +void SCL_boardGetMoves(SCL_Board board, uint8_t pieceSquare, SCL_SquareSet result) { + SCL_SquareSet allMoves; + + SCL_squareSetClear(allMoves); + + for(uint8_t i = 0; i < 8; ++i) result[i] = 0; + + SCL_boardGetPseudoMoves(board, pieceSquare, 1, allMoves); + + // Now only keep moves that don't lead to one's check: + + SCL_SQUARE_SET_ITERATE_BEGIN(allMoves) + + SCL_MoveUndo undo = SCL_boardMakeMove(board, pieceSquare, iteratedSquare, 'q'); + + if(!SCL_boardCheck(board, !SCL_boardWhitesTurn(board))) + SCL_squareSetAdd(result, iteratedSquare); + + SCL_boardUndoMove(board, undo); + + SCL_SQUARE_SET_ITERATE_END +} + +uint8_t SCL_boardDead(SCL_Board board) { + /* + This byte represents material by bits: + + MSB _ _ _ _ _ _ _ _ LSB + | | | | | \_ white knight + | | | | \__ white bishop on white + | | | \____ white bishop on black + | | \________ black knight + | \__________ black bishop on white + \____________ black bishop on black + */ + uint8_t material = 0; + + const char* p = board; + + for(uint8_t i = 0; i < SCL_BOARD_SQUARES; ++i) { + char c = *p; + + switch(c) { + case 'n': + material |= 0x01; + break; + case 'N': + material |= 0x10; + break; + case 'b': + material |= (0x02 << (!SCL_squareIsWhite(i))); + break; + case 'B': + material |= (0x20 << (!SCL_squareIsWhite(i))); + break; + case 'p': + case 'P': + case 'r': + case 'R': + case 'q': + case 'Q': + return 0; // REMOVE later if more complex check are performed + break; + + default: + break; + } + + p++; + } + + // TODO: add other checks than only insufficient material + + // possible combinations of insufficient material: + + return (material == 0x00) || // king vs king + (material == 0x01) || // king and knight vs king + (material == 0x10) || // king and knight vs king + (material == 0x02) || // king and bishop vs king + (material == 0x20) || // king and bishop vs king + (material == 0x04) || // king and bishop vs king + (material == 0x40) || // king and bishop vs king + (material == 0x22) || // king and bishop vs king and bishop (same color) + (material == 0x44); // king and bishop vs king and bishop (same color) +} + +uint8_t SCL_boardGetPosition(SCL_Board board) { + uint8_t check = SCL_boardCheck(board, SCL_boardWhitesTurn(board)); + uint8_t moves = SCL_boardMovePossible(board); + + if(check) + return moves ? SCL_POSITION_CHECK : SCL_POSITION_MATE; + else if(!moves) + return SCL_POSITION_STALEMATE; + + if(SCL_boardDead(board)) return SCL_POSITION_DEAD; + + return SCL_POSITION_NORMAL; +} + +uint8_t SCL_stringToMove( + const char* moveString, + uint8_t* resultFrom, + uint8_t* resultTo, + char* resultPromotion) { + char c; + + uint8_t* dst = resultFrom; + + for(uint8_t i = 0; i < 2; ++i) { + c = *moveString; + + *dst = (c >= 'a') ? (c - 'a') : (c - 'A'); + + if(*dst > 7) return 0; + + moveString++; + c = *moveString; + + *dst += 8 * (c - '1'); + + if(*dst > 63) return 0; + + moveString++; + + dst = resultTo; + } + + c = *moveString; + + if(c < 'A') c = c - 'A' + 'a'; + + switch(c) { + case 'N': + case 'n': + *resultPromotion = 'n'; + break; + case 'B': + case 'b': + *resultPromotion = 'b'; + break; + case 'R': + case 'r': + *resultPromotion = 'r'; + break; + case 'Q': + case 'q': + default: + *resultPromotion = 'q'; + break; + } + + return 1; +} + +void SCL_printBoard( + SCL_Board board, + SCL_PutCharFunction putCharFunc, + SCL_SquareSet highlightSquares, + uint8_t selectSquare, + uint8_t format, + uint8_t offset, + uint8_t labels, + uint8_t blackDown) { + if(labels) { + for(uint8_t i = 0; i < offset + 2; ++i) putCharFunc(' '); + + for(uint8_t i = 0; i < 8; ++i) { + if((format != SCL_PRINT_FORMAT_COMPACT) && (format != SCL_PRINT_FORMAT_COMPACT_UTF8)) + putCharFunc(' '); + + putCharFunc(blackDown ? ('H' - i) : ('A' + i)); + } + + putCharFunc('\n'); + } + + int8_t i = 7; + int8_t add = 1; + + if(!blackDown) { + i = 56; + add = -1; + } + + for(int8_t row = 0; row < 8; ++row) { + for(uint8_t j = 0; j < offset; ++j) putCharFunc(' '); + + if(labels) { + putCharFunc(!blackDown ? ('8' - row) : ('1' + row)); + putCharFunc(' '); + } + + const char* square = board + i; + + for(int8_t col = 0; col < 8; ++col) { + switch(format) { + case SCL_PRINT_FORMAT_COMPACT: + putCharFunc( + (*square == '.') ? + (((i != selectSquare) ? + (!SCL_squareSetContains(highlightSquares, i) ? *square : '*') : + '#')) : + *square); + break; + + case SCL_PRINT_FORMAT_UTF8: { + char squareChar = SCL_squareIsWhite(i) ? '.' : ','; + char pieceChar = (*square == '.') ? squareChar : *square; + + if(i == selectSquare) { + putCharFunc('('); + + if(*square == '.') + putCharFunc(')'); + else + SCL_printSquareUTF8(pieceChar, putCharFunc); + } else if(!SCL_squareSetContains(highlightSquares, i)) { + SCL_printSquareUTF8(squareChar, putCharFunc); + SCL_printSquareUTF8(pieceChar, putCharFunc); + } else { + putCharFunc('['); + + if(*square == '.') + putCharFunc(']'); + else + SCL_printSquareUTF8(*square, putCharFunc); + } + + break; + } + + case SCL_PRINT_FORMAT_COMPACT_UTF8: + SCL_printSquareUTF8( + (*square == '.') ? + (SCL_squareSetContains(highlightSquares, i) ? + '*' : + (i == selectSquare ? '#' : ((SCL_squareIsWhite(i) ? '.' : ',')))) : + *square, + putCharFunc); + break; + + case SCL_PRINT_FORMAT_NORMAL: + default: { + uint8_t c = *square; + + char squareColor = SCL_squareIsWhite(i) ? ' ' : ':'; + + putCharFunc( + (i != selectSquare) ? + (!SCL_squareSetContains(highlightSquares, i) ? squareColor : '#') : + '@'); + + putCharFunc(c == '.' ? squareColor : *square); + break; + } + } + + i -= add; + square -= add; + } + + putCharFunc('\n'); + + i += add * 16; + } // for rows +} + +int16_t SCL_pieceValuePositive(char piece) { + switch(piece) { + case 'p': + case 'P': + return SCL_VALUE_PAWN; + break; + case 'n': + case 'N': + return SCL_VALUE_KNIGHT; + break; + case 'b': + case 'B': + return SCL_VALUE_BISHOP; + break; + case 'r': + case 'R': + return SCL_VALUE_ROOK; + break; + case 'q': + case 'Q': + return SCL_VALUE_QUEEN; + break; + case 'k': + case 'K': + return SCL_VALUE_KING; + break; + default: + break; + } + + return 0; +} + +int16_t SCL_pieceValue(char piece) { + switch(piece) { + case 'P': + return SCL_VALUE_PAWN; + break; + case 'N': + return SCL_VALUE_KNIGHT; + break; + case 'B': + return SCL_VALUE_BISHOP; + break; + case 'R': + return SCL_VALUE_ROOK; + break; + case 'Q': + return SCL_VALUE_QUEEN; + break; + case 'K': + return SCL_VALUE_KING; + break; + case 'p': + return -1 * SCL_VALUE_PAWN; + break; + case 'n': + return -1 * SCL_VALUE_KNIGHT; + break; + case 'b': + return -1 * SCL_VALUE_BISHOP; + break; + case 'r': + return -1 * SCL_VALUE_ROOK; + break; + case 'q': + return -1 * SCL_VALUE_QUEEN; + break; + case 'k': + return -1 * SCL_VALUE_KING; + break; + default: + break; + } + + return 0; +} + +#define ATTACK_BONUS 3 +#define MOBILITY_BONUS 10 +#define CENTER_BONUS 7 +#define CHECK_BONUS 5 +#define KING_CASTLED_BONUS 30 +#define KING_BACK_BONUS 15 +#define KING_NOT_CENTER_BONUS 15 +#define PAWN_NON_DOUBLE_BONUS 3 +#define PAWN_PAIR_BONUS 3 +#define KING_CENTERNESS 10 + +int16_t _SCL_rateKingEndgamePosition(uint8_t position) { + int16_t result = 0; + uint8_t rank = position / 8; + position %= 8; + + if(position > 1 && position < 6) result += KING_CENTERNESS; + + if(rank > 1 && rank < 6) result += KING_CENTERNESS; + + return result; +} + +int16_t SCL_boardEvaluateStatic(SCL_Board board) { + uint8_t position = SCL_boardGetPosition(board); + + int16_t total = 0; + + switch(position) { + case SCL_POSITION_MATE: + return SCL_boardWhitesTurn(board) ? -1 * SCL_EVALUATION_MAX_SCORE : + SCL_EVALUATION_MAX_SCORE; + break; + + case SCL_POSITION_STALEMATE: + case SCL_POSITION_DEAD: + return 0; + break; + + /* + main points are assigned as follows: + - points for material as a sum of all material on board + - for playing side: if a piece attacks piece of greater value, a fraction + of the value difference is gained (we suppose exchange), this is only + gained once per every attacking piece (maximum gain is taken), we only + take fraction so that actually taking the piece is favored + - ATTACK_BONUS points for any attacked piece + + other points are assigned as follows (in total these shouldn't be more + than the value of one pawn) + - mobility: MOBILITY_BONUS points for each piece with at least 4 possible + moves + - center control: CENTER_BONUS points for a piece on a center square + - CHECK_BONUS points for check + - king: + - safety (non endgame): KING_BACK_BONUS points for king on staring rank, + additional KING_CASTLED_BONUS if the kind if on castled square or + closer to the edge, additional KING_NOT_CENTER_BONUS for king not on + its start neighbouring center square + - center closeness (endgame): up to 2 * KING_CENTERNESS points for + being closer to center + - non-doubled pawns: PAWN_NON_DOUBLE_BONUS points for each pawn without + same color pawn directly in front of it + - pawn structure: PAWN_PAIR_BONUS points for each pawn guarding own pawn + - advancing pawns: 1 point for each pawn's rank in its move + direction + */ + + case SCL_POSITION_CHECK: + total += SCL_boardWhitesTurn(board) ? -1 * CHECK_BONUS : CHECK_BONUS; + /* FALLTHROUGH */ + case SCL_POSITION_NORMAL: + default: { + SCL_SquareSet moves; + + const char* p = board; + + int16_t positiveMaterial = 0; + uint8_t endgame = 0; + + // first count material to see if this is endgame or not + for(uint8_t i = 0; i < SCL_BOARD_SQUARES; ++i, ++p) { + char s = *p; + + if(s != '.') { + positiveMaterial += SCL_pieceValuePositive(s); + total += SCL_pieceValue(s); + } + } + + endgame = positiveMaterial <= SCL_ENDGAME_MATERIAL_LIMIT; + + p = board; + + for(uint8_t i = 0; i < SCL_BOARD_SQUARES; ++i, ++p) { + char s = *p; + + if(s != '.') { + uint8_t white = SCL_pieceIsWhite(s); + + switch(s) { + case 'k': // king safety + if(endgame) + total -= _SCL_rateKingEndgamePosition(i); + else if(i >= 56) { + total -= KING_BACK_BONUS; + + if(i != 59) { + total -= KING_NOT_CENTER_BONUS; + + if(i >= 62 || i <= 58) total -= KING_CASTLED_BONUS; + } + } + break; + + case 'K': + if(endgame) + total += _SCL_rateKingEndgamePosition(i); + else if(i <= 7) { + total += KING_BACK_BONUS; + + if(i != 3) { + total += KING_NOT_CENTER_BONUS; + + if(i <= 2 || i >= 6) total += KING_CASTLED_BONUS; + } + } + break; + + case 'P': // pawns + case 'p': { + int8_t rank = i / 8; + + if(rank != 0 && rank != 7) { + if(s == 'P') { + total += rank; + + char* tmp = board + i + 8; + + if(*tmp != 'P') total += PAWN_NON_DOUBLE_BONUS; + + if(i % 8 != 7) { + tmp++; + + if(*tmp == 'P') total += PAWN_PAIR_BONUS; + + if(*(tmp - 16) == 'P') total += PAWN_PAIR_BONUS; + } + } else { + total -= 7 - rank; + + char* tmp = board + i - 8; + + if(*tmp != 'p') total -= PAWN_NON_DOUBLE_BONUS; + + if(i % 8 != 7) { + tmp += 17; + + if(*tmp == 'p') total -= PAWN_PAIR_BONUS; + + if(*(tmp - 16) == 'p') total -= PAWN_PAIR_BONUS; + } + } + } + + break; + } + + default: + break; + } + + if(i >= 27 && i <= 36 && (i >= 35 || i <= 28)) // center control + total += white ? CENTER_BONUS : (-1 * CENTER_BONUS); + + // for performance we only take pseudo moves + SCL_boardGetPseudoMoves(board, i, 0, moves); + + if(SCL_squareSetSize(moves) >= 4) // mobility + total += white ? MOBILITY_BONUS : (-1 * MOBILITY_BONUS); + + int16_t exchangeBonus = 0; + + SCL_SQUARE_SET_ITERATE_BEGIN(moves) + + if(board[iteratedSquare] != '.') { + total += white ? ATTACK_BONUS : (-1 * ATTACK_BONUS); + + if(SCL_boardWhitesTurn(board) == white) { + int16_t valueDiff = SCL_pieceValuePositive(board[iteratedSquare]) - + SCL_pieceValuePositive(s); + + valueDiff /= 4; // only take a fraction to favor taking + + if(valueDiff > exchangeBonus) exchangeBonus = valueDiff; + } + } + + SCL_SQUARE_SET_ITERATE_END + + if(exchangeBonus != 0) total += white ? exchangeBonus : -1 * exchangeBonus; + } + } // for each square + + return total; + + break; + + } // normal position + } // switch + + return 0; +} + +#undef ATTACK_BONUS +#undef MOBILITY_BONUS +#undef CENTER_BONUS +#undef CHECK_BONUS +#undef KING_CASTLED_BONUS +#undef KING_BACK_BONUS +#undef PAWN_NON_DOUBLE_BONUS +#undef PAWN_PAIR_BONUS +#undef KING_CENTERNESS + +SCL_StaticEvaluationFunction _SCL_staticEvaluationFunction; +int16_t _SCL_currentEval; +int8_t _SCL_depthHardLimit; + +/** + Inner recursive function for SCL_boardEvaluateDynamic. It is passed a square + (or -1) at which last capture happened, to implement capture extension. +*/ +int16_t _SCL_boardEvaluateDynamic( + SCL_Board board, + int8_t depth, + int16_t alphaBeta, + int8_t takenSquare) { +#if SCL_COUNT_EVALUATED_POSITIONS + SCL_positionsEvaluated++; +#endif + +#if SCL_CALL_WDT_RESET + wdt_reset(); +#endif + + uint8_t whitesTurn = SCL_boardWhitesTurn(board); + int8_t valueMultiply = whitesTurn ? 1 : -1; + int16_t bestMoveValue = -1 * SCL_EVALUATION_MAX_SCORE; + uint8_t shouldCompute = depth > 0; + uint8_t extended = 0; + uint8_t positionType = SCL_boardGetPosition(board); + + if(!shouldCompute) { + /* here we do two extensions (deeper search): taking on a same square + (exchanges) and checks (good for mating and preventing mates): */ + extended = (depth > _SCL_depthHardLimit) && + (takenSquare >= 0 || (SCL_boardGetPosition(board) == SCL_POSITION_CHECK)); + + shouldCompute = extended; + } + +#if SCL_DEBUG_AI + char moveStr[8]; + uint8_t debugFirst = 1; +#endif + + if(shouldCompute && + (positionType == SCL_POSITION_NORMAL || positionType == SCL_POSITION_CHECK)) { +#if SCL_DEBUG_AI + putchar('('); +#endif + + alphaBeta *= valueMultiply; + uint8_t end = 0; + const char* b = board; + + depth--; + + for(uint8_t i = 0; i < SCL_BOARD_SQUARES; ++i, ++b) { + char s = *b; + + if(s != '.' && SCL_pieceIsWhite(s) == whitesTurn) { + SCL_SquareSet moves; + + SCL_squareSetClear(moves); + + SCL_boardGetMoves(board, i, moves); + + if(!SCL_squareSetEmpty(moves)) { + SCL_SQUARE_SET_ITERATE_BEGIN(moves) + + int8_t captureExtension = -1; + + if(board[iteratedSquare] != '.' && // takes a piece + (takenSquare == -1 || // extend on first taken sq. + (extended && takenSquare != -1) || // ignore check extension + (iteratedSquare == takenSquare))) // extend on same sq. taken + captureExtension = iteratedSquare; + + SCL_MoveUndo undo = SCL_boardMakeMove(board, i, iteratedSquare, 'q'); + + uint8_t s0Dummy, s1Dummy; + char pDummy; + + SCL_UNUSED(s0Dummy); + SCL_UNUSED(s1Dummy); + SCL_UNUSED(pDummy); + +#if SCL_DEBUG_AI + if(debugFirst) + debugFirst = 0; + else + putchar(','); + + if(extended) putchar('*'); + + printf("%s ", SCL_moveToString(board, i, iteratedSquare, 'q', moveStr)); +#endif + + int16_t value = _SCL_boardEvaluateDynamic( + board, + depth, // this is depth - 1, we decremented it +#if SCL_ALPHA_BETA + valueMultiply * bestMoveValue, +#else + 0, +#endif + captureExtension) * + valueMultiply; + + SCL_boardUndoMove(board, undo); + + if(value > bestMoveValue) { + bestMoveValue = value; + +#if SCL_ALPHA_BETA + // alpha-beta pruning: + + if(value > alphaBeta) // no, >= can't be here + { + end = 1; + iterationEnd = 1; + } +#endif + } + + SCL_SQUARE_SET_ITERATE_END + } // !squre set empty? + } // valid piece? + + if(end) break; + + } // for each square + +#if SCL_DEBUG_AI + putchar(')'); +#endif + } else // don't dive recursively, evaluate statically + { + bestMoveValue = valueMultiply * +#ifndef SCL_EVALUATION_FUNCTION + _SCL_staticEvaluationFunction(board); +#else + SCL_EVALUATION_FUNCTION(board); +#endif + + /* For stalemate return the opposite value of the board, i.e. if the + position is good for white, then stalemate is good for black and vice + versa. */ + if(positionType == SCL_POSITION_STALEMATE) bestMoveValue *= -1; + } + + /* Here we either improve (if the move worsens the situation) or devalve (if + it improves the situation) the result: this needs to be done so that good + moves far away are seen as worse compared to equally good moves achieved + in fewer moves. Without this an AI in winning situation may just repeat + random moves and draw by repetition even if it has mate in 1 (it sees all + moves as leading to mate). */ + bestMoveValue += bestMoveValue > _SCL_currentEval * valueMultiply ? -1 : 1; + +#if SCL_DEBUG_AI + printf("%d", bestMoveValue * valueMultiply); +#endif + + return bestMoveValue * valueMultiply; +} + +int16_t SCL_boardEvaluateDynamic( + SCL_Board board, + uint8_t baseDepth, + uint8_t extensionExtraDepth, + SCL_StaticEvaluationFunction evalFunction) { + _SCL_staticEvaluationFunction = evalFunction; + _SCL_currentEval = evalFunction(board); + _SCL_depthHardLimit = 0; + _SCL_depthHardLimit -= extensionExtraDepth; + + return _SCL_boardEvaluateDynamic( + board, + baseDepth, + SCL_boardWhitesTurn(board) ? SCL_EVALUATION_MAX_SCORE : (-1 * SCL_EVALUATION_MAX_SCORE), + -1); +} + +void SCL_boardRandomMove( + SCL_Board board, + SCL_RandomFunction randFunc, + uint8_t* squareFrom, + uint8_t* squareTo, + char* resultProm) { + *resultProm = (randFunc() < 128) ? ((randFunc() < 128) ? 'r' : 'n') : + ((randFunc() < 128) ? 'b' : 'q'); + + SCL_SquareSet set; + uint8_t white = SCL_boardWhitesTurn(board); + const char* s = board; + + SCL_squareSetClear(set); + + // find squares with pieces that have legal moves + + for(uint8_t i = 0; i < SCL_BOARD_SQUARES; ++i, ++s) { + char c = *s; + + if(c != '.' && SCL_pieceIsWhite(c) == white) { + SCL_SquareSet moves; + + SCL_boardGetMoves(board, i, moves); + + if(SCL_squareSetSize(moves) != 0) SCL_squareSetAdd(set, i); + } + } + + *squareFrom = SCL_squareSetGetRandom(set, randFunc); + + SCL_boardGetMoves(board, *squareFrom, set); + + *squareTo = SCL_squareSetGetRandom(set, randFunc); +} + +void SCL_printBoardSimple( + SCL_Board board, + SCL_PutCharFunction putCharFunc, + uint8_t selectSquare, + uint8_t format) { + SCL_SquareSet s; + + SCL_squareSetClear(s); + + SCL_printBoard(board, putCharFunc, s, selectSquare, format, 1, 1, 0); +} + +int16_t SCL_getAIMove( + SCL_Board board, + uint8_t baseDepth, + uint8_t extensionExtraDepth, + uint8_t endgameExtraDepth, + SCL_StaticEvaluationFunction evalFunc, + SCL_RandomFunction randFunc, + uint8_t randomness, + uint8_t repetitionMoveFrom, + uint8_t repetitionMoveTo, + uint8_t* resultFrom, + uint8_t* resultTo, + char* resultProm) { +#if SCL_DEBUG_AI + puts("===== AI debug ====="); + putchar('('); + unsigned char debugFirst = 1; + char moveStr[8]; +#endif + + if(baseDepth == 0) { + SCL_boardRandomMove(board, randFunc, resultFrom, resultTo, resultProm); +#ifndef SCL_EVALUATION_FUNCTION + return evalFunc(board); +#else + return SCL_EVALUATION_FUNCTION(board); +#endif + } + + if(SCL_boardEstimatePhase(board) == SCL_PHASE_ENDGAME) baseDepth += endgameExtraDepth; + + *resultFrom = 0; + *resultTo = 0; + *resultProm = 'q'; + + int16_t bestScore = SCL_boardWhitesTurn(board) ? -1 * SCL_EVALUATION_MAX_SCORE - 1 : + (SCL_EVALUATION_MAX_SCORE + 1); + + for(uint8_t i = 0; i < SCL_BOARD_SQUARES; ++i) + if(board[i] != '.' && SCL_boardWhitesTurn(board) == SCL_pieceIsWhite(board[i])) { + SCL_SquareSet moves; + + SCL_squareSetClear(moves); + + SCL_boardGetMoves(board, i, moves); + + SCL_SQUARE_SET_ITERATE_BEGIN(moves) + + int16_t score = 0; + +#if SCL_DEBUG_AI + if(debugFirst) + debugFirst = 0; + else + putchar(','); + + printf("%s ", SCL_moveToString(board, i, iteratedSquare, 'q', moveStr)); + +#endif + + if(i != repetitionMoveFrom || iteratedSquare != repetitionMoveTo) { + SCL_MoveUndo undo = SCL_boardMakeMove(board, i, iteratedSquare, 'q'); + + score = + SCL_boardEvaluateDynamic(board, baseDepth - 1, extensionExtraDepth, evalFunc); + + SCL_boardUndoMove(board, undo); + } + + if(randFunc != 0 && randomness > 1 && score < 16000 && score > -16000) { + /*^ We limit randomizing by about half the max score for two reasons: + to prevent over/under flows and secondly we don't want to alter + the highest values for checkmate -- these are modified by tiny + values depending on their depth so as to prevent endless loops in + which most moves are winning, biasing such values would completely + kill that algorithm */ + + int16_t bias = randFunc(); + bias = (bias - 128) / 2; + bias *= randomness - 1; + score += bias; + } + + uint8_t comparison = score == bestScore; + + if((comparison != 1) && ((SCL_boardWhitesTurn(board) && score > bestScore) || + (!SCL_boardWhitesTurn(board) && score < bestScore))) + comparison = 2; + + uint8_t replace = 0; + + if(randFunc == 0) + replace = comparison == 2; + else + replace = + (comparison == 2) || + ((comparison == 1) && (randFunc() < 160)); // not uniform distr. but simple + + if(replace) { + *resultFrom = i; + *resultTo = iteratedSquare; + bestScore = score; + } + + SCL_SQUARE_SET_ITERATE_END + } + +#if SCL_DEBUG_AI + printf(")%d %s\n", bestScore, SCL_moveToString(board, *resultFrom, *resultTo, 'q', moveStr)); + puts("===== AI debug end ===== "); +#endif + + return bestScore; +} + +uint8_t SCL_boardToFEN(SCL_Board board, char* string) { + uint8_t square = 56; + uint8_t spaces = 0; + uint8_t result = 0; + +#define put(c) \ + { \ + *string = (c); \ + string++; \ + result++; \ + } + + while(1) // pieces + { + char s = board[square]; + + if(s == '.') { + spaces++; + } else { + if(spaces != 0) { + put('0' + spaces) spaces = 0; + } + + put(s) + } + + square++; + + if(square % 8 == 0) { + if(spaces != 0) { + put('0' + spaces) spaces = 0; + } + + if(square == 8) break; + + put('/'); + + square -= 16; + } + } + + put(' '); + put(SCL_boardWhitesTurn(board) ? 'w' : 'b'); + put(' '); + + uint8_t b = board[SCL_BOARD_ENPASSANT_CASTLE_BYTE] & 0xf0; + + if(b != 0) // castling + { + if(b & 0x10) put('K'); + if(b & 0x20) put('Q'); + if(b & 0x40) put('k'); + if(b & 0x80) put('q'); + } else + put('-'); + + put(' '); + + b = board[SCL_BOARD_ENPASSANT_CASTLE_BYTE] & 0x0f; + + if(b < 8) { + put('a' + b); + put(SCL_boardWhitesTurn(board) ? '6' : '3'); + } else + put('-'); + + for(uint8_t i = 0; i < 2; ++i) { + put(' '); + + uint8_t moves = i == 0 ? ((uint8_t)board[SCL_BOARD_MOVE_COUNT_BYTE]) : + (((uint8_t)board[SCL_BOARD_PLY_BYTE]) / 2 + 1); + + uint8_t hundreds = moves / 100; + uint8_t tens = (moves % 100) / 10; + + if(hundreds != 0) { + put('0' + hundreds); + put('0' + tens); + } else if(tens != 0) + put('0' + tens); + + put('0' + moves % 10); + } + + *string = 0; // terminate the string + + return result + 1; + +#undef put +} + +uint8_t SCL_boardFromFEN(SCL_Board board, const char* string) { + uint8_t square = 56; + + while(1) { + char c = *string; + + if(c == 0) return 0; + + if(c != '/' && c != ' ') // ignore line separators + { + if(c < '9') // empty square sequence + { + while(c > '0') { + board[square] = '.'; + square++; + c--; + } + } else // piece + { + board[square] = c; + square++; + } + } else { + if(square == 8) break; + + square -= 16; + } + + string++; + } + +#define nextChar \ + string++; \ + if(*string == 0) return 0; + + nextChar // space + + board[SCL_BOARD_PLY_BYTE] = *string == 'b'; + nextChar + + nextChar // space + + uint8_t castleEnPassant = 0x0; + + while(*string != ' ') { + switch(*string) { + case 'K': + castleEnPassant |= 0x10; + break; + case 'Q': + castleEnPassant |= 0x20; + break; + case 'k': + castleEnPassant |= 0x40; + break; + case 'q': + castleEnPassant |= 0x80; + break; + default: + castleEnPassant |= 0xf0; + break; // for partial XFEN compat. + } + + nextChar + } + + nextChar // space + + if(*string != '-') { + castleEnPassant |= *string - 'a'; + nextChar + } + else castleEnPassant |= 0x0f; + + nextChar + + board[SCL_BOARD_ENPASSANT_CASTLE_BYTE] = castleEnPassant; + + for(uint8_t i = 0; i < 2; ++i) { + nextChar // space + + uint8_t ply = 0; + + while(1) { + char c = *string; + + if(c < '0' || c > '9') break; + + ply = ply * 10 + (c - '0'); + + string++; + } + + if(i == 0 && *string == 0) return 0; + + if(i == 0) + board[SCL_BOARD_MOVE_COUNT_BYTE] = ply; + else + board[SCL_BOARD_PLY_BYTE] += (ply - 1) * 2; + } + +#if SCL_960_CASTLING + _SCL_board960RememberRookPositions(board); +#endif + + return 1; +#undef nextChar +} + +uint8_t SCL_boardEstimatePhase(SCL_Board board) { + uint16_t totalMaterial = 0; + + uint8_t ply = board[SCL_BOARD_PLY_BYTE]; + + for(uint8_t i = 0; i < SCL_BOARD_SQUARES; ++i) { + char s = *board; + + if(s != '.') { + int16_t v = SCL_pieceValue(s); + + if(!SCL_pieceIsWhite(s)) v *= -1; + + totalMaterial += v; + } + + board++; + } + + if(totalMaterial < SCL_ENDGAME_MATERIAL_LIMIT) return SCL_PHASE_ENDGAME; + + if(ply <= 10 && (totalMaterial >= SCL_START_MATERIAL - 3 * SCL_VALUE_PAWN)) + return SCL_PHASE_OPENING; + + return SCL_PHASE_MIDGAME; +} + +#define SCL_IMAGE_COUNT 12 + +static const uint8_t SCL_images[8 * SCL_IMAGE_COUNT] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x81, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x81, 0xff, 0xff, 0xff, 0xff, 0xff, 0x81, 0xe7, 0xf7, + 0xf7, 0xaa, 0xff, 0xbd, 0xe7, 0xf7, 0xf7, 0xaa, 0xff, 0xc3, 0xc3, 0xe3, 0xc1, 0x80, + 0xff, 0x99, 0xdb, 0xeb, 0xc9, 0x94, 0xe7, 0xc3, 0x81, 0xc1, 0x94, 0x80, 0xe7, 0xdb, + 0xbd, 0xdd, 0xbe, 0xbe, 0xc3, 0xc3, 0x91, 0xe3, 0x80, 0x80, 0xdb, 0x99, 0x8d, 0xeb, + 0xaa, 0xbe, 0xc3, 0x81, 0xe1, 0xc1, 0xc1, 0xc1, 0xdb, 0xbd, 0xdd, 0xe3, 0xdd, 0xdd, + 0x81, 0x81, 0xc1, 0x9c, 0xc1, 0xc1, 0x81, 0x81, 0xc1, 0x9c, 0xc1, 0xc1}; + +void SCL_drawBoard( + SCL_Board board, + SCL_PutPixelFunction putPixel, + uint8_t selectedSquare, + SCL_SquareSet highlightSquares, + uint8_t blackDown) { + uint8_t row = 0; + uint8_t col = 0; + uint8_t x = 0; + uint8_t y = 0; + uint16_t n = 0; + uint8_t s = 0; + + uint8_t pictureLine = 0; + uint8_t loadLine = 1; + + while(row < 8) { + if(loadLine) { + s = blackDown ? (row * 8 + (7 - col)) : ((7 - row) * 8 + col); + + char piece = board[s]; + + if(piece == '.') + pictureLine = (y == 4) ? 0xef : 0xff; + else { + uint8_t offset = SCL_pieceIsWhite(piece) ? 6 : 0; + piece = SCL_pieceToColor(piece, 1); + + switch(piece) { + case 'R': + offset += 1; + break; + case 'N': + offset += 2; + break; + case 'B': + offset += 3; + break; + case 'K': + offset += 4; + break; + case 'Q': + offset += 5; + break; + default: + break; + } + + pictureLine = SCL_images[y * SCL_IMAGE_COUNT + offset]; + } + + if(SCL_squareSetContains(highlightSquares, s)) pictureLine &= (y % 2) ? 0xaa : 0x55; + + if(s == selectedSquare) pictureLine &= (y == 0 || y == 7) ? 0x00 : ~0x81; + + loadLine = 0; + } + + putPixel(pictureLine & 0x80, n); + pictureLine <<= 1; + + n++; + x++; + + if(x == 8) { + col++; + loadLine = 1; + x = 0; + } + + if(col == 8) { + y++; + col = 0; + x = 0; + } + + if(y == 8) { + row++; + y = 0; + } + } +} + +uint32_t SCL_boardHash32(const SCL_Board board) { + uint32_t result = (board[SCL_BOARD_PLY_BYTE] & 0x01) + + (((uint32_t)((uint8_t)board[SCL_BOARD_ENPASSANT_CASTLE_BYTE])) << 24) + + board[SCL_BOARD_MOVE_COUNT_BYTE]; + + const char* b = board; + + for(uint8_t i = 0; i < SCL_BOARD_SQUARES; ++i, ++b) { + switch(*b) { +#define C(p, n) \ + case p: \ + result ^= (i + 1) * n; \ + break; + // the below number are primes + C('P', 4003) + C('R', 84673) + C('N', 93911) + C('B', 999331) + C('Q', 909091) + C('K', 2796203) + C('p', 4793) + C('r', 19391) + C('n', 391939) + C('b', 108301) + C('q', 174763) + C('k', 2474431) +#undef C + default: + break; + } + } + + // for extra spread of values we swap the low/high parts: + result = (result >> 16) | (result << 16); + + return result; +} + +void SCL_boardDisableCastling(SCL_Board board) { + board[SCL_BOARD_ENPASSANT_CASTLE_BYTE] &= 0x0f; +} + +uint8_t SCL_boardMoveResetsCount(SCL_Board board, uint8_t squareFrom, uint8_t squareTo) { + return board[squareFrom] == 'P' || board[squareFrom] == 'p' || board[squareTo] != '.'; +} + +void SCL_printPGN(SCL_Record r, SCL_PutCharFunction putCharFunc, SCL_Board initialState) { + if(SCL_recordLength(r) == 0) return; + + uint16_t pos = 0; + + SCL_Board board; + + if(initialState != 0) + for(uint8_t i = 0; i < SCL_BOARD_STATE_SIZE; ++i) board[i] = initialState[i]; + else + SCL_boardInit(board); + + while(1) { + uint8_t s0, s1; + char p; + + uint8_t state = SCL_recordGetMove(r, pos, &s0, &s1, &p); + + pos++; + + if(pos % 2) { + uint8_t move = pos / 2 + 1; + + if(move / 100 != 0) putCharFunc('0' + move / 100); + + if(move / 10 != 0 || move / 100 != 0) putCharFunc('0' + (move % 100) / 10); + + putCharFunc('0' + move % 10); + + putCharFunc('.'); + putCharFunc(' '); + } + +#if !SCL_960_CASTLING + if((board[s0] == 'K' && s0 == 4 && (s1 == 2 || s1 == 6)) || + (board[s0] == 'k' && s0 == 60 && (s1 == 62 || s1 == 58))) +#else + if((board[s0] == 'K' && board[s1] == 'R') || (board[s0] == 'k' && board[s1] == 'r')) +#endif + { + putCharFunc('O'); + putCharFunc('-'); + putCharFunc('O'); + +#if !SCL_960_CASTLING + if(s1 == 58 || s1 == 2) +#else + if((s1 == (board[SCL_BOARD_EXTRA_BYTE] & 0x07)) || + (s1 == 56 + (board[SCL_BOARD_EXTRA_BYTE] & 0x07))) +#endif + { + putCharFunc('-'); + putCharFunc('O'); + } + } else { + uint8_t pawn = board[s0] == 'P' || board[s0] == 'p'; + + if(!pawn) { + putCharFunc(SCL_pieceToColor(board[s0], 1)); + + // disambiguation: + + uint8_t specify = 0; + + for(int i = 0; i < SCL_BOARD_SQUARES; ++i) + if(i != s0 && board[i] == board[s0]) { + SCL_SquareSet s; + + SCL_squareSetClear(s); + + SCL_boardGetMoves(board, i, s); + + if(SCL_squareSetContains(s, s1)) specify |= (s0 % 8 != s1 % 8) ? 1 : 2; + } + + if(specify & 0x01) putCharFunc('a' + s0 % 8); + + if(specify & 0x02) putCharFunc('1' + s0 / 8); + } + + if(board[s1] != '.' || (pawn && s0 % 8 != s1 % 8 && board[s1] == '.')) // capture? + { + if(pawn) putCharFunc('a' + s0 % 8); + + putCharFunc('x'); + } + + putCharFunc('a' + s1 % 8); + putCharFunc('1' + s1 / 8); + + if(pawn && (s1 >= 56 || s1 <= 7)) // promotion? + { + putCharFunc('='); + putCharFunc(SCL_pieceToColor(p, 1)); + } + } + + SCL_boardMakeMove(board, s0, s1, p); + + uint8_t position = SCL_boardGetPosition(board); + + if(position == SCL_POSITION_CHECK) putCharFunc('+'); + + if(position == SCL_POSITION_MATE) { + putCharFunc('#'); + break; + } else if(state != SCL_RECORD_CONT) { + putCharFunc('*'); + break; + } + + putCharFunc(' '); + } +} + +void SCL_recordCopy(SCL_Record recordFrom, SCL_Record recordTo) { + for(uint16_t i = 0; i < SCL_RECORD_MAX_SIZE; ++i) recordTo[i] = recordFrom[i]; +} + +void SCL_gameInit(SCL_Game* game, const SCL_Board startState) { + game->startState = startState; + + if(startState != 0) + SCL_boardCopy(startState, game->board); + else + SCL_boardInit(game->board); + + SCL_recordInit(game->record); + + for(uint8_t i = 0; i < 14; ++i) game->prevMoves[i] = 0; + + game->state = SCL_GAME_STATE_PLAYING; + game->ply = 0; + + SCL_recordInit(game->record); +} + +uint8_t SCL_gameGetRepetiotionMove(SCL_Game* game, uint8_t* squareFrom, uint8_t* squareTo) { + if(squareFrom != 0 && squareTo != 0) { + *squareFrom = 0; + *squareTo = 0; + } + + /* pos. 1st 2nd 3rd + | | | + v v v + 01 23 45 67 89 AB CD EF + move ab cd ba dc ab cd ba dc */ + + if(game->ply >= 7 && game->prevMoves[0] == game->prevMoves[5] && + game->prevMoves[0] == game->prevMoves[8] && game->prevMoves[0] == game->prevMoves[13] && + + game->prevMoves[1] == game->prevMoves[4] && game->prevMoves[1] == game->prevMoves[9] && + game->prevMoves[1] == game->prevMoves[12] && + + game->prevMoves[2] == game->prevMoves[7] && game->prevMoves[2] == game->prevMoves[10] && + + game->prevMoves[3] == game->prevMoves[6] && game->prevMoves[3] == game->prevMoves[11]) { + if(squareFrom != 0 && squareTo != 0) { + *squareFrom = game->prevMoves[3]; + *squareTo = game->prevMoves[2]; + } + + return 1; + } + + return 0; +} + +void SCL_gameMakeMove(SCL_Game* game, uint8_t squareFrom, uint8_t squareTo, char promoteTo) { + uint8_t repetitionS0, repetitionS1; + + SCL_gameGetRepetiotionMove(game, &repetitionS0, &repetitionS1); + SCL_boardMakeMove(game->board, squareFrom, squareTo, promoteTo); + SCL_recordAdd(game->record, squareFrom, squareTo, promoteTo, SCL_RECORD_CONT); + // ^ TODO: SCL_RECORD_CONT + + game->ply++; + + for(uint8_t i = 0; i < 14 - 2; ++i) game->prevMoves[i] = game->prevMoves[i + 2]; + + game->prevMoves[12] = squareFrom; + game->prevMoves[13] = squareTo; + + if(squareFrom == repetitionS0 && squareTo == repetitionS1) + game->state = SCL_GAME_STATE_DRAW_REPETITION; + else if(game->board[SCL_BOARD_MOVE_COUNT_BYTE] >= 50) + game->state = SCL_GAME_STATE_DRAW_50; + else { + uint8_t position = SCL_boardGetPosition(game->board); + + switch(position) { + case SCL_POSITION_MATE: + game->state = SCL_boardWhitesTurn(game->board) ? SCL_GAME_STATE_BLACK_WIN : + SCL_GAME_STATE_WHITE_WIN; + break; + + case SCL_POSITION_STALEMATE: + game->state = SCL_GAME_STATE_DRAW_STALEMATE; + break; + + case SCL_POSITION_DEAD: + game->state = SCL_GAME_STATE_DRAW_DEAD; + break; + + default: + break; + } + } +} + +uint8_t SCL_gameUndoMove(SCL_Game* game) { + if(game->ply == 0) return 0; + + if((game->ply - 1) > SCL_recordLength(game->record)) return 0; // can't undo, lacking record + + SCL_Record r; + + SCL_recordCopy(game->record, r); + + uint16_t applyMoves = game->ply - 1; + + SCL_gameInit(game, game->startState); + + for(uint16_t i = 0; i < applyMoves; ++i) { + uint8_t s0, s1; + char p; + + SCL_recordGetMove(r, i, &s0, &s1, &p); + SCL_gameMakeMove(game, s0, s1, p); + } + + return 1; +} + +uint8_t SCL_boardMoveIsLegal(SCL_Board board, uint8_t squareFrom, uint8_t squareTo) { + if(squareFrom >= SCL_BOARD_SQUARES || squareTo >= SCL_BOARD_SQUARES) return 0; + + char piece = board[squareFrom]; + + if((piece == '.') || (SCL_boardWhitesTurn(board) != SCL_pieceIsWhite(piece))) return 0; + + SCL_SquareSet moves; + + SCL_boardGetMoves(board, squareFrom, moves); + + return SCL_squareSetContains(moves, squareTo); +} + +#endif // guard diff --git a/applications/external/chess/flipchess.c b/applications/external/chess/flipchess.c new file mode 100644 index 000000000..3a1ca8c79 --- /dev/null +++ b/applications/external/chess/flipchess.c @@ -0,0 +1,179 @@ +#include "flipchess.h" +#include "helpers/flipchess_haptic.h" + +bool flipchess_custom_event_callback(void* context, uint32_t event) { + furi_assert(context); + FlipChess* app = context; + return scene_manager_handle_custom_event(app->scene_manager, event); +} + +void flipchess_tick_event_callback(void* context) { + furi_assert(context); + FlipChess* app = context; + scene_manager_handle_tick_event(app->scene_manager); +} + +//leave app if back button pressed +bool flipchess_navigation_event_callback(void* context) { + furi_assert(context); + FlipChess* app = context; + return scene_manager_handle_back_event(app->scene_manager); +} + +static void text_input_callback(void* context) { + furi_assert(context); + FlipChess* app = context; + bool handled = false; + + // check that there is text in the input + if(strlen(app->input_text) > 0) { + if(app->input_state == FlipChessTextInputGame) { + if(app->import_game == 1) { + strncpy(app->import_game_text, app->input_text, TEXT_SIZE); + + uint8_t status = FlipChessStatusNone; + if(status == FlipChessStatusNone) { + //notification_message(app->notification, &sequence_blink_cyan_100); + flipchess_play_happy_bump(app); + } else { + //notification_message(app->notification, &sequence_blink_red_100); + flipchess_play_long_bump(app); + } + } + // reset input state + app->input_state = FlipChessTextInputDefault; + handled = true; + view_dispatcher_switch_to_view(app->view_dispatcher, FlipChessViewIdMenu); + } + } + + if(!handled) { + // reset input state + app->input_state = FlipChessTextInputDefault; + view_dispatcher_switch_to_view(app->view_dispatcher, FlipChessViewIdMenu); + } +} + +FlipChess* flipchess_app_alloc() { + FlipChess* app = malloc(sizeof(FlipChess)); + app->gui = furi_record_open(RECORD_GUI); + app->notification = furi_record_open(RECORD_NOTIFICATION); + + //Turn backlight on, believe me this makes testing your app easier + notification_message(app->notification, &sequence_display_backlight_on); + + //Scene additions + app->view_dispatcher = view_dispatcher_alloc(); + view_dispatcher_enable_queue(app->view_dispatcher); + + app->scene_manager = scene_manager_alloc(&flipchess_scene_handlers, app); + view_dispatcher_set_event_callback_context(app->view_dispatcher, app); + view_dispatcher_set_navigation_event_callback( + app->view_dispatcher, flipchess_navigation_event_callback); + view_dispatcher_set_tick_event_callback( + app->view_dispatcher, flipchess_tick_event_callback, 100); + view_dispatcher_set_custom_event_callback( + app->view_dispatcher, flipchess_custom_event_callback); + app->submenu = submenu_alloc(); + + // Settings + app->haptic = FlipChessHapticOn; + app->white_mode = FlipChessPlayerHuman; + app->black_mode = FlipChessPlayerAI1; + + // Startscreen + app->sound = 0; + // Main menu + app->import_game = 0; + + // Text input + app->input_state = FlipChessTextInputDefault; + + view_dispatcher_add_view( + app->view_dispatcher, FlipChessViewIdMenu, submenu_get_view(app->submenu)); + app->flipchess_startscreen = flipchess_startscreen_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, + FlipChessViewIdStartscreen, + flipchess_startscreen_get_view(app->flipchess_startscreen)); + app->flipchess_scene_1 = flipchess_scene_1_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, + FlipChessViewIdScene1, + flipchess_scene_1_get_view(app->flipchess_scene_1)); + app->variable_item_list = variable_item_list_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, + FlipChessViewIdSettings, + variable_item_list_get_view(app->variable_item_list)); + + app->text_input = text_input_alloc(); + text_input_set_result_callback( + app->text_input, + text_input_callback, + (void*)app, + app->input_text, + TEXT_BUFFER_SIZE, + //clear default text + true); + text_input_set_header_text(app->text_input, "Input"); + view_dispatcher_add_view( + app->view_dispatcher, FlipChessViewIdTextInput, text_input_get_view(app->text_input)); + + //End Scene Additions + + return app; +} + +void flipchess_app_free(FlipChess* app) { + furi_assert(app); + + // Scene manager + scene_manager_free(app->scene_manager); + + text_input_free(app->text_input); + + // View Dispatcher + view_dispatcher_remove_view(app->view_dispatcher, FlipChessViewIdMenu); + view_dispatcher_remove_view(app->view_dispatcher, FlipChessViewIdScene1); + view_dispatcher_remove_view(app->view_dispatcher, FlipChessViewIdSettings); + view_dispatcher_remove_view(app->view_dispatcher, FlipChessViewIdTextInput); + submenu_free(app->submenu); + + view_dispatcher_free(app->view_dispatcher); + furi_record_close(RECORD_GUI); + + app->gui = NULL; + app->notification = NULL; + + //Remove whatever is left + //memzero(app, sizeof(FlipChess)); + free(app); +} + +int32_t flipchess_app(void* p) { + UNUSED(p); + FlipChess* app = flipchess_app_alloc(); + + // Disabled because causes exit on custom firmwares such as RM + /*if(!furi_hal_region_is_provisioned()) { + flipchess_app_free(app); + return 1; + }*/ + + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); + + scene_manager_next_scene( + app->scene_manager, FlipChessSceneStartscreen); //Start with start screen + //scene_manager_next_scene(app->scene_manager, FlipChessSceneMenu); //if you want to directly start with Menu + + furi_hal_random_init(); + // furi_hal_power_suppress_charge_enter(); + + view_dispatcher_run(app->view_dispatcher); + + // furi_hal_power_suppress_charge_exit(); + flipchess_app_free(app); + + return 0; +} diff --git a/applications/external/chess/flipchess.h b/applications/external/chess/flipchess.h new file mode 100644 index 000000000..39cdaa90b --- /dev/null +++ b/applications/external/chess/flipchess.h @@ -0,0 +1,78 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "scenes/flipchess_scene.h" +#include "views/flipchess_startscreen.h" +#include "views/flipchess_scene_1.h" + +#define FLIPCHESS_VERSION "v1.8.0" + +#define TEXT_BUFFER_SIZE 96 +#define TEXT_SIZE (TEXT_BUFFER_SIZE - 1) + +typedef struct { + Gui* gui; + NotificationApp* notification; + ViewDispatcher* view_dispatcher; + Submenu* submenu; + SceneManager* scene_manager; + VariableItemList* variable_item_list; + TextInput* text_input; + FlipChessStartscreen* flipchess_startscreen; + FlipChessScene1* flipchess_scene_1; + // Settings options + int haptic; + int white_mode; + int black_mode; + // Startscreen options + uint8_t sound; + // Main menu options + uint8_t import_game; + // Text input + uint8_t input_state; + char import_game_text[TEXT_BUFFER_SIZE]; + char input_text[TEXT_BUFFER_SIZE]; +} FlipChess; + +typedef enum { + FlipChessViewIdStartscreen, + FlipChessViewIdMenu, + FlipChessViewIdScene1, + FlipChessViewIdSettings, + FlipChessViewIdTextInput, +} FlipChessViewId; + +typedef enum { + FlipChessHapticOff, + FlipChessHapticOn, +} FlipChessHapticState; + +typedef enum { + FlipChessPlayerHuman = 0, + FlipChessPlayerAI1 = 1, + FlipChessPlayerAI2 = 2, + FlipChessPlayerAI3 = 3, +} FlipChessPlayerMode; + +typedef enum { FlipChessTextInputDefault, FlipChessTextInputGame } FlipChessTextInputState; + +typedef enum { + FlipChessStatusNone = 0, + FlipChessStatusMovePlayer = 1, + FlipChessStatusMoveAI = 2, + FlipChessStatusMoveUndo = 3, + FlipChessStatusReturn = 10, + FlipChessStatusLoadError = 11, + FlipChessStatusSaveError = 12, +} FlipChessStatus; diff --git a/applications/external/chess/flipchess_10px.png b/applications/external/chess/flipchess_10px.png new file mode 100644 index 000000000..c7b0779c1 Binary files /dev/null and b/applications/external/chess/flipchess_10px.png differ diff --git a/applications/external/chess/helpers/flipchess_custom_event.h b/applications/external/chess/helpers/flipchess_custom_event.h new file mode 100644 index 000000000..4391a2fa6 --- /dev/null +++ b/applications/external/chess/helpers/flipchess_custom_event.h @@ -0,0 +1,16 @@ +#pragma once + +typedef enum { + FlipChessCustomEventStartscreenUp, + FlipChessCustomEventStartscreenDown, + FlipChessCustomEventStartscreenLeft, + FlipChessCustomEventStartscreenRight, + FlipChessCustomEventStartscreenOk, + FlipChessCustomEventStartscreenBack, + FlipChessCustomEventScene1Up, + FlipChessCustomEventScene1Down, + FlipChessCustomEventScene1Left, + FlipChessCustomEventScene1Right, + FlipChessCustomEventScene1Ok, + FlipChessCustomEventScene1Back, +} FlipChessCustomEvent; \ No newline at end of file diff --git a/applications/external/chess/helpers/flipchess_file.c b/applications/external/chess/helpers/flipchess_file.c new file mode 100644 index 000000000..0a3cf5a4f --- /dev/null +++ b/applications/external/chess/helpers/flipchess_file.c @@ -0,0 +1,153 @@ +#include "flipchess_file.h" +#include +#include + +// #define FLIPCHESS_APP_BASE_FOLDER APP_BOARDA_PATH("flipchess") +#define FLIPCHESS_APP_BASE_FOLDER EXT_PATH("apps_data/flipchess") +#define FLIPCHESS_APP_BASE_FOLDER_PATH(path) FLIPCHESS_APP_BASE_FOLDER "/" path +#define FLIPCHESS_BOARD_FILE_NAME "board_fen.txt" +#define FLIPCHESS_BOARD_FILE_NAME_BAK "board_fen.bak" +#define FLIPCHESS_BOARD_PATH FLIPCHESS_APP_BASE_FOLDER_PATH(FLIPCHESS_BOARD_FILE_NAME) +#define FLIPCHESS_BOARD_PATH_BAK FLIPCHESS_APP_BASE_FOLDER_PATH(FLIPCHESS_BOARD_FILE_NAME_BAK) + +#define FILE_MAX_PATH_LEN 48 +#define FILE_MAX_CHARS 94 + +bool flipchess_has_file(const FlipChessFile file_type, const char* file_name, const bool remove) { + bool ret = false; + const char* path; + if(file_type == FlipChessFileBoard) { + path = FLIPCHESS_BOARD_PATH; + } else { + char path_buf[FILE_MAX_PATH_LEN] = {0}; + strcpy(path_buf, FLIPCHESS_APP_BASE_FOLDER); // 22 + strcpy(path_buf + strlen(path_buf), "/"); + strcpy(path_buf + strlen(path_buf), file_name); + path = path_buf; + } + + Storage* fs_api = furi_record_open(RECORD_STORAGE); + if(remove) { + ret = storage_simply_remove(fs_api, path); + } else { + ret = storage_file_exists(fs_api, path); + } + furi_record_close(RECORD_STORAGE); + + return ret; +} + +bool flipchess_load_file(char* contents, const FlipChessFile file_type, const char* file_name) { + bool ret = false; + const char* path; + if(file_type == FlipChessFileBoard) { + path = FLIPCHESS_BOARD_PATH; + } else { + char path_buf[FILE_MAX_PATH_LEN] = {0}; + strcpy(path_buf, FLIPCHESS_APP_BASE_FOLDER); // 22 + strcpy(path_buf + strlen(path_buf), "/"); + strcpy(path_buf + strlen(path_buf), file_name); + path = path_buf; + } + + Storage* fs_api = furi_record_open(RECORD_STORAGE); + + File* settings_file = storage_file_alloc(fs_api); + if(storage_file_open(settings_file, path, FSAM_READ, FSOM_OPEN_EXISTING)) { + char chr; + int i = 0; + while((storage_file_read(settings_file, &chr, 1) == 1) && + !storage_file_eof(settings_file)) { + if(i < FILE_MAX_CHARS) { + contents[i] = chr; + } + i++; + } + ret = true; + } else { + contents[0] = '\0'; + ret = false; + } + storage_file_close(settings_file); + storage_file_free(settings_file); + furi_record_close(RECORD_STORAGE); + + if(strlen(contents) > 0) { + Storage* fs_api = furi_record_open(RECORD_STORAGE); + FileInfo layout_file_info; + FS_Error file_check_err = storage_common_stat(fs_api, path, &layout_file_info); + furi_record_close(RECORD_STORAGE); + if(file_check_err != FSE_OK) { + contents[0] = '\0'; + ret = false; + } + // if(layout_file_info.size != 256) { + // memzero(settings, strlen(settings)); + // settings[0] = '\0'; + // } + } + + return ret; +} + +bool flipchess_save_file( + const char* settings, + const FlipChessFile file_type, + const char* file_name, + const bool append, + const bool overwrite) { + bool ret = false; + const char* path; + const char* path_bak; + if(file_type == FlipChessFileBoard) { + path = FLIPCHESS_BOARD_PATH; + path_bak = FLIPCHESS_BOARD_PATH_BAK; + } else { + char path_buf[FILE_MAX_PATH_LEN] = {0}; + strcpy(path_buf, FLIPCHESS_APP_BASE_FOLDER); // 22 + strcpy(path_buf + strlen(path_buf), "/"); + strcpy(path_buf + strlen(path_buf), file_name); + path = path_buf; + path_bak = NULL; + } + int open_mode = FSOM_OPEN_ALWAYS; + if(append) { + open_mode = FSOM_OPEN_APPEND; + } + + Storage* fs_api = furi_record_open(RECORD_STORAGE); + + // try to create the folder + storage_simply_mkdir(fs_api, FLIPCHESS_APP_BASE_FOLDER); + + if(overwrite) { + storage_simply_remove(fs_api, path); + } + + File* settings_file = storage_file_alloc(fs_api); + if(storage_file_open(settings_file, path, FSAM_WRITE, open_mode)) { + storage_file_write(settings_file, settings, strlen(settings)); + storage_file_write(settings_file, "\n", 1); + ret = true; + } + storage_file_close(settings_file); + storage_file_free(settings_file); + + if(path_bak != NULL) { + if(overwrite) { + storage_simply_remove(fs_api, path_bak); + } + + File* settings_file_bak = storage_file_alloc(fs_api); + if(storage_file_open(settings_file_bak, path_bak, FSAM_WRITE, open_mode)) { + storage_file_write(settings_file_bak, settings, strlen(settings)); + storage_file_write(settings_file_bak, "\n", 1); + } + storage_file_close(settings_file_bak); + storage_file_free(settings_file_bak); + } + + furi_record_close(RECORD_STORAGE); + + return ret; +} diff --git a/applications/external/chess/helpers/flipchess_file.h b/applications/external/chess/helpers/flipchess_file.h new file mode 100644 index 000000000..1550b2e2d --- /dev/null +++ b/applications/external/chess/helpers/flipchess_file.h @@ -0,0 +1,15 @@ +#include + +typedef enum { + FlipChessFileBoard, + FlipChessFileOther, +} FlipChessFile; + +bool flipchess_has_file(const FlipChessFile file_type, const char* file_name, const bool remove); +bool flipchess_load_file(char* contents, const FlipChessFile file_type, const char* file_name); +bool flipchess_save_file( + const char* contents, + const FlipChessFile file_type, + const char* file_name, + const bool append, + const bool overwrite); \ No newline at end of file diff --git a/applications/external/chess/helpers/flipchess_haptic.c b/applications/external/chess/helpers/flipchess_haptic.c new file mode 100644 index 000000000..b07fd73df --- /dev/null +++ b/applications/external/chess/helpers/flipchess_haptic.c @@ -0,0 +1,35 @@ +#include "flipchess_haptic.h" +#include "../flipchess.h" + +void flipchess_play_happy_bump(void* context) { + FlipChess* app = context; + if(app->haptic != 1) { + return; + } + notification_message(app->notification, &sequence_set_vibro_on); + furi_thread_flags_wait(0, FuriFlagWaitAny, 20); + notification_message(app->notification, &sequence_reset_vibro); +} + +void flipchess_play_bad_bump(void* context) { + FlipChess* app = context; + if(app->haptic != 1) { + return; + } + notification_message(app->notification, &sequence_set_vibro_on); + furi_thread_flags_wait(0, FuriFlagWaitAny, 100); + notification_message(app->notification, &sequence_reset_vibro); +} + +void flipchess_play_long_bump(void* context) { + FlipChess* app = context; + if(app->haptic != 1) { + return; + } + for(int i = 0; i < 4; i++) { + notification_message(app->notification, &sequence_set_vibro_on); + furi_thread_flags_wait(0, FuriFlagWaitAny, 50); + notification_message(app->notification, &sequence_reset_vibro); + furi_thread_flags_wait(0, FuriFlagWaitAny, 100); + } +} diff --git a/applications/external/chess/helpers/flipchess_haptic.h b/applications/external/chess/helpers/flipchess_haptic.h new file mode 100644 index 000000000..19c14a3e6 --- /dev/null +++ b/applications/external/chess/helpers/flipchess_haptic.h @@ -0,0 +1,7 @@ +#include + +void flipchess_play_happy_bump(void* context); + +void flipchess_play_bad_bump(void* context); + +void flipchess_play_long_bump(void* context); diff --git a/applications/external/chess/helpers/flipchess_voice.cpp b/applications/external/chess/helpers/flipchess_voice.cpp new file mode 100644 index 000000000..9e0c19908 --- /dev/null +++ b/applications/external/chess/helpers/flipchess_voice.cpp @@ -0,0 +1,37 @@ +#include "flipchess_voice.h" +#include +#include +#include "../sam/stm32_sam.h" +STM32SAM voice; + +void flipchess_voice_shall_we_play() { + if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(1000)) { + voice.begin(); + voice.say("SHAAL WE PLAY AY GAME?"); + furi_hal_speaker_release(); + } +} + +void flipchess_voice_which_side() { + if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(1000)) { + voice.begin(); + voice.say("WHICH SIDE DO YOU WANT?"); + furi_hal_speaker_release(); + } +} + +void flipchess_voice_how_about_chess() { + if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(1000)) { + voice.begin(); + voice.say("HOW ABOUT A NICE GAME OF CHESS?"); + furi_hal_speaker_release(); + } +} + +void flipchess_voice_a_strange_game() { + if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(1000)) { + voice.begin(); + voice.say("A STRANGE GAME... THE ONLY WINNING MOVE IS NOT TO PLAY."); + furi_hal_speaker_release(); + } +} \ No newline at end of file diff --git a/applications/external/chess/helpers/flipchess_voice.h b/applications/external/chess/helpers/flipchess_voice.h new file mode 100644 index 000000000..3b1060118 --- /dev/null +++ b/applications/external/chess/helpers/flipchess_voice.h @@ -0,0 +1,12 @@ +#ifdef __cplusplus +#define EXTERNC extern "C" +#else +#define EXTERNC +#endif + +EXTERNC void flipchess_voice_shall_we_play(); +EXTERNC void flipchess_voice_which_side(); +EXTERNC void flipchess_voice_how_about_chess(); +EXTERNC void flipchess_voice_a_strange_game(); + +#undef EXTERNC diff --git a/applications/external/chess/icons/Background_128x11.png b/applications/external/chess/icons/Background_128x11.png new file mode 100644 index 000000000..149780f0c Binary files /dev/null and b/applications/external/chess/icons/Background_128x11.png differ diff --git a/applications/external/chess/icons/FLIPR_128x64.png b/applications/external/chess/icons/FLIPR_128x64.png new file mode 100644 index 000000000..48d3684a1 Binary files /dev/null and b/applications/external/chess/icons/FLIPR_128x64.png differ diff --git a/applications/external/chess/sam/stm32_sam.cpp b/applications/external/chess/sam/stm32_sam.cpp new file mode 100644 index 000000000..41053a5ee --- /dev/null +++ b/applications/external/chess/sam/stm32_sam.cpp @@ -0,0 +1,5703 @@ + +#include "stm32_sam.h" +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////////////////// +// +// All +// +//////////////////////////////////////////////////////////////////////////////////////////// + +char input[256 + 1] = {0}; //tab39445 +//standard sam sound + +unsigned char wait1 = 7; +unsigned char wait2 = 6; + +unsigned char A, X, Y; +unsigned char mem44; +unsigned char mem47; +unsigned char mem49; +unsigned char mem39; +unsigned char mem50; +unsigned char mem51; +unsigned char mem53; +unsigned char mem56; +unsigned char mem59 = 0; + +unsigned char phonemeIndexOutput[60]; //tab47296 +unsigned char stressOutput[60]; //tab47365 +unsigned char phonemeLengthOutput[60]; //tab47416 + +// contains the soundbuffer position +int bufferpos; + +//////////////////////////////////////////////////////////////////////////////////////////// +// +// Sam Tabs +// +//////////////////////////////////////////////////////////////////////////////////////////// + +//tab40672 +const unsigned char stressInputTable[] = {'*', '1', '2', '3', '4', '5', '6', '7', '8'}; + +//tab40682 +const unsigned char signInputTable1[] = { + ' ', '.', '?', ',', '-', 'I', 'I', 'E', 'A', 'A', 'A', 'A', 'U', 'A', 'I', 'E', 'U', + 'O', 'R', 'L', 'W', 'Y', 'W', 'R', 'L', 'W', 'Y', 'M', 'N', 'N', 'D', 'Q', 'S', 'S', + 'F', 'T', '/', '/', 'Z', 'Z', 'V', 'D', 'C', '*', 'J', '*', '*', '*', 'E', 'A', 'O', + 'A', 'O', 'U', 'B', '*', '*', 'D', '*', '*', 'G', '*', '*', 'G', '*', '*', 'P', '*', + '*', 'T', '*', '*', 'K', '*', '*', 'K', '*', '*', 'U', 'U', 'U'}; + +//tab40763 +const unsigned char signInputTable2[] = { + '*', '*', '*', '*', '*', 'Y', 'H', 'H', 'E', 'A', 'H', 'O', 'H', 'X', 'X', 'R', 'X', + 'H', 'X', 'X', 'X', 'X', 'H', '*', '*', '*', '*', '*', '*', 'X', 'X', '*', '*', 'H', + '*', 'H', 'H', 'X', '*', 'H', '*', 'H', 'H', '*', '*', '*', '*', '*', 'Y', 'Y', 'Y', + 'W', 'W', 'W', '*', '*', '*', '*', '*', '*', '*', '*', '*', 'X', '*', '*', '*', '*', + '*', '*', '*', '*', '*', '*', '*', 'X', '*', '*', 'L', 'M', 'N'}; + +//loc_9F8C +const unsigned char flags[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0x84, 0x84, 0xA4, + 0xA4, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x44, 0x44, 0x44, 0x44, 0x44, 0x4C, + 0x4C, 0x4C, 0x48, 0x4C, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x44, 0x44, 0x44, 0x44, + 0x48, 0x40, 0x4C, 0x44, 0x00, 0x00, 0xB4, 0xB4, 0xB4, 0x94, 0x94, 0x94, 0x4E, 0x4E, + 0x4E, 0x4E, 0x4E, 0x4E, 0x4E, 0x4E, 0x4E, 0x4E, 0x4E, 0x4E, 0x4B, 0x4B, 0x4B, 0x4B, + 0x4B, 0x4B, 0x4B, 0x4B, 0x4B, 0x4B, 0x4B, 0x4B, 0x80, 0xC1, 0xC1 + +}; + +//??? flags overlap flags2 +//loc_9FDA +const unsigned char flags2[] = { + 0x80, 0xC1, 0xC1, 0xC1, 0xC1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x10, 0x10, 0x08, 0x0C, 0x08, 0x04, 0x40, + 0x24, 0x20, 0x20, 0x24, 0x00, 0x00, 0x24, 0x20, 0x20, 0x24, 0x20, 0x20, 0x00, 0x20, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x04, 0x04, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x04, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + +//tab45616??? +const unsigned char phonemeStressedLengthTable[] = { + 0x00, 0x12, 0x12, 0x12, 8, 0xB, 9, 0xB, 0xE, 0xF, 0xB, 0x10, 0xC, 6, 6, 0xE, + 0xC, 0xE, 0xC, 0xB, 8, 8, 0xB, 0xA, 9, 8, 8, 8, 8, 8, 3, 5, + 2, 2, 2, 2, 2, 2, 6, 6, 8, 6, 6, 2, 9, 4, 2, 1, + 0xE, 0xF, 0xF, 0xF, 0xE, 0xE, 8, 2, 2, 7, 2, 1, 7, 2, 2, 7, + 2, 2, 8, 2, 2, 6, 2, 2, 7, 2, 4, 7, 1, 4, 5, 5}; + +//tab45536??? +const unsigned char phonemeLengthTable[] = { + 0, 0x12, 0x12, 0x12, 8, 8, 8, 8, 8, 0xB, 6, 0xC, 0xA, 5, 5, 0xB, 0xA, 0xA, 0xA, 9, + 8, 7, 9, 7, 6, 8, 6, 7, 7, 7, 2, 5, 2, 2, 2, 2, 2, 2, 6, 6, + 7, 6, 6, 2, 8, 3, 1, 0x1E, 0xD, 0xC, 0xC, 0xC, 0xE, 9, 6, 1, 2, 5, 1, 1, + 6, 1, 2, 6, 1, 2, 8, 2, 2, 4, 2, 2, 6, 1, 4, 6, 1, 4, 0xC7, 0xFF}; + +/* + + Ind | phoneme | flags | + -----|---------|----------| + 0 | * | 00000000 | + 1 | .* | 00000000 | + 2 | ?* | 00000000 | + 3 | ,* | 00000000 | + 4 | -* | 00000000 | + + VOWELS + 5 | IY | 10100100 | + 6 | IH | 10100100 | + 7 | EH | 10100100 | + 8 | AE | 10100100 | + 9 | AA | 10100100 | + 10 | AH | 10100100 | + 11 | AO | 10000100 | + 17 | OH | 10000100 | + 12 | UH | 10000100 | + 16 | UX | 10000100 | + 15 | ER | 10000100 | + 13 | AX | 10100100 | + 14 | IX | 10100100 | + + DIPHTONGS + 48 | EY | 10110100 | + 49 | AY | 10110100 | + 50 | OY | 10110100 | + 51 | AW | 10010100 | + 52 | OW | 10010100 | + 53 | UW | 10010100 | + + + 21 | YX | 10000100 | + 20 | WX | 10000100 | + 18 | RX | 10000100 | + 19 | LX | 10000100 | + 37 | /X | 01000000 | + 30 | DX | 01001000 | + + + 22 | WH | 01000100 | + + + VOICED CONSONANTS + 23 | R* | 01000100 | + 24 | L* | 01000100 | + 25 | W* | 01000100 | + 26 | Y* | 01000100 | + 27 | M* | 01001100 | + 28 | N* | 01001100 | + 29 | NX | 01001100 | + 54 | B* | 01001110 | + 57 | D* | 01001110 | + 60 | G* | 01001110 | + 44 | J* | 01001100 | + 38 | Z* | 01000100 | + 39 | ZH | 01000100 | + 40 | V* | 01000100 | + 41 | DH | 01000100 | + + unvoiced CONSONANTS + 32 | S* | 01000000 | + 33 | SH | 01000000 | + 34 | F* | 01000000 | + 35 | TH | 01000000 | + 66 | P* | 01001011 | + 69 | T* | 01001011 | + 72 | K* | 01001011 | + 42 | CH | 01001000 | + 36 | /H | 01000000 | + + 43 | ** | 01000000 | + 45 | ** | 01000100 | + 46 | ** | 00000000 | + 47 | ** | 00000000 | + + + 55 | ** | 01001110 | + 56 | ** | 01001110 | + 58 | ** | 01001110 | + 59 | ** | 01001110 | + 61 | ** | 01001110 | + 62 | ** | 01001110 | + 63 | GX | 01001110 | + 64 | ** | 01001110 | + 65 | ** | 01001110 | + 67 | ** | 01001011 | + 68 | ** | 01001011 | + 70 | ** | 01001011 | + 71 | ** | 01001011 | + 73 | ** | 01001011 | + 74 | ** | 01001011 | + 75 | KX | 01001011 | + 76 | ** | 01001011 | + 77 | ** | 01001011 | + + + SPECIAL + 78 | UL | 10000000 | + 79 | UM | 11000001 | + 80 | UN | 11000001 | + 31 | Q* | 01001100 | + +*/ + +//////////////////////////////////////////////////////////////////////////////////////////// +// +// RenderTabs +// +//////////////////////////////////////////////////////////////////////////////////////////// + +const unsigned char tab48426[5] = {0x18, 0x1A, 0x17, 0x17, 0x17}; + +const unsigned char tab47492[] = {0, 0, 0xE0, 0xE6, 0xEC, 0xF3, 0xF9, 0, 6, 0xC, 6}; + +const unsigned char amplitudeRescale[] = { + 0, + 1, + 2, + 2, + 2, + 3, + 3, + 4, + 4, + 5, + 6, + 8, + 9, + 0xB, + 0xD, + 0xF, + 0 //17 elements? +}; + +// Used to decide which phoneme's blend lengths. The candidate with the lower score is selected. +// tab45856 +const unsigned char blendRank[] = {0, 0x1F, 0x1F, 0x1F, 0x1F, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 5, 5, 2, 0xA, 2, 8, + 5, 5, 0xB, 0xA, 9, 8, 8, 0xA0, 8, 8, + 0x17, 0x1F, 0x12, 0x12, 0x12, 0x12, 0x1E, 0x1E, 0x14, 0x14, + 0x14, 0x14, 0x17, 0x17, 0x1A, 0x1A, 0x1D, 0x1D, 2, 2, + 2, 2, 2, 2, 0x1A, 0x1D, 0x1B, 0x1A, 0x1D, 0x1B, + 0x1A, 0x1D, 0x1B, 0x1A, 0x1D, 0x1B, 0x17, 0x1D, 0x17, 0x17, + 0x1D, 0x17, 0x17, 0x1D, 0x17, 0x17, 0x1D, 0x17, 0x17, 0x17}; + +// Number of frames at the end of a phoneme devoted to interpolating to next phoneme's final value +//tab45696 +const unsigned char outBlendLength[] = {0, 2, 2, 2, 2, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 3, 2, 4, 4, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 2, 2, 2, 1, 0, 1, 0, 1, 0, 5, + 5, 5, 5, 5, 4, 4, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, + 0, 1, 2, 0, 2, 2, 0, 1, 3, 0, 2, 3, 0, 2, 0xA0, 0xA0}; + +// Number of frames at beginning of a phoneme devoted to interpolating to phoneme's final value +// tab45776 +const unsigned char inBlendLength[] = {0, 2, 2, 2, 2, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 3, 3, 4, 4, 3, 3, 3, 3, 3, 1, 2, 3, 2, 1, + 3, 3, 3, 3, 1, 1, 3, 3, 3, 2, 2, 3, 2, 3, 0, 0, + 5, 5, 5, 5, 4, 4, 2, 0, 2, 2, 0, 3, 2, 0, 4, 2, + 0, 3, 2, 0, 2, 2, 0, 2, 3, 0, 3, 3, 0, 3, 0xB0, 0xA0}; + +// Looks like it's used as bit flags +// High bits masked by 248 (11111000) +// +// 32: S* 241 11110001 +// 33: SH 226 11100010 +// 34: F* 211 11010011 +// 35: TH 187 10111011 +// 36: /H 124 01111100 +// 37: /X 149 10010101 +// 38: Z* 1 00000001 +// 39: ZH 2 00000010 +// 40: V* 3 00000011 +// 41: DH 3 00000011 +// 43: ** 114 01110010 +// 45: ** 2 00000010 +// 67: ** 27 00011011 +// 70: ** 25 00011001 +// tab45936 +const unsigned char sampledConsonantFlags[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xF1, 0xE2, 0xD3, 0xBB, 0x7C, 0x95, 1, 2, + 3, 3, 0, 0x72, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0x1B, 0, 0, 0x19, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + +//tab45056 +unsigned char freq1data[] = { + 0x00, 0x13, 0x13, 0x13, 0x13, 0xA, 0xE, 0x12, 0x18, 0x1A, 0x16, 0x14, 0x10, 0x14, 0xE, 0x12, + 0xE, 0x12, 0x12, 0x10, 0xC, 0xE, 0xA, 0x12, 0xE, 0xA, 8, 6, 6, 6, 6, 0x11, + 6, 6, 6, 6, 0xE, 0x10, 9, 0xA, 8, 0xA, 6, 6, 6, 5, 6, 0, + 0x12, 0x1A, 0x14, 0x1A, 0x12, 0xC, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 0xA, 0xA, 6, 6, 6, 0x2C, 0x13}; + +//tab451356 +unsigned char freq2data[] = {0x00, 0x43, 0x43, 0x43, 0x43, 0x54, 0x48, 0x42, 0x3E, 0x28, + 0x2C, 0x1E, 0x24, 0x2C, 0x48, 0x30, 0x24, 0x1E, 0x32, 0x24, + 0x1C, 0x44, 0x18, 0x32, 0x1E, 0x18, 0x52, 0x2E, 0x36, 0x56, + 0x36, 0x43, 0x49, 0x4F, 0x1A, 0x42, 0x49, 0x25, 0x33, 0x42, + 0x28, 0x2F, 0x4F, 0x4F, 0x42, 0x4F, 0x6E, 0x00, 0x48, 0x26, + 0x1E, 0x2A, 0x1E, 0x22, 0x1A, 0x1A, 0x1A, 0x42, 0x42, 0x42, + 0x6E, 0x6E, 0x6E, 0x54, 0x54, 0x54, 0x1A, 0x1A, 0x1A, 0x42, + 0x42, 0x42, 0x6D, 0x56, 0x6D, 0x54, 0x54, 0x54, 0x7F, 0x7F}; +//tab45216 +unsigned char freq3data[] = {0x00, 0x5B, 0x5B, 0x5B, 0x5B, 0x6E, 0x5D, 0x5B, 0x58, 0x59, + 0x57, 0x58, 0x52, 0x59, 0x5D, 0x3E, 0x52, 0x58, 0x3E, 0x6E, + 0x50, 0x5D, 0x5A, 0x3C, 0x6E, 0x5A, 0x6E, 0x51, 0x79, 0x65, + 0x79, 0x5B, 0x63, 0x6A, 0x51, 0x79, 0x5D, 0x52, 0x5D, 0x67, + 0x4C, 0x5D, 0x65, 0x65, 0x79, 0x65, 0x79, 0x00, 0x5A, 0x58, + 0x58, 0x58, 0x58, 0x52, 0x51, 0x51, 0x51, 0x79, 0x79, 0x79, + 0x70, 0x6E, 0x6E, 0x5E, 0x5E, 0x5E, 0x51, 0x51, 0x51, 0x79, + 0x79, 0x79, 0x65, 0x65, 0x70, 0x5E, 0x5E, 0x5E, 0x08, 0x01}; + +//////////////////////////////////////////////////////////////////////////////////////////// +// +// Reciter +// +//////////////////////////////////////////////////////////////////////////////////////////// + +unsigned char inputtemp[256]; // secure copy of input tab36096 + +//////////////////////////////////////////////////////////////////////////////////////////// +// +// Render +// +//////////////////////////////////////////////////////////////////////////////////////////// + +//timetable for more accurate c64 simulation +int timetable[5][5] = { + {162, 167, 167, 127, 128}, + {226, 60, 60, 0, 0}, + {225, 60, 59, 0, 0}, + {200, 0, 0, 54, 55}, + {199, 0, 0, 54, 54}}; + +unsigned oldtimetableindex; + +const unsigned char ampl1data[] = {0, 0, 0, 0, 0, 0xD, 0xD, 0xE, 0xF, 0xF, 0xF, 0xF, + 0xF, 0xC, 0xD, 0xC, 0xF, 0xF, 0xD, 0xD, 0xD, 0xE, 0xD, 0xC, + 0xD, 0xD, 0xD, 0xC, 9, 9, 0, 0, 0, 0, 0, 0, + 0, 0, 0xB, 0xB, 0xB, 0xB, 0, 0, 1, 0xB, 0, 2, + 0xE, 0xF, 0xF, 0xF, 0xF, 0xD, 2, 4, 0, 2, 4, 0, + 1, 4, 0, 1, 4, 0, 0, 0, 0, 0, 0, 0, + 0, 0xC, 0, 0, 0, 0, 0xF, 0xF}; + +const unsigned char ampl2data[] = { + 0, 0, 0, 0, 0, 0xA, 0xB, 0xD, 0xE, 0xD, 0xC, 0xC, 0xB, 9, 0xB, 0xB, 0xC, 0xC, 0xC, 8, + 8, 0xC, 8, 0xA, 8, 8, 0xA, 3, 9, 6, 0, 0, 0, 0, 0, 0, 0, 0, 3, 5, + 3, 4, 0, 0, 0, 5, 0xA, 2, 0xE, 0xD, 0xC, 0xD, 0xC, 8, 0, 1, 0, 0, 1, 0, + 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0xA, 0, 0, 0xA, 0, 0, 0}; + +const unsigned char ampl3data[] = {0, 0, 0, 0, 0, 8, 7, 8, 8, 1, 1, 0, 1, 0, 7, 5, + 1, 0, 6, 1, 0, 7, 0, 5, 1, 0, 8, 0, 0, 3, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0xE, 1, + 9, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 5, 0, 0x13, 0x10}; + +//tab42240 +const signed char sinus[256] = { + 0, 3, 6, 9, 12, 16, 19, 22, 25, 28, 31, 34, 37, 40, 43, 46, + 49, 51, 54, 57, 60, 63, 65, 68, 71, 73, 76, 78, 81, 83, 85, 88, + 90, 92, 94, 96, 98, 100, 102, 104, 106, 107, 109, 111, 112, 113, 115, 116, + 117, 118, 120, 121, 122, 122, 123, 124, 125, 125, 126, 126, 126, 127, 127, 127, + 127, 127, 127, 127, 126, 126, 126, 125, 125, 124, 123, 122, 122, 121, 120, 118, + 117, 116, 115, 113, 112, 111, 109, 107, 106, 104, 102, 100, 98, 96, 94, 92, + 90, 88, 85, 83, 81, 78, 76, 73, 71, 68, 65, 63, 60, 57, 54, 51, + 49, 46, 43, 40, 37, 34, 31, 28, 25, 22, 19, 16, 12, 9, 6, 3, + 0, -3, -6, -9, -12, -16, -19, -22, -25, -28, -31, -34, -37, -40, -43, -46, + -49, -51, -54, -57, -60, -63, -65, -68, -71, -73, -76, -78, -81, -83, -85, -88, + -90, -92, -94, -96, -98, -100, -102, -104, -106, -107, -109, -111, -112, -113, -115, -116, + -117, -118, -120, -121, -122, -122, -123, -124, -125, -125, -126, -126, -126, -127, -127, -127, + -127, -127, -127, -127, -126, -126, -126, -125, -125, -124, -123, -122, -122, -121, -120, -118, + -117, -116, -115, -113, -112, -111, -109, -107, -106, -104, -102, -100, -98, -96, -94, -92, + -90, -88, -85, -83, -81, -78, -76, -73, -71, -68, -65, -63, -60, -57, -54, -51, + -49, -46, -43, -40, -37, -34, -31, -28, -25, -22, -19, -16, -12, -9, -6, -3}; + +//tab42496 +const unsigned char rectangle[] = { + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, + 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, + 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, + 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, + 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, + 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, + 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, + 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, + 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, + 0x70}; + +//random data ? +const unsigned char sampleTable[0x500] = { + //00 + + 0x38, + 0x84, + 0x6B, + 0x19, + 0xC6, + 0x63, + 0x18, + 0x86, + 0x73, + 0x98, + 0xC6, + 0xB1, + 0x1C, + 0xCA, + 0x31, + 0x8C, + 0xC7, + 0x31, + 0x88, + 0xC2, + 0x30, + 0x98, + 0x46, + 0x31, + 0x18, + 0xC6, + 0x35, + 0xC, + 0xCA, + 0x31, + 0xC, + 0xC6 + //20 + , + 0x21, + 0x10, + 0x24, + 0x69, + 0x12, + 0xC2, + 0x31, + 0x14, + 0xC4, + 0x71, + 8, + 0x4A, + 0x22, + 0x49, + 0xAB, + 0x6A, + 0xA8, + 0xAC, + 0x49, + 0x51, + 0x32, + 0xD5, + 0x52, + 0x88, + 0x93, + 0x6C, + 0x94, + 0x22, + 0x15, + 0x54, + 0xD2, + 0x25 + //40 + , + 0x96, + 0xD4, + 0x50, + 0xA5, + 0x46, + 0x21, + 8, + 0x85, + 0x6B, + 0x18, + 0xC4, + 0x63, + 0x10, + 0xCE, + 0x6B, + 0x18, + 0x8C, + 0x71, + 0x19, + 0x8C, + 0x63, + 0x35, + 0xC, + 0xC6, + 0x33, + 0x99, + 0xCC, + 0x6C, + 0xB5, + 0x4E, + 0xA2, + 0x99 + //60 + , + 0x46, + 0x21, + 0x28, + 0x82, + 0x95, + 0x2E, + 0xE3, + 0x30, + 0x9C, + 0xC5, + 0x30, + 0x9C, + 0xA2, + 0xB1, + 0x9C, + 0x67, + 0x31, + 0x88, + 0x66, + 0x59, + 0x2C, + 0x53, + 0x18, + 0x84, + 0x67, + 0x50, + 0xCA, + 0xE3, + 0xA, + 0xAC, + 0xAB, + 0x30 + //80 + , + 0xAC, + 0x62, + 0x30, + 0x8C, + 0x63, + 0x10, + 0x94, + 0x62, + 0xB1, + 0x8C, + 0x82, + 0x28, + 0x96, + 0x33, + 0x98, + 0xD6, + 0xB5, + 0x4C, + 0x62, + 0x29, + 0xA5, + 0x4A, + 0xB5, + 0x9C, + 0xC6, + 0x31, + 0x14, + 0xD6, + 0x38, + 0x9C, + 0x4B, + 0xB4 + //A0 + , + 0x86, + 0x65, + 0x18, + 0xAE, + 0x67, + 0x1C, + 0xA6, + 0x63, + 0x19, + 0x96, + 0x23, + 0x19, + 0x84, + 0x13, + 8, + 0xA6, + 0x52, + 0xAC, + 0xCA, + 0x22, + 0x89, + 0x6E, + 0xAB, + 0x19, + 0x8C, + 0x62, + 0x34, + 0xC4, + 0x62, + 0x19, + 0x86, + 0x63 + //C0 + , + 0x18, + 0xC4, + 0x23, + 0x58, + 0xD6, + 0xA3, + 0x50, + 0x42, + 0x54, + 0x4A, + 0xAD, + 0x4A, + 0x25, + 0x11, + 0x6B, + 0x64, + 0x89, + 0x4A, + 0x63, + 0x39, + 0x8A, + 0x23, + 0x31, + 0x2A, + 0xEA, + 0xA2, + 0xA9, + 0x44, + 0xC5, + 0x12, + 0xCD, + 0x42 + //E0 + , + 0x34, + 0x8C, + 0x62, + 0x18, + 0x8C, + 0x63, + 0x11, + 0x48, + 0x66, + 0x31, + 0x9D, + 0x44, + 0x33, + 0x1D, + 0x46, + 0x31, + 0x9C, + 0xC6, + 0xB1, + 0xC, + 0xCD, + 0x32, + 0x88, + 0xC4, + 0x73, + 0x18, + 0x86, + 0x73, + 8, + 0xD6, + 0x63, + 0x58 + //100 + , + 7, + 0x81, + 0xE0, + 0xF0, + 0x3C, + 7, + 0x87, + 0x90, + 0x3C, + 0x7C, + 0xF, + 0xC7, + 0xC0, + 0xC0, + 0xF0, + 0x7C, + 0x1E, + 7, + 0x80, + 0x80, + 0, + 0x1C, + 0x78, + 0x70, + 0xF1, + 0xC7, + 0x1F, + 0xC0, + 0xC, + 0xFE, + 0x1C, + 0x1F + //120 + , + 0x1F, + 0xE, + 0xA, + 0x7A, + 0xC0, + 0x71, + 0xF2, + 0x83, + 0x8F, + 3, + 0xF, + 0xF, + 0xC, + 0, + 0x79, + 0xF8, + 0x61, + 0xE0, + 0x43, + 0xF, + 0x83, + 0xE7, + 0x18, + 0xF9, + 0xC1, + 0x13, + 0xDA, + 0xE9, + 0x63, + 0x8F, + 0xF, + 0x83 + //140 + , + 0x83, + 0x87, + 0xC3, + 0x1F, + 0x3C, + 0x70, + 0xF0, + 0xE1, + 0xE1, + 0xE3, + 0x87, + 0xB8, + 0x71, + 0xE, + 0x20, + 0xE3, + 0x8D, + 0x48, + 0x78, + 0x1C, + 0x93, + 0x87, + 0x30, + 0xE1, + 0xC1, + 0xC1, + 0xE4, + 0x78, + 0x21, + 0x83, + 0x83, + 0xC3 + //160 + , + 0x87, + 6, + 0x39, + 0xE5, + 0xC3, + 0x87, + 7, + 0xE, + 0x1C, + 0x1C, + 0x70, + 0xF4, + 0x71, + 0x9C, + 0x60, + 0x36, + 0x32, + 0xC3, + 0x1E, + 0x3C, + 0xF3, + 0x8F, + 0xE, + 0x3C, + 0x70, + 0xE3, + 0xC7, + 0x8F, + 0xF, + 0xF, + 0xE, + 0x3C + //180 + , + 0x78, + 0xF0, + 0xE3, + 0x87, + 6, + 0xF0, + 0xE3, + 7, + 0xC1, + 0x99, + 0x87, + 0xF, + 0x18, + 0x78, + 0x70, + 0x70, + 0xFC, + 0xF3, + 0x10, + 0xB1, + 0x8C, + 0x8C, + 0x31, + 0x7C, + 0x70, + 0xE1, + 0x86, + 0x3C, + 0x64, + 0x6C, + 0xB0, + 0xE1 + //1A0 + , + 0xE3, + 0xF, + 0x23, + 0x8F, + 0xF, + 0x1E, + 0x3E, + 0x38, + 0x3C, + 0x38, + 0x7B, + 0x8F, + 7, + 0xE, + 0x3C, + 0xF4, + 0x17, + 0x1E, + 0x3C, + 0x78, + 0xF2, + 0x9E, + 0x72, + 0x49, + 0xE3, + 0x25, + 0x36, + 0x38, + 0x58, + 0x39, + 0xE2, + 0xDE + //1C0 + , + 0x3C, + 0x78, + 0x78, + 0xE1, + 0xC7, + 0x61, + 0xE1, + 0xE1, + 0xB0, + 0xF0, + 0xF0, + 0xC3, + 0xC7, + 0xE, + 0x38, + 0xC0, + 0xF0, + 0xCE, + 0x73, + 0x73, + 0x18, + 0x34, + 0xB0, + 0xE1, + 0xC7, + 0x8E, + 0x1C, + 0x3C, + 0xF8, + 0x38, + 0xF0, + 0xE1 + //1E0 + , + 0xC1, + 0x8B, + 0x86, + 0x8F, + 0x1C, + 0x78, + 0x70, + 0xF0, + 0x78, + 0xAC, + 0xB1, + 0x8F, + 0x39, + 0x31, + 0xDB, + 0x38, + 0x61, + 0xC3, + 0xE, + 0xE, + 0x38, + 0x78, + 0x73, + 0x17, + 0x1E, + 0x39, + 0x1E, + 0x38, + 0x64, + 0xE1, + 0xF1, + 0xC1 + //200 + , + 0x4E, + 0xF, + 0x40, + 0xA2, + 2, + 0xC5, + 0x8F, + 0x81, + 0xA1, + 0xFC, + 0x12, + 8, + 0x64, + 0xE0, + 0x3C, + 0x22, + 0xE0, + 0x45, + 7, + 0x8E, + 0xC, + 0x32, + 0x90, + 0xF0, + 0x1F, + 0x20, + 0x49, + 0xE0, + 0xF8, + 0xC, + 0x60, + 0xF0 + //220 + , + 0x17, + 0x1A, + 0x41, + 0xAA, + 0xA4, + 0xD0, + 0x8D, + 0x12, + 0x82, + 0x1E, + 0x1E, + 3, + 0xF8, + 0x3E, + 3, + 0xC, + 0x73, + 0x80, + 0x70, + 0x44, + 0x26, + 3, + 0x24, + 0xE1, + 0x3E, + 4, + 0x4E, + 4, + 0x1C, + 0xC1, + 9, + 0xCC + //240 + , + 0x9E, + 0x90, + 0x21, + 7, + 0x90, + 0x43, + 0x64, + 0xC0, + 0xF, + 0xC6, + 0x90, + 0x9C, + 0xC1, + 0x5B, + 3, + 0xE2, + 0x1D, + 0x81, + 0xE0, + 0x5E, + 0x1D, + 3, + 0x84, + 0xB8, + 0x2C, + 0xF, + 0x80, + 0xB1, + 0x83, + 0xE0, + 0x30, + 0x41 + //260 + , + 0x1E, + 0x43, + 0x89, + 0x83, + 0x50, + 0xFC, + 0x24, + 0x2E, + 0x13, + 0x83, + 0xF1, + 0x7C, + 0x4C, + 0x2C, + 0xC9, + 0xD, + 0x83, + 0xB0, + 0xB5, + 0x82, + 0xE4, + 0xE8, + 6, + 0x9C, + 7, + 0xA0, + 0x99, + 0x1D, + 7, + 0x3E, + 0x82, + 0x8F + //280 + , + 0x70, + 0x30, + 0x74, + 0x40, + 0xCA, + 0x10, + 0xE4, + 0xE8, + 0xF, + 0x92, + 0x14, + 0x3F, + 6, + 0xF8, + 0x84, + 0x88, + 0x43, + 0x81, + 0xA, + 0x34, + 0x39, + 0x41, + 0xC6, + 0xE3, + 0x1C, + 0x47, + 3, + 0xB0, + 0xB8, + 0x13, + 0xA, + 0xC2 + //2A0 + , + 0x64, + 0xF8, + 0x18, + 0xF9, + 0x60, + 0xB3, + 0xC0, + 0x65, + 0x20, + 0x60, + 0xA6, + 0x8C, + 0xC3, + 0x81, + 0x20, + 0x30, + 0x26, + 0x1E, + 0x1C, + 0x38, + 0xD3, + 1, + 0xB0, + 0x26, + 0x40, + 0xF4, + 0xB, + 0xC3, + 0x42, + 0x1F, + 0x85, + 0x32 + //2C0 + , + 0x26, + 0x60, + 0x40, + 0xC9, + 0xCB, + 1, + 0xEC, + 0x11, + 0x28, + 0x40, + 0xFA, + 4, + 0x34, + 0xE0, + 0x70, + 0x4C, + 0x8C, + 0x1D, + 7, + 0x69, + 3, + 0x16, + 0xC8, + 4, + 0x23, + 0xE8, + 0xC6, + 0x9A, + 0xB, + 0x1A, + 3, + 0xE0 + //2E0 + , + 0x76, + 6, + 5, + 0xCF, + 0x1E, + 0xBC, + 0x58, + 0x31, + 0x71, + 0x66, + 0, + 0xF8, + 0x3F, + 4, + 0xFC, + 0xC, + 0x74, + 0x27, + 0x8A, + 0x80, + 0x71, + 0xC2, + 0x3A, + 0x26, + 6, + 0xC0, + 0x1F, + 5, + 0xF, + 0x98, + 0x40, + 0xAE + //300 + , + 1, + 0x7F, + 0xC0, + 7, + 0xFF, + 0, + 0xE, + 0xFE, + 0, + 3, + 0xDF, + 0x80, + 3, + 0xEF, + 0x80, + 0x1B, + 0xF1, + 0xC2, + 0, + 0xE7, + 0xE0, + 0x18, + 0xFC, + 0xE0, + 0x21, + 0xFC, + 0x80, + 0x3C, + 0xFC, + 0x40, + 0xE, + 0x7E + //320 + , + 0, + 0x3F, + 0x3E, + 0, + 0xF, + 0xFE, + 0, + 0x1F, + 0xFF, + 0, + 0x3E, + 0xF0, + 7, + 0xFC, + 0, + 0x7E, + 0x10, + 0x3F, + 0xFF, + 0, + 0x3F, + 0x38, + 0xE, + 0x7C, + 1, + 0x87, + 0xC, + 0xFC, + 0xC7, + 0, + 0x3E, + 4 + //340 + , + 0xF, + 0x3E, + 0x1F, + 0xF, + 0xF, + 0x1F, + 0xF, + 2, + 0x83, + 0x87, + 0xCF, + 3, + 0x87, + 0xF, + 0x3F, + 0xC0, + 7, + 0x9E, + 0x60, + 0x3F, + 0xC0, + 3, + 0xFE, + 0, + 0x3F, + 0xE0, + 0x77, + 0xE1, + 0xC0, + 0xFE, + 0xE0, + 0xC3 + //360 + , + 0xE0, + 1, + 0xDF, + 0xF8, + 3, + 7, + 0, + 0x7E, + 0x70, + 0, + 0x7C, + 0x38, + 0x18, + 0xFE, + 0xC, + 0x1E, + 0x78, + 0x1C, + 0x7C, + 0x3E, + 0xE, + 0x1F, + 0x1E, + 0x1E, + 0x3E, + 0, + 0x7F, + 0x83, + 7, + 0xDB, + 0x87, + 0x83 + //380 + , + 7, + 0xC7, + 7, + 0x10, + 0x71, + 0xFF, + 0, + 0x3F, + 0xE2, + 1, + 0xE0, + 0xC1, + 0xC3, + 0xE1, + 0, + 0x7F, + 0xC0, + 5, + 0xF0, + 0x20, + 0xF8, + 0xF0, + 0x70, + 0xFE, + 0x78, + 0x79, + 0xF8, + 2, + 0x3F, + 0xC, + 0x8F, + 3 + //3a0 + , + 0xF, + 0x9F, + 0xE0, + 0xC1, + 0xC7, + 0x87, + 3, + 0xC3, + 0xC3, + 0xB0, + 0xE1, + 0xE1, + 0xC1, + 0xE3, + 0xE0, + 0x71, + 0xF0, + 0, + 0xFC, + 0x70, + 0x7C, + 0xC, + 0x3E, + 0x38, + 0xE, + 0x1C, + 0x70, + 0xC3, + 0xC7, + 3, + 0x81, + 0xC1 + //3c0 + , + 0xC7, + 0xE7, + 0, + 0xF, + 0xC7, + 0x87, + 0x19, + 9, + 0xEF, + 0xC4, + 0x33, + 0xE0, + 0xC1, + 0xFC, + 0xF8, + 0x70, + 0xF0, + 0x78, + 0xF8, + 0xF0, + 0x61, + 0xC7, + 0, + 0x1F, + 0xF8, + 1, + 0x7C, + 0xF8, + 0xF0, + 0x78, + 0x70, + 0x3C + //3e0 + , + 0x7C, + 0xCE, + 0xE, + 0x21, + 0x83, + 0xCF, + 8, + 7, + 0x8F, + 8, + 0xC1, + 0x87, + 0x8F, + 0x80, + 0xC7, + 0xE3, + 0, + 7, + 0xF8, + 0xE0, + 0xEF, + 0, + 0x39, + 0xF7, + 0x80, + 0xE, + 0xF8, + 0xE1, + 0xE3, + 0xF8, + 0x21, + 0x9F + //400 + , + 0xC0, + 0xFF, + 3, + 0xF8, + 7, + 0xC0, + 0x1F, + 0xF8, + 0xC4, + 4, + 0xFC, + 0xC4, + 0xC1, + 0xBC, + 0x87, + 0xF0, + 0xF, + 0xC0, + 0x7F, + 5, + 0xE0, + 0x25, + 0xEC, + 0xC0, + 0x3E, + 0x84, + 0x47, + 0xF0, + 0x8E, + 3, + 0xF8, + 3 + //420 + , + 0xFB, + 0xC0, + 0x19, + 0xF8, + 7, + 0x9C, + 0xC, + 0x17, + 0xF8, + 7, + 0xE0, + 0x1F, + 0xA1, + 0xFC, + 0xF, + 0xFC, + 1, + 0xF0, + 0x3F, + 0, + 0xFE, + 3, + 0xF0, + 0x1F, + 0, + 0xFD, + 0, + 0xFF, + 0x88, + 0xD, + 0xF9, + 1 + //440 + , + 0xFF, + 0, + 0x70, + 7, + 0xC0, + 0x3E, + 0x42, + 0xF3, + 0xD, + 0xC4, + 0x7F, + 0x80, + 0xFC, + 7, + 0xF0, + 0x5E, + 0xC0, + 0x3F, + 0, + 0x78, + 0x3F, + 0x81, + 0xFF, + 1, + 0xF8, + 1, + 0xC3, + 0xE8, + 0xC, + 0xE4, + 0x64, + 0x8F + ////460 + , + 0xE4, + 0xF, + 0xF0, + 7, + 0xF0, + 0xC2, + 0x1F, + 0, + 0x7F, + 0xC0, + 0x6F, + 0x80, + 0x7E, + 3, + 0xF8, + 7, + 0xF0, + 0x3F, + 0xC0, + 0x78, + 0xF, + 0x82, + 7, + 0xFE, + 0x22, + 0x77, + 0x70, + 2, + 0x76, + 3, + 0xFE, + 0 + //480 + , + 0xFE, + 0x67, + 0, + 0x7C, + 0xC7, + 0xF1, + 0x8E, + 0xC6, + 0x3B, + 0xE0, + 0x3F, + 0x84, + 0xF3, + 0x19, + 0xD8, + 3, + 0x99, + 0xFC, + 9, + 0xB8, + 0xF, + 0xF8, + 0, + 0x9D, + 0x24, + 0x61, + 0xF9, + 0xD, + 0, + 0xFD, + 3, + 0xF0 + //4a0 + , + 0x1F, + 0x90, + 0x3F, + 1, + 0xF8, + 0x1F, + 0xD0, + 0xF, + 0xF8, + 0x37, + 1, + 0xF8, + 7, + 0xF0, + 0xF, + 0xC0, + 0x3F, + 0, + 0xFE, + 3, + 0xF8, + 0xF, + 0xC0, + 0x3F, + 0, + 0xFA, + 3, + 0xF0, + 0xF, + 0x80, + 0xFF, + 1 + //4c0 + , + 0xB8, + 7, + 0xF0, + 1, + 0xFC, + 1, + 0xBC, + 0x80, + 0x13, + 0x1E, + 0, + 0x7F, + 0xE1, + 0x40, + 0x7F, + 0xA0, + 0x7F, + 0xB0, + 0, + 0x3F, + 0xC0, + 0x1F, + 0xC0, + 0x38, + 0xF, + 0xF0, + 0x1F, + 0x80, + 0xFF, + 1, + 0xFC, + 3 + //4e0 + , + 0xF1, + 0x7E, + 1, + 0xFE, + 1, + 0xF0, + 0xFF, + 0, + 0x7F, + 0xC0, + 0x1D, + 7, + 0xF0, + 0xF, + 0xC0, + 0x7E, + 6, + 0xE0, + 7, + 0xE0, + 0xF, + 0xF8, + 6, + 0xC1, + 0xFE, + 1, + 0xFC, + 3, + 0xE0, + 0xF, + 0, + 0xFC}; + +//////////////////////////////////////////////////////////////////////////////////////////// +// +// Render +// +//////////////////////////////////////////////////////////////////////////////////////////// + +unsigned char pitches[256]; // tab43008 + +unsigned char frequency1[256]; +unsigned char frequency2[256]; +unsigned char frequency3[256]; + +unsigned char amplitude1[256]; +unsigned char amplitude2[256]; +unsigned char amplitude3[256]; + +unsigned char sampledConsonantFlag[256]; // tab44800 + +//////////////////////////////////////////////////////////////////////////////////////////// +// +// Sam +// +//////////////////////////////////////////////////////////////////////////////////////////// + +unsigned char stress[256]; //numbers from 0 to 8 +unsigned char phonemeLength[256]; //tab40160 +unsigned char phonemeindex[256]; + +//////////////////////////////////////////////////////////////////////////////////////////// +// +// ReciterTabs +// +//////////////////////////////////////////////////////////////////////////////////////////// + +//some flags +const unsigned char tab36376[] = { + 0, 0, 0, 0, 0, 0, 0, 0, // 0-7 + 0, 0, 0, 0, 0, 0, 0, 0, // 8-15 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 2, 2, 2, 2, 2, 2, 130, // ' ', '!' + 0, 0, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 2, 2, 2, 2, 2, 2, 2, 192, 168, 176, 172, 192, 160, 184, // '@', 'A' + 160, 192, 188, 160, 172, 168, 172, 192, 160, 160, 172, 180, 164, 192, 168, 168, + 176, 192, 188, 0, 0, 0, 2, 0, // 'X', 'Y', 'Z', '[', + 32, 32, 155, 32, 192, 185, 32, 205, 163, 76, 138, 142}; + +const unsigned char rules[] = { + ']', 'A' | 0x80, ' ', '(', 'A', '.', ')', '=', + 'E', 'H', '4', 'Y', '.', ' ' | 0x80, '(', 'A', + ')', ' ', '=', 'A', 'H' | 0x80, ' ', '(', 'A', + 'R', 'E', ')', ' ', '=', 'A', 'A', 'R' | 0x80, + ' ', '(', 'A', 'R', ')', 'O', '=', 'A', + 'X', 'R' | 0x80, '(', 'A', 'R', ')', '#', '=', + 'E', 'H', '4', 'R' | 0x80, ' ', '^', '(', 'A', + 'S', ')', '#', '=', 'E', 'Y', '4', 'S' | 0x80, + '(', 'A', ')', 'W', 'A', '=', 'A', 'X' | 0x80, + '(', 'A', 'W', ')', '=', 'A', 'O', '5' | 0x80, + ' ', ':', '(', 'A', 'N', 'Y', ')', '=', + 'E', 'H', '4', 'N', 'I', 'Y' | 0x80, '(', 'A', + ')', '^', '+', '#', '=', 'E', 'Y', '5' | 0x80, + '#', ':', '(', 'A', 'L', 'L', 'Y', ')', + '=', 'U', 'L', 'I', 'Y' | 0x80, ' ', '(', 'A', + 'L', ')', '#', '=', 'U', 'L' | 0x80, '(', 'A', + 'G', 'A', 'I', 'N', ')', '=', 'A', 'X', + 'G', 'E', 'H', '4', 'N' | 0x80, '#', ':', '(', + 'A', 'G', ')', 'E', '=', 'I', 'H', 'J' | 0x80, + '(', 'A', ')', '^', '%', '=', 'E', 'Y' | 0x80, + '(', 'A', ')', '^', '+', ':', '#', '=', + 'A', 'E' | 0x80, ' ', ':', '(', 'A', ')', '^', + '+', ' ', '=', 'E', 'Y', '4' | 0x80, ' ', '(', + 'A', 'R', 'R', ')', '=', 'A', 'X', 'R' | 0x80, + '(', 'A', 'R', 'R', ')', '=', 'A', 'E', + '4', 'R' | 0x80, ' ', '^', '(', 'A', 'R', ')', + ' ', '=', 'A', 'A', '5', 'R' | 0x80, '(', 'A', + 'R', ')', '=', 'A', 'A', '5', 'R' | 0x80, '(', + 'A', 'I', 'R', ')', '=', 'E', 'H', '4', + 'R' | 0x80, '(', 'A', 'I', ')', '=', 'E', 'Y', + '4' | 0x80, '(', 'A', 'Y', ')', '=', 'E', 'Y', + '5' | 0x80, '(', 'A', 'U', ')', '=', 'A', 'O', + '4' | 0x80, '#', ':', '(', 'A', 'L', ')', ' ', + '=', 'U', 'L' | 0x80, '#', ':', '(', 'A', 'L', + 'S', ')', ' ', '=', 'U', 'L', 'Z' | 0x80, '(', + 'A', 'L', 'K', ')', '=', 'A', 'O', '4', + 'K' | 0x80, '(', 'A', 'L', ')', '^', '=', 'A', + 'O', 'L' | 0x80, ' ', ':', '(', 'A', 'B', 'L', + 'E', ')', '=', 'E', 'Y', '4', 'B', 'U', + 'L' | 0x80, '(', 'A', 'B', 'L', 'E', ')', '=', + 'A', 'X', 'B', 'U', 'L' | 0x80, '(', 'A', ')', + 'V', 'O', '=', 'E', 'Y', '4' | 0x80, '(', 'A', + 'N', 'G', ')', '+', '=', 'E', 'Y', '4', + 'N', 'J' | 0x80, '(', 'A', 'T', 'A', 'R', 'I', + ')', '=', 'A', 'H', 'T', 'A', 'A', '4', + 'R', 'I', 'Y' | 0x80, '(', 'A', ')', 'T', 'O', + 'M', '=', 'A', 'E' | 0x80, '(', 'A', ')', 'T', + 'T', 'I', '=', 'A', 'E' | 0x80, ' ', '(', 'A', + 'T', ')', ' ', '=', 'A', 'E', 'T' | 0x80, ' ', + '(', 'A', ')', 'T', '=', 'A', 'H' | 0x80, '(', + 'A', ')', '=', 'A', 'E' | 0x80, + + ']', 'B' | 0x80, ' ', '(', 'B', ')', ' ', '=', + 'B', 'I', 'Y', '4' | 0x80, ' ', '(', 'B', 'E', + ')', '^', '#', '=', 'B', 'I', 'H' | 0x80, '(', + 'B', 'E', 'I', 'N', 'G', ')', '=', 'B', + 'I', 'Y', '4', 'I', 'H', 'N', 'X' | 0x80, ' ', + '(', 'B', 'O', 'T', 'H', ')', ' ', '=', + 'B', 'O', 'W', '4', 'T', 'H' | 0x80, ' ', '(', + 'B', 'U', 'S', ')', '#', '=', 'B', 'I', + 'H', '4', 'Z' | 0x80, '(', 'B', 'R', 'E', 'A', + 'K', ')', '=', 'B', 'R', 'E', 'Y', '5', + 'K' | 0x80, '(', 'B', 'U', 'I', 'L', ')', '=', + 'B', 'I', 'H', '4', 'L' | 0x80, '(', 'B', ')', + '=', 'B' | 0x80, + + ']', 'C' | 0x80, ' ', '(', 'C', ')', ' ', '=', + 'S', 'I', 'Y', '4' | 0x80, ' ', '(', 'C', 'H', + ')', '^', '=', 'K' | 0x80, '^', 'E', '(', 'C', + 'H', ')', '=', 'K' | 0x80, '(', 'C', 'H', 'A', + ')', 'R', '#', '=', 'K', 'E', 'H', '5' | 0x80, + '(', 'C', 'H', ')', '=', 'C', 'H' | 0x80, ' ', + 'S', '(', 'C', 'I', ')', '#', '=', 'S', + 'A', 'Y', '4' | 0x80, '(', 'C', 'I', ')', 'A', + '=', 'S', 'H' | 0x80, '(', 'C', 'I', ')', 'O', + '=', 'S', 'H' | 0x80, '(', 'C', 'I', ')', 'E', + 'N', '=', 'S', 'H' | 0x80, '(', 'C', 'I', 'T', + 'Y', ')', '=', 'S', 'I', 'H', 'T', 'I', + 'Y' | 0x80, '(', 'C', ')', '+', '=', 'S' | 0x80, '(', + 'C', 'K', ')', '=', 'K' | 0x80, '(', 'C', 'O', + 'M', 'M', 'O', 'D', 'O', 'R', 'E', ')', + '=', 'K', 'A', 'A', '4', 'M', 'A', 'H', + 'D', 'O', 'H', 'R' | 0x80, '(', 'C', 'O', 'M', + ')', '=', 'K', 'A', 'H', 'M' | 0x80, '(', 'C', + 'U', 'I', 'T', ')', '=', 'K', 'I', 'H', + 'T' | 0x80, '(', 'C', 'R', 'E', 'A', ')', '=', + 'K', 'R', 'I', 'Y', 'E', 'Y' | 0x80, '(', 'C', + ')', '=', 'K' | 0x80, + + ']', 'D' | 0x80, ' ', '(', 'D', ')', ' ', '=', + 'D', 'I', 'Y', '4' | 0x80, ' ', '(', 'D', 'R', + '.', ')', ' ', '=', 'D', 'A', 'A', '4', + 'K', 'T', 'E', 'R' | 0x80, '#', ':', '(', 'D', + 'E', 'D', ')', ' ', '=', 'D', 'I', 'H', + 'D' | 0x80, '.', 'E', '(', 'D', ')', ' ', '=', + 'D' | 0x80, '#', ':', '^', 'E', '(', 'D', ')', + ' ', '=', 'T' | 0x80, ' ', '(', 'D', 'E', ')', + '^', '#', '=', 'D', 'I', 'H' | 0x80, ' ', '(', + 'D', 'O', ')', ' ', '=', 'D', 'U', 'W' | 0x80, + ' ', '(', 'D', 'O', 'E', 'S', ')', '=', + 'D', 'A', 'H', 'Z' | 0x80, '(', 'D', 'O', 'N', + 'E', ')', ' ', '=', 'D', 'A', 'H', '5', + 'N' | 0x80, '(', 'D', 'O', 'I', 'N', 'G', ')', + '=', 'D', 'U', 'W', '4', 'I', 'H', 'N', + 'X' | 0x80, ' ', '(', 'D', 'O', 'W', ')', '=', + 'D', 'A', 'W' | 0x80, '#', '(', 'D', 'U', ')', + 'A', '=', 'J', 'U', 'W' | 0x80, '#', '(', 'D', + 'U', ')', '^', '#', '=', 'J', 'A', 'X' | 0x80, + '(', 'D', ')', '=', 'D' | 0x80, + + ']', 'E' | 0x80, ' ', '(', 'E', ')', ' ', '=', + 'I', 'Y', 'I', 'Y', '4' | 0x80, '#', ':', '(', + 'E', ')', ' ', '=' | 0x80, '\'', ':', '^', '(', + 'E', ')', ' ', '=' | 0x80, ' ', ':', '(', 'E', + ')', ' ', '=', 'I', 'Y' | 0x80, '#', '(', 'E', + 'D', ')', ' ', '=', 'D' | 0x80, '#', ':', '(', + 'E', ')', 'D', ' ', '=' | 0x80, '(', 'E', 'V', + ')', 'E', 'R', '=', 'E', 'H', '4', 'V' | 0x80, + '(', 'E', ')', '^', '%', '=', 'I', 'Y', + '4' | 0x80, '(', 'E', 'R', 'I', ')', '#', '=', + 'I', 'Y', '4', 'R', 'I', 'Y' | 0x80, '(', 'E', + 'R', 'I', ')', '=', 'E', 'H', '4', 'R', + 'I', 'H' | 0x80, '#', ':', '(', 'E', 'R', ')', + '#', '=', 'E', 'R' | 0x80, '(', 'E', 'R', 'R', + 'O', 'R', ')', '=', 'E', 'H', '4', 'R', + 'O', 'H', 'R' | 0x80, '(', 'E', 'R', 'A', 'S', + 'E', ')', '=', 'I', 'H', 'R', 'E', 'Y', + '5', 'S' | 0x80, '(', 'E', 'R', ')', '#', '=', + 'E', 'H', 'R' | 0x80, '(', 'E', 'R', ')', '=', + 'E', 'R' | 0x80, ' ', '(', 'E', 'V', 'E', 'N', + ')', '=', 'I', 'Y', 'V', 'E', 'H', 'N' | 0x80, + '#', ':', '(', 'E', ')', 'W', '=' | 0x80, '@', + '(', 'E', 'W', ')', '=', 'U', 'W' | 0x80, '(', + 'E', 'W', ')', '=', 'Y', 'U', 'W' | 0x80, '(', + 'E', ')', 'O', '=', 'I', 'Y' | 0x80, '#', ':', + '&', '(', 'E', 'S', ')', ' ', '=', 'I', + 'H', 'Z' | 0x80, '#', ':', '(', 'E', ')', 'S', + ' ', '=' | 0x80, '#', ':', '(', 'E', 'L', 'Y', + ')', ' ', '=', 'L', 'I', 'Y' | 0x80, '#', ':', + '(', 'E', 'M', 'E', 'N', 'T', ')', '=', + 'M', 'E', 'H', 'N', 'T' | 0x80, '(', 'E', 'F', + 'U', 'L', ')', '=', 'F', 'U', 'H', 'L' | 0x80, + '(', 'E', 'E', ')', '=', 'I', 'Y', '4' | 0x80, + '(', 'E', 'A', 'R', 'N', ')', '=', 'E', + 'R', '5', 'N' | 0x80, ' ', '(', 'E', 'A', 'R', + ')', '^', '=', 'E', 'R', '5' | 0x80, '(', 'E', + 'A', 'D', ')', '=', 'E', 'H', 'D' | 0x80, '#', + ':', '(', 'E', 'A', ')', ' ', '=', 'I', + 'Y', 'A', 'X' | 0x80, '(', 'E', 'A', ')', 'S', + 'U', '=', 'E', 'H', '5' | 0x80, '(', 'E', 'A', + ')', '=', 'I', 'Y', '5' | 0x80, '(', 'E', 'I', + 'G', 'H', ')', '=', 'E', 'Y', '4' | 0x80, '(', + 'E', 'I', ')', '=', 'I', 'Y', '4' | 0x80, ' ', + '(', 'E', 'Y', 'E', ')', '=', 'A', 'Y', + '4' | 0x80, '(', 'E', 'Y', ')', '=', 'I', 'Y' | 0x80, + '(', 'E', 'U', ')', '=', 'Y', 'U', 'W', + '5' | 0x80, '(', 'E', 'Q', 'U', 'A', 'L', ')', + '=', 'I', 'Y', '4', 'K', 'W', 'U', 'L' | 0x80, + '(', 'E', ')', '=', 'E', 'H' | 0x80, + + ']', 'F' | 0x80, ' ', '(', 'F', ')', ' ', '=', + 'E', 'H', '4', 'F' | 0x80, '(', 'F', 'U', 'L', + ')', '=', 'F', 'U', 'H', 'L' | 0x80, '(', 'F', + 'R', 'I', 'E', 'N', 'D', ')', '=', 'F', + 'R', 'E', 'H', '5', 'N', 'D' | 0x80, '(', 'F', + 'A', 'T', 'H', 'E', 'R', ')', '=', 'F', + 'A', 'A', '4', 'D', 'H', 'E', 'R' | 0x80, '(', + 'F', ')', 'F', '=' | 0x80, '(', 'F', ')', '=', + 'F' | 0x80, + + ']', 'G' | 0x80, ' ', '(', 'G', ')', ' ', '=', + 'J', 'I', 'Y', '4' | 0x80, '(', 'G', 'I', 'V', + ')', '=', 'G', 'I', 'H', '5', 'V' | 0x80, ' ', + '(', 'G', ')', 'I', '^', '=', 'G' | 0x80, '(', + 'G', 'E', ')', 'T', '=', 'G', 'E', 'H', + '5' | 0x80, 'S', 'U', '(', 'G', 'G', 'E', 'S', + ')', '=', 'G', 'J', 'E', 'H', '4', 'S' | 0x80, + '(', 'G', 'G', ')', '=', 'G' | 0x80, ' ', 'B', + '#', '(', 'G', ')', '=', 'G' | 0x80, '(', 'G', + ')', '+', '=', 'J' | 0x80, '(', 'G', 'R', 'E', + 'A', 'T', ')', '=', 'G', 'R', 'E', 'Y', + '4', 'T' | 0x80, '(', 'G', 'O', 'N', ')', 'E', + '=', 'G', 'A', 'O', '5', 'N' | 0x80, '#', '(', + 'G', 'H', ')', '=' | 0x80, ' ', '(', 'G', 'N', + ')', '=', 'N' | 0x80, '(', 'G', ')', '=', 'G' | 0x80, + + ']', 'H' | 0x80, ' ', '(', 'H', ')', ' ', '=', + 'E', 'Y', '4', 'C', 'H' | 0x80, ' ', '(', 'H', + 'A', 'V', ')', '=', '/', 'H', 'A', 'E', + '6', 'V' | 0x80, ' ', '(', 'H', 'E', 'R', 'E', + ')', '=', '/', 'H', 'I', 'Y', 'R' | 0x80, ' ', + '(', 'H', 'O', 'U', 'R', ')', '=', 'A', + 'W', '5', 'E', 'R' | 0x80, '(', 'H', 'O', 'W', + ')', '=', '/', 'H', 'A', 'W' | 0x80, '(', 'H', + ')', '#', '=', '/', 'H' | 0x80, '(', 'H', ')', + '=' | 0x80, + + ']', 'I' | 0x80, ' ', '(', 'I', 'N', ')', '=', + 'I', 'H', 'N' | 0x80, ' ', '(', 'I', ')', ' ', + '=', 'A', 'Y', '4' | 0x80, '(', 'I', ')', ' ', + '=', 'A', 'Y' | 0x80, '(', 'I', 'N', ')', 'D', + '=', 'A', 'Y', '5', 'N' | 0x80, 'S', 'E', 'M', + '(', 'I', ')', '=', 'I', 'Y' | 0x80, ' ', 'A', + 'N', 'T', '(', 'I', ')', '=', 'A', 'Y' | 0x80, + '(', 'I', 'E', 'R', ')', '=', 'I', 'Y', + 'E', 'R' | 0x80, '#', ':', 'R', '(', 'I', 'E', + 'D', ')', ' ', '=', 'I', 'Y', 'D' | 0x80, '(', + 'I', 'E', 'D', ')', ' ', '=', 'A', 'Y', + '5', 'D' | 0x80, '(', 'I', 'E', 'N', ')', '=', + 'I', 'Y', 'E', 'H', 'N' | 0x80, '(', 'I', 'E', + ')', 'T', '=', 'A', 'Y', '4', 'E', 'H' | 0x80, + '(', 'I', '\'', ')', '=', 'A', 'Y', '5' | 0x80, + ' ', ':', '(', 'I', ')', '^', '%', '=', + 'A', 'Y', '5' | 0x80, ' ', ':', '(', 'I', 'E', + ')', ' ', '=', 'A', 'Y', '4' | 0x80, '(', 'I', + ')', '%', '=', 'I', 'Y' | 0x80, '(', 'I', 'E', + ')', '=', 'I', 'Y', '4' | 0x80, ' ', '(', 'I', + 'D', 'E', 'A', ')', '=', 'A', 'Y', 'D', + 'I', 'Y', '5', 'A', 'H' | 0x80, '(', 'I', ')', + '^', '+', ':', '#', '=', 'I', 'H' | 0x80, '(', + 'I', 'R', ')', '#', '=', 'A', 'Y', 'R' | 0x80, + '(', 'I', 'Z', ')', '%', '=', 'A', 'Y', + 'Z' | 0x80, '(', 'I', 'S', ')', '%', '=', 'A', + 'Y', 'Z' | 0x80, 'I', '^', '(', 'I', ')', '^', + '#', '=', 'I', 'H' | 0x80, '+', '^', '(', 'I', + ')', '^', '+', '=', 'A', 'Y' | 0x80, '#', ':', + '^', '(', 'I', ')', '^', '+', '=', 'I', + 'H' | 0x80, '(', 'I', ')', '^', '+', '=', 'A', + 'Y' | 0x80, '(', 'I', 'R', ')', '=', 'E', 'R' | 0x80, + '(', 'I', 'G', 'H', ')', '=', 'A', 'Y', + '4' | 0x80, '(', 'I', 'L', 'D', ')', '=', 'A', + 'Y', '5', 'L', 'D' | 0x80, ' ', '(', 'I', 'G', + 'N', ')', '=', 'I', 'H', 'G', 'N' | 0x80, '(', + 'I', 'G', 'N', ')', ' ', '=', 'A', 'Y', + '4', 'N' | 0x80, '(', 'I', 'G', 'N', ')', '^', + '=', 'A', 'Y', '4', 'N' | 0x80, '(', 'I', 'G', + 'N', ')', '%', '=', 'A', 'Y', '4', 'N' | 0x80, + '(', 'I', 'C', 'R', 'O', ')', '=', 'A', + 'Y', '4', 'K', 'R', 'O', 'H' | 0x80, '(', 'I', + 'Q', 'U', 'E', ')', '=', 'I', 'Y', '4', + 'K' | 0x80, '(', 'I', ')', '=', 'I', 'H' | 0x80, + + ']', 'J' | 0x80, ' ', '(', 'J', ')', ' ', '=', + 'J', 'E', 'Y', '4' | 0x80, '(', 'J', ')', '=', + 'J' | 0x80, + + ']', 'K' | 0x80, ' ', '(', 'K', ')', ' ', '=', + 'K', 'E', 'Y', '4' | 0x80, ' ', '(', 'K', ')', + 'N', '=' | 0x80, '(', 'K', ')', '=', 'K' | 0x80, + + ']', 'L' | 0x80, ' ', '(', 'L', ')', ' ', '=', + 'E', 'H', '4', 'L' | 0x80, '(', 'L', 'O', ')', + 'C', '#', '=', 'L', 'O', 'W' | 0x80, 'L', '(', + 'L', ')', '=' | 0x80, '#', ':', '^', '(', 'L', + ')', '%', '=', 'U', 'L' | 0x80, '(', 'L', 'E', + 'A', 'D', ')', '=', 'L', 'I', 'Y', 'D' | 0x80, + ' ', '(', 'L', 'A', 'U', 'G', 'H', ')', + '=', 'L', 'A', 'E', '4', 'F' | 0x80, '(', 'L', + ')', '=', 'L' | 0x80, + + ']', 'M' | 0x80, ' ', '(', 'M', ')', ' ', '=', + 'E', 'H', '4', 'M' | 0x80, ' ', '(', 'M', 'R', + '.', ')', ' ', '=', 'M', 'I', 'H', '4', + 'S', 'T', 'E', 'R' | 0x80, ' ', '(', 'M', 'S', + '.', ')', '=', 'M', 'I', 'H', '5', 'Z' | 0x80, + ' ', '(', 'M', 'R', 'S', '.', ')', ' ', + '=', 'M', 'I', 'H', '4', 'S', 'I', 'X', + 'Z' | 0x80, '(', 'M', 'O', 'V', ')', '=', 'M', + 'U', 'W', '4', 'V' | 0x80, '(', 'M', 'A', 'C', + 'H', 'I', 'N', ')', '=', 'M', 'A', 'H', + 'S', 'H', 'I', 'Y', '5', 'N' | 0x80, 'M', '(', + 'M', ')', '=' | 0x80, '(', 'M', ')', '=', 'M' | 0x80, + + ']', 'N' | 0x80, ' ', '(', 'N', ')', ' ', '=', + 'E', 'H', '4', 'N' | 0x80, 'E', '(', 'N', 'G', + ')', '+', '=', 'N', 'J' | 0x80, '(', 'N', 'G', + ')', 'R', '=', 'N', 'X', 'G' | 0x80, '(', 'N', + 'G', ')', '#', '=', 'N', 'X', 'G' | 0x80, '(', + 'N', 'G', 'L', ')', '%', '=', 'N', 'X', + 'G', 'U', 'L' | 0x80, '(', 'N', 'G', ')', '=', + 'N', 'X' | 0x80, '(', 'N', 'K', ')', '=', 'N', + 'X', 'K' | 0x80, ' ', '(', 'N', 'O', 'W', ')', + ' ', '=', 'N', 'A', 'W', '4' | 0x80, 'N', '(', + 'N', ')', '=' | 0x80, '(', 'N', 'O', 'N', ')', + 'E', '=', 'N', 'A', 'H', '4', 'N' | 0x80, '(', + 'N', ')', '=', 'N' | 0x80, + + ']', 'O' | 0x80, ' ', '(', 'O', ')', ' ', '=', + 'O', 'H', '4', 'W' | 0x80, '(', 'O', 'F', ')', + ' ', '=', 'A', 'H', 'V' | 0x80, ' ', '(', 'O', + 'H', ')', ' ', '=', 'O', 'W', '5' | 0x80, '(', + 'O', 'R', 'O', 'U', 'G', 'H', ')', '=', + 'E', 'R', '4', 'O', 'W' | 0x80, '#', ':', '(', + 'O', 'R', ')', ' ', '=', 'E', 'R' | 0x80, '#', + ':', '(', 'O', 'R', 'S', ')', ' ', '=', + 'E', 'R', 'Z' | 0x80, '(', 'O', 'R', ')', '=', + 'A', 'O', 'R' | 0x80, ' ', '(', 'O', 'N', 'E', + ')', '=', 'W', 'A', 'H', 'N' | 0x80, '#', '(', + 'O', 'N', 'E', ')', ' ', '=', 'W', 'A', + 'H', 'N' | 0x80, '(', 'O', 'W', ')', '=', 'O', + 'W' | 0x80, ' ', '(', 'O', 'V', 'E', 'R', ')', + '=', 'O', 'W', '5', 'V', 'E', 'R' | 0x80, 'P', + 'R', '(', 'O', ')', 'V', '=', 'U', 'W', + '4' | 0x80, '(', 'O', 'V', ')', '=', 'A', 'H', + '4', 'V' | 0x80, '(', 'O', ')', '^', '%', '=', + 'O', 'W', '5' | 0x80, '(', 'O', ')', '^', 'E', + 'N', '=', 'O', 'W' | 0x80, '(', 'O', ')', '^', + 'I', '#', '=', 'O', 'W', '5' | 0x80, '(', 'O', + 'L', ')', 'D', '=', 'O', 'W', '4', 'L' | 0x80, + '(', 'O', 'U', 'G', 'H', 'T', ')', '=', + 'A', 'O', '5', 'T' | 0x80, '(', 'O', 'U', 'G', + 'H', ')', '=', 'A', 'H', '5', 'F' | 0x80, ' ', + '(', 'O', 'U', ')', '=', 'A', 'W' | 0x80, 'H', + '(', 'O', 'U', ')', 'S', '#', '=', 'A', + 'W', '4' | 0x80, '(', 'O', 'U', 'S', ')', '=', + 'A', 'X', 'S' | 0x80, '(', 'O', 'U', 'R', ')', + '=', 'O', 'H', 'R' | 0x80, '(', 'O', 'U', 'L', + 'D', ')', '=', 'U', 'H', '5', 'D' | 0x80, '(', + 'O', 'U', ')', '^', 'L', '=', 'A', 'H', + '5' | 0x80, '(', 'O', 'U', 'P', ')', '=', 'U', + 'W', '5', 'P' | 0x80, '(', 'O', 'U', ')', '=', + 'A', 'W' | 0x80, '(', 'O', 'Y', ')', '=', 'O', + 'Y' | 0x80, '(', 'O', 'I', 'N', 'G', ')', '=', + 'O', 'W', '4', 'I', 'H', 'N', 'X' | 0x80, '(', + 'O', 'I', ')', '=', 'O', 'Y', '5' | 0x80, '(', + 'O', 'O', 'R', ')', '=', 'O', 'H', '5', + 'R' | 0x80, '(', 'O', 'O', 'K', ')', '=', 'U', + 'H', '5', 'K' | 0x80, 'F', '(', 'O', 'O', 'D', + ')', '=', 'U', 'W', '5', 'D' | 0x80, 'L', '(', + 'O', 'O', 'D', ')', '=', 'A', 'H', '5', + 'D' | 0x80, 'M', '(', 'O', 'O', 'D', ')', '=', + 'U', 'W', '5', 'D' | 0x80, '(', 'O', 'O', 'D', + ')', '=', 'U', 'H', '5', 'D' | 0x80, 'F', '(', + 'O', 'O', 'T', ')', '=', 'U', 'H', '5', + 'T' | 0x80, '(', 'O', 'O', ')', '=', 'U', 'W', + '5' | 0x80, '(', 'O', '\'', ')', '=', 'O', 'H' | 0x80, + '(', 'O', ')', 'E', '=', 'O', 'W' | 0x80, '(', + 'O', ')', ' ', '=', 'O', 'W' | 0x80, '(', 'O', + 'A', ')', '=', 'O', 'W', '4' | 0x80, ' ', '(', + 'O', 'N', 'L', 'Y', ')', '=', 'O', 'W', + '4', 'N', 'L', 'I', 'Y' | 0x80, ' ', '(', 'O', + 'N', 'C', 'E', ')', '=', 'W', 'A', 'H', + '4', 'N', 'S' | 0x80, '(', 'O', 'N', '\'', 'T', + ')', '=', 'O', 'W', '4', 'N', 'T' | 0x80, 'C', + '(', 'O', ')', 'N', '=', 'A', 'A' | 0x80, '(', + 'O', ')', 'N', 'G', '=', 'A', 'O' | 0x80, ' ', + ':', '^', '(', 'O', ')', 'N', '=', 'A', + 'H' | 0x80, 'I', '(', 'O', 'N', ')', '=', 'U', + 'N' | 0x80, '#', ':', '(', 'O', 'N', ')', '=', + 'U', 'N' | 0x80, '#', '^', '(', 'O', 'N', ')', + '=', 'U', 'N' | 0x80, '(', 'O', ')', 'S', 'T', + '=', 'O', 'W' | 0x80, '(', 'O', 'F', ')', '^', + '=', 'A', 'O', '4', 'F' | 0x80, '(', 'O', 'T', + 'H', 'E', 'R', ')', '=', 'A', 'H', '5', + 'D', 'H', 'E', 'R' | 0x80, 'R', '(', 'O', ')', + 'B', '=', 'R', 'A', 'A' | 0x80, '^', 'R', '(', + 'O', ')', ':', '#', '=', 'O', 'W', '5' | 0x80, + '(', 'O', 'S', 'S', ')', ' ', '=', 'A', + 'O', '5', 'S' | 0x80, '#', ':', '^', '(', 'O', + 'M', ')', '=', 'A', 'H', 'M' | 0x80, '(', 'O', + ')', '=', 'A', 'A' | 0x80, + + ']', 'P' | 0x80, ' ', '(', 'P', ')', ' ', '=', + 'P', 'I', 'Y', '4' | 0x80, '(', 'P', 'H', ')', + '=', 'F' | 0x80, '(', 'P', 'E', 'O', 'P', 'L', + ')', '=', 'P', 'I', 'Y', '5', 'P', 'U', + 'L' | 0x80, '(', 'P', 'O', 'W', ')', '=', 'P', + 'A', 'W', '4' | 0x80, '(', 'P', 'U', 'T', ')', + ' ', '=', 'P', 'U', 'H', 'T' | 0x80, '(', 'P', + ')', 'P', '=' | 0x80, '(', 'P', ')', 'S', '=' | 0x80, + '(', 'P', ')', 'N', '=' | 0x80, '(', 'P', 'R', + 'O', 'F', '.', ')', '=', 'P', 'R', 'O', + 'H', 'F', 'E', 'H', '4', 'S', 'E', 'R' | 0x80, + '(', 'P', ')', '=', 'P' | 0x80, + + ']', 'Q' | 0x80, ' ', '(', 'Q', ')', ' ', '=', + 'K', 'Y', 'U', 'W', '4' | 0x80, '(', 'Q', 'U', + 'A', 'R', ')', '=', 'K', 'W', 'O', 'H', + '5', 'R' | 0x80, '(', 'Q', 'U', ')', '=', 'K', + 'W' | 0x80, '(', 'Q', ')', '=', 'K' | 0x80, ']', 'R' | 0x80, + ' ', '(', 'R', ')', ' ', '=', 'A', 'A', + '5', 'R' | 0x80, ' ', '(', 'R', 'E', ')', '^', + '#', '=', 'R', 'I', 'Y' | 0x80, '(', 'R', ')', + 'R', '=' | 0x80, '(', 'R', ')', '=', 'R' | 0x80, + + ']', 'S' | 0x80, ' ', '(', 'S', ')', ' ', '=', + 'E', 'H', '4', 'S' | 0x80, '(', 'S', 'H', ')', + '=', 'S', 'H' | 0x80, '#', '(', 'S', 'I', 'O', + 'N', ')', '=', 'Z', 'H', 'U', 'N' | 0x80, '(', + 'S', 'O', 'M', 'E', ')', '=', 'S', 'A', + 'H', 'M' | 0x80, '#', '(', 'S', 'U', 'R', ')', + '#', '=', 'Z', 'H', 'E', 'R' | 0x80, '(', 'S', + 'U', 'R', ')', '#', '=', 'S', 'H', 'E', + 'R' | 0x80, '#', '(', 'S', 'U', ')', '#', '=', + 'Z', 'H', 'U', 'W' | 0x80, '#', '(', 'S', 'S', + 'U', ')', '#', '=', 'S', 'H', 'U', 'W' | 0x80, + '#', '(', 'S', 'E', 'D', ')', '=', 'Z', + 'D' | 0x80, '#', '(', 'S', ')', '#', '=', 'Z' | 0x80, + '(', 'S', 'A', 'I', 'D', ')', '=', 'S', + 'E', 'H', 'D' | 0x80, '^', '(', 'S', 'I', 'O', + 'N', ')', '=', 'S', 'H', 'U', 'N' | 0x80, '(', + 'S', ')', 'S', '=' | 0x80, '.', '(', 'S', ')', + ' ', '=', 'Z' | 0x80, '#', ':', '.', 'E', '(', + 'S', ')', ' ', '=', 'Z' | 0x80, '#', ':', '^', + '#', '(', 'S', ')', ' ', '=', 'S' | 0x80, 'U', + '(', 'S', ')', ' ', '=', 'S' | 0x80, ' ', ':', + '#', '(', 'S', ')', ' ', '=', 'Z' | 0x80, '#', + '#', '(', 'S', ')', ' ', '=', 'Z' | 0x80, ' ', + '(', 'S', 'C', 'H', ')', '=', 'S', 'K' | 0x80, + '(', 'S', ')', 'C', '+', '=' | 0x80, '#', '(', + 'S', 'M', ')', '=', 'Z', 'U', 'M' | 0x80, '#', + '(', 'S', 'N', ')', '\'', '=', 'Z', 'U', + 'M' | 0x80, '(', 'S', 'T', 'L', 'E', ')', '=', + 'S', 'U', 'L' | 0x80, '(', 'S', ')', '=', 'S' | 0x80, + + ']', 'T' | 0x80, ' ', '(', 'T', ')', ' ', '=', + 'T', 'I', 'Y', '4' | 0x80, ' ', '(', 'T', 'H', + 'E', ')', ' ', '#', '=', 'D', 'H', 'I', + 'Y' | 0x80, ' ', '(', 'T', 'H', 'E', ')', ' ', + '=', 'D', 'H', 'A', 'X' | 0x80, '(', 'T', 'O', + ')', ' ', '=', 'T', 'U', 'X' | 0x80, ' ', '(', + 'T', 'H', 'A', 'T', ')', '=', 'D', 'H', + 'A', 'E', 'T' | 0x80, ' ', '(', 'T', 'H', 'I', + 'S', ')', ' ', '=', 'D', 'H', 'I', 'H', + 'S' | 0x80, ' ', '(', 'T', 'H', 'E', 'Y', ')', + '=', 'D', 'H', 'E', 'Y' | 0x80, ' ', '(', 'T', + 'H', 'E', 'R', 'E', ')', '=', 'D', 'H', + 'E', 'H', 'R' | 0x80, '(', 'T', 'H', 'E', 'R', + ')', '=', 'D', 'H', 'E', 'R' | 0x80, '(', 'T', + 'H', 'E', 'I', 'R', ')', '=', 'D', 'H', + 'E', 'H', 'R' | 0x80, ' ', '(', 'T', 'H', 'A', + 'N', ')', ' ', '=', 'D', 'H', 'A', 'E', + 'N' | 0x80, ' ', '(', 'T', 'H', 'E', 'M', ')', + ' ', '=', 'D', 'H', 'A', 'E', 'N' | 0x80, '(', + 'T', 'H', 'E', 'S', 'E', ')', ' ', '=', + 'D', 'H', 'I', 'Y', 'Z' | 0x80, ' ', '(', 'T', + 'H', 'E', 'N', ')', '=', 'D', 'H', 'E', + 'H', 'N' | 0x80, '(', 'T', 'H', 'R', 'O', 'U', + 'G', 'H', ')', '=', 'T', 'H', 'R', 'U', + 'W', '4' | 0x80, '(', 'T', 'H', 'O', 'S', 'E', + ')', '=', 'D', 'H', 'O', 'H', 'Z' | 0x80, '(', + 'T', 'H', 'O', 'U', 'G', 'H', ')', ' ', + '=', 'D', 'H', 'O', 'W' | 0x80, '(', 'T', 'O', + 'D', 'A', 'Y', ')', '=', 'T', 'U', 'X', + 'D', 'E', 'Y' | 0x80, '(', 'T', 'O', 'M', 'O', + ')', 'R', 'R', 'O', 'W', '=', 'T', 'U', + 'M', 'A', 'A', '5' | 0x80, '(', 'T', 'O', ')', + 'T', 'A', 'L', '=', 'T', 'O', 'W', '5' | 0x80, + ' ', '(', 'T', 'H', 'U', 'S', ')', '=', + 'D', 'H', 'A', 'H', '4', 'S' | 0x80, '(', 'T', + 'H', ')', '=', 'T', 'H' | 0x80, '#', ':', '(', + 'T', 'E', 'D', ')', '=', 'T', 'I', 'X', + 'D' | 0x80, 'S', '(', 'T', 'I', ')', '#', 'N', + '=', 'C', 'H' | 0x80, '(', 'T', 'I', ')', 'O', + '=', 'S', 'H' | 0x80, '(', 'T', 'I', ')', 'A', + '=', 'S', 'H' | 0x80, '(', 'T', 'I', 'E', 'N', + ')', '=', 'S', 'H', 'U', 'N' | 0x80, '(', 'T', + 'U', 'R', ')', '#', '=', 'C', 'H', 'E', + 'R' | 0x80, '(', 'T', 'U', ')', 'A', '=', 'C', + 'H', 'U', 'W' | 0x80, ' ', '(', 'T', 'W', 'O', + ')', '=', 'T', 'U', 'W' | 0x80, '&', '(', 'T', + ')', 'E', 'N', ' ', '=' | 0x80, '(', 'T', ')', + '=', 'T' | 0x80, + + ']', 'U' | 0x80, ' ', '(', 'U', ')', ' ', '=', + 'Y', 'U', 'W', '4' | 0x80, ' ', '(', 'U', 'N', + ')', 'I', '=', 'Y', 'U', 'W', 'N' | 0x80, ' ', + '(', 'U', 'N', ')', '=', 'A', 'H', 'N' | 0x80, + ' ', '(', 'U', 'P', 'O', 'N', ')', '=', + 'A', 'X', 'P', 'A', 'O', 'N' | 0x80, '@', '(', + 'U', 'R', ')', '#', '=', 'U', 'H', '4', + 'R' | 0x80, '(', 'U', 'R', ')', '#', '=', 'Y', + 'U', 'H', '4', 'R' | 0x80, '(', 'U', 'R', ')', + '=', 'E', 'R' | 0x80, '(', 'U', ')', '^', ' ', + '=', 'A', 'H' | 0x80, '(', 'U', ')', '^', '^', + '=', 'A', 'H', '5' | 0x80, '(', 'U', 'Y', ')', + '=', 'A', 'Y', '5' | 0x80, ' ', 'G', '(', 'U', + ')', '#', '=' | 0x80, 'G', '(', 'U', ')', '%', + '=' | 0x80, 'G', '(', 'U', ')', '#', '=', 'W' | 0x80, + '#', 'N', '(', 'U', ')', '=', 'Y', 'U', + 'W' | 0x80, '@', '(', 'U', ')', '=', 'U', 'W' | 0x80, + '(', 'U', ')', '=', 'Y', 'U', 'W' | 0x80, + + ']', 'V' | 0x80, ' ', '(', 'V', ')', ' ', '=', + 'V', 'I', 'Y', '4' | 0x80, '(', 'V', 'I', 'E', + 'W', ')', '=', 'V', 'Y', 'U', 'W', '5' | 0x80, + '(', 'V', ')', '=', 'V' | 0x80, + + ']', 'W' | 0x80, ' ', '(', 'W', ')', ' ', '=', + 'D', 'A', 'H', '4', 'B', 'U', 'L', 'Y', + 'U', 'W' | 0x80, ' ', '(', 'W', 'E', 'R', 'E', + ')', '=', 'W', 'E', 'R' | 0x80, '(', 'W', 'A', + ')', 'S', 'H', '=', 'W', 'A', 'A' | 0x80, '(', + 'W', 'A', ')', 'S', 'T', '=', 'W', 'E', + 'Y' | 0x80, '(', 'W', 'A', ')', 'S', '=', 'W', + 'A', 'H' | 0x80, '(', 'W', 'A', ')', 'T', '=', + 'W', 'A', 'A' | 0x80, '(', 'W', 'H', 'E', 'R', + 'E', ')', '=', 'W', 'H', 'E', 'H', 'R' | 0x80, + '(', 'W', 'H', 'A', 'T', ')', '=', 'W', + 'H', 'A', 'H', 'T' | 0x80, '(', 'W', 'H', 'O', + 'L', ')', '=', '/', 'H', 'O', 'W', 'L' | 0x80, + '(', 'W', 'H', 'O', ')', '=', '/', 'H', + 'U', 'W' | 0x80, '(', 'W', 'H', ')', '=', 'W', + 'H' | 0x80, '(', 'W', 'A', 'R', ')', '#', '=', + 'W', 'E', 'H', 'R' | 0x80, '(', 'W', 'A', 'R', + ')', '=', 'W', 'A', 'O', 'R' | 0x80, '(', 'W', + 'O', 'R', ')', '^', '=', 'W', 'E', 'R' | 0x80, + '(', 'W', 'R', ')', '=', 'R' | 0x80, '(', 'W', + 'O', 'M', ')', 'A', '=', 'W', 'U', 'H', + 'M' | 0x80, '(', 'W', 'O', 'M', ')', 'E', '=', + 'W', 'I', 'H', 'M' | 0x80, '(', 'W', 'E', 'A', + ')', 'R', '=', 'W', 'E', 'H' | 0x80, '(', 'W', + 'A', 'N', 'T', ')', '=', 'W', 'A', 'A', + '5', 'N', 'T' | 0x80, 'A', 'N', 'S', '(', 'W', + 'E', 'R', ')', '=', 'E', 'R' | 0x80, '(', 'W', + ')', '=', 'W' | 0x80, + + ']', 'X' | 0x80, ' ', '(', 'X', ')', ' ', '=', + 'E', 'H', '4', 'K', 'R' | 0x80, ' ', '(', 'X', + ')', '=', 'Z' | 0x80, '(', 'X', ')', '=', 'K', + 'S' | 0x80, + + ']', 'Y' | 0x80, ' ', '(', 'Y', ')', ' ', '=', + 'W', 'A', 'Y', '4' | 0x80, '(', 'Y', 'O', 'U', + 'N', 'G', ')', '=', 'Y', 'A', 'H', 'N', + 'X' | 0x80, ' ', '(', 'Y', 'O', 'U', 'R', ')', + '=', 'Y', 'O', 'H', 'R' | 0x80, ' ', '(', 'Y', + 'O', 'U', ')', '=', 'Y', 'U', 'W' | 0x80, ' ', + '(', 'Y', 'E', 'S', ')', '=', 'Y', 'E', + 'H', 'S' | 0x80, ' ', '(', 'Y', ')', '=', 'Y' | 0x80, + 'F', '(', 'Y', ')', '=', 'A', 'Y' | 0x80, 'P', + 'S', '(', 'Y', 'C', 'H', ')', '=', 'A', + 'Y', 'K' | 0x80, '#', ':', '^', '(', 'Y', ')', + '=', 'I', 'Y' | 0x80, '#', ':', '^', '(', 'Y', + ')', 'I', '=', 'I', 'Y' | 0x80, ' ', ':', '(', + 'Y', ')', ' ', '=', 'A', 'Y' | 0x80, ' ', ':', + '(', 'Y', ')', '#', '=', 'A', 'Y' | 0x80, ' ', + ':', '(', 'Y', ')', '^', '+', ':', '#', + '=', 'I', 'H' | 0x80, ' ', ':', '(', 'Y', ')', + '^', '#', '=', 'A', 'Y' | 0x80, '(', 'Y', ')', + '=', 'I', 'H' | 0x80, + + ']', 'Z' | 0x80, ' ', '(', 'Z', ')', ' ', '=', + 'Z', 'I', 'Y', '4' | 0x80, '(', 'Z', ')', '=', + 'Z' | 0x80, 'j' | 0x80}; + +const unsigned char rules2[] = { + '(', 'A', ')', '=' | 0x80, '(', '!', ')', '=', + '.' | 0x80, '(', '"', ')', ' ', '=', '-', 'A', + 'H', '5', 'N', 'K', 'W', 'O', 'W', 'T', + '-' | 0x80, '(', '"', ')', '=', 'K', 'W', 'O', + 'W', '4', 'T', '-' | 0x80, '(', '#', ')', '=', + ' ', 'N', 'A', 'H', '4', 'M', 'B', 'E', + 'R' | 0x80, '(', '$', ')', '=', ' ', 'D', 'A', + 'A', '4', 'L', 'E', 'R' | 0x80, '(', '%', ')', + '=', ' ', 'P', 'E', 'R', 'S', 'E', 'H', + '4', 'N', 'T' | 0x80, '(', '&', ')', '=', ' ', + 'A', 'E', 'N', 'D' | 0x80, '(', '\'', ')', '=' | 0x80, + '(', '*', ')', '=', ' ', 'A', 'E', '4', + 'S', 'T', 'E', 'R', 'I', 'H', 'S', 'K' | 0x80, + '(', '+', ')', '=', ' ', 'P', 'L', 'A', + 'H', '4', 'S' | 0x80, '(', ',', ')', '=', ',' | 0x80, + ' ', '(', '-', ')', ' ', '=', '-' | 0x80, '(', + '-', ')', '=' | 0x80, '(', '.', ')', '=', ' ', + 'P', 'O', 'Y', 'N', 'T' | 0x80, '(', '/', ')', + '=', ' ', 'S', 'L', 'A', 'E', '4', 'S', + 'H' | 0x80, '(', '0', ')', '=', ' ', 'Z', 'I', + 'Y', '4', 'R', 'O', 'W' | 0x80, ' ', '(', '1', + 'S', 'T', ')', '=', 'F', 'E', 'R', '4', + 'S', 'T' | 0x80, ' ', '(', '1', '0', 'T', 'H', + ')', '=', 'T', 'E', 'H', '4', 'N', 'T', + 'H' | 0x80, '(', '1', ')', '=', ' ', 'W', 'A', + 'H', '4', 'N' | 0x80, ' ', '(', '2', 'N', 'D', + ')', '=', 'S', 'E', 'H', '4', 'K', 'U', + 'N', 'D' | 0x80, '(', '2', ')', '=', ' ', 'T', + 'U', 'W', '4' | 0x80, ' ', '(', '3', 'R', 'D', + ')', '=', 'T', 'H', 'E', 'R', '4', 'D' | 0x80, + '(', '3', ')', '=', ' ', 'T', 'H', 'R', + 'I', 'Y', '4' | 0x80, '(', '4', ')', '=', ' ', + 'F', 'O', 'H', '4', 'R' | 0x80, ' ', '(', '5', + 'T', 'H', ')', '=', 'F', 'I', 'H', '4', + 'F', 'T', 'H' | 0x80, '(', '5', ')', '=', ' ', + 'F', 'A', 'Y', '4', 'V' | 0x80, ' ', '(', '6', + '4', ')', ' ', '=', 'S', 'I', 'H', '4', + 'K', 'S', 'T', 'I', 'Y', ' ', 'F', 'O', + 'H', 'R' | 0x80, '(', '6', ')', '=', ' ', 'S', + 'I', 'H', '4', 'K', 'S' | 0x80, '(', '7', ')', + '=', ' ', 'S', 'E', 'H', '4', 'V', 'U', + 'N' | 0x80, ' ', '(', '8', 'T', 'H', ')', '=', + 'E', 'Y', '4', 'T', 'H' | 0x80, '(', '8', ')', + '=', ' ', 'E', 'Y', '4', 'T' | 0x80, '(', '9', + ')', '=', ' ', 'N', 'A', 'Y', '4', 'N' | 0x80, + '(', ':', ')', '=', '.' | 0x80, '(', ';', ')', + '=', '.' | 0x80, '(', '<', ')', '=', ' ', 'L', + 'E', 'H', '4', 'S', ' ', 'D', 'H', 'A', + 'E', 'N' | 0x80, '(', '=', ')', '=', ' ', 'I', + 'Y', '4', 'K', 'W', 'U', 'L', 'Z' | 0x80, '(', + '>', ')', '=', ' ', 'G', 'R', 'E', 'Y', + '4', 'T', 'E', 'R', ' ', 'D', 'H', 'A', + 'E', 'N' | 0x80, '(', '?', ')', '=', '?' | 0x80, '(', + '@', ')', '=', ' ', 'A', 'E', '6', 'T' | 0x80, + '(', '^', ')', '=', ' ', 'K', 'A', 'E', + '4', 'R', 'I', 'X', 'T' | 0x80, ']', 'A' | 0x80}; + +//26 items. From 'A' to 'Z' +// positions for mem62 and mem63 for each character +const unsigned char tab37489[] = {0, 149, 247, 162, 57, 197, 6, 126, 199, 38, 55, 78, 145, + 241, 85, 161, 254, 36, 69, 45, 167, 54, 83, 46, 71, 218}; + +const unsigned char tab37515[] = {125, 126, 126, 127, 128, 129, 130, 130, 130, 132, 132, 132, 132, + 132, 133, 135, 135, 136, 136, 137, 138, 139, 139, 140, 140, 140}; + +void STM32SAM::Output8BitAry(int index, unsigned char ary[5]) { + int k; + + uint32_t bufferposOld = bufferpos; + + bufferpos += timetable[oldtimetableindex][index]; + oldtimetableindex = index; + + int sample_uS = bufferpos - bufferposOld; + + uint32_t f = 0; + + // write a little bit in advance + for(k = 0; k < 5; k++) { + // buffer[bufferpos / 50 + k] = ary[k]; + + // f = micros() + sample_uS / (_STM32SAM_SPEED + 1); + // while(micros() < f) { + // }; + f = sample_uS / (_STM32SAM_SPEED + 1); + furi_delay_us(f); + SetAUDIO(ary[k]); + // delayMicroseconds(sample_uS / 5 ); + } + + // SetAUDIO(ary[0]); +} + +void STM32SAM::Output8Bit(int index, unsigned char A) { + unsigned char ary[5] = {A, A, A, A, A}; + Output8BitAry(index, ary); +} + +//written by me because of different table positions. +// mem[47] = ... +// 168=pitches +// 169=frequency1 +// 170=frequency2 +// 171=frequency3 +// 172=amplitude1 +// 173=amplitude2 +// 174=amplitude3 +unsigned char STM32SAM::Read(unsigned char p, unsigned char Y) { + switch(p) { + case 168: + return pitches[Y]; + case 169: + return frequency1[Y]; + case 170: + return frequency2[Y]; + case 171: + return frequency3[Y]; + case 172: + return amplitude1[Y]; + case 173: + return amplitude2[Y]; + case 174: + return amplitude3[Y]; + } + // Serial1.println("Error reading to tables"); + return 0; +} + +void STM32SAM::Write(unsigned char p, unsigned char Y, unsigned char value) { + switch(p) { + case 168: + pitches[Y] = value; + return; + case 169: + frequency1[Y] = value; + return; + case 170: + frequency2[Y] = value; + return; + case 171: + frequency3[Y] = value; + return; + case 172: + amplitude1[Y] = value; + return; + case 173: + amplitude2[Y] = value; + return; + case 174: + amplitude3[Y] = value; + return; + } + //Serial1.println("Error writing to tables\n"); +} + +// ------------------------------------------------------------------------- +//Code48227 +// Render a sampled sound from the sampleTable. +// +// Phoneme Sample Start Sample End +// 32: S* 15 255 +// 33: SH 257 511 +// 34: F* 559 767 +// 35: TH 583 767 +// 36: /H 903 1023 +// 37: /X 1135 1279 +// 38: Z* 84 119 +// 39: ZH 340 375 +// 40: V* 596 639 +// 41: DH 596 631 +// +// 42: CH +// 43: ** 399 511 +// +// 44: J* +// 45: ** 257 276 +// 46: ** +// +// 66: P* +// 67: ** 743 767 +// 68: ** +// +// 69: T* +// 70: ** 231 255 +// 71: ** +// +// The SampledPhonemesTable[] holds flags indicating if a phoneme is +// voiced or not. If the upper 5 bits are zero, the sample is voiced. +// +// Samples in the sampleTable are compressed, with bits being converted to +// bytes from high bit to low, as follows: +// +// unvoiced 0 bit -> X +// unvoiced 1 bit -> 5 +// +// voiced 0 bit -> 6 +// voiced 1 bit -> 24 +// +// Where X is a value from the table: +// +// { 0x18, 0x1A, 0x17, 0x17, 0x17 }; +// +// The index into this table is determined by masking off the lower +// 3 bits from the SampledPhonemesTable: +// +// index = (SampledPhonemesTable[i] & 7) - 1; +// +// For voices samples, samples are interleaved between voiced output. + +// Code48227() +void STM32SAM::RenderSample(unsigned char* mem66) { + int tempA; + // current phoneme's index + mem49 = Y; + + // mask low three bits and subtract 1 get value to + // convert 0 bits on unvoiced samples. + A = mem39 & 7; + X = A - 1; + + // store the result + mem56 = X; + + // determine which offset to use from table { 0x18, 0x1A, 0x17, 0x17, 0x17 } + // T, S, Z 0 0x18 + // CH, J, SH, ZH 1 0x1A + // P, F*, V, TH, DH 2 0x17 + // /H 3 0x17 + // /X 4 0x17 + + // get value from the table + mem53 = tab48426[X]; + mem47 = X; //46016+mem[56]*256 + + // voiced sample? + A = mem39 & 248; + if(A == 0) { + // voiced phoneme: Z*, ZH, V*, DH + Y = mem49; + A = pitches[mem49] >> 4; + + // jump to voiced portion + goto pos48315; + } + + Y = A ^ 255; +pos48274: + + // step through the 8 bits in the sample + mem56 = 8; + + // get the next sample from the table + // mem47*256 = offset to start of samples + A = sampleTable[mem47 * 256 + Y]; +pos48280: + + // left shift to get the high bit + tempA = A; + A = A << 1; + //48281: BCC 48290 + + // bit not set? + if((tempA & 128) == 0) { + // convert the bit to value from table + X = mem53; + //mem[54296] = X; + // output the byte + Output8Bit(1, (X & 0x0f) * 16); + // if X != 0, exit loop + if(X != 0) goto pos48296; + } + + // output a 5 for the on bit + Output8Bit(2, 5 * 16); + + //48295: NOP +pos48296: + + X = 0; + + // decrement counter + mem56--; + + // if not done, jump to top of loop + if(mem56 != 0) goto pos48280; + + // increment position + Y++; + if(Y != 0) goto pos48274; + + // restore values and return + mem44 = 1; + Y = mem49; + return; + + unsigned char phase1; + +pos48315: + // handle voiced samples here + + // number of samples? + phase1 = A ^ 255; + + Y = *mem66; + do { + //pos48321: + + // shift through all 8 bits + mem56 = 8; + //A = Read(mem47, Y); + + // fetch value from table + A = sampleTable[mem47 * 256 + Y]; + + // loop 8 times + //pos48327: + do { + //48327: ASL A + //48328: BCC 48337 + + // left shift and check high bit + tempA = A; + A = A << 1; + if((tempA & 128) != 0) { + // if bit set, output 26 + X = 26; + Output8Bit(3, (X & 0xf) * 16); + } else { + //timetable 4 + // bit is not set, output a 6 + X = 6; + Output8Bit(4, (X & 0xf) * 16); + } + + mem56--; + } while(mem56 != 0); + + // move ahead in the table + Y++; + + // continue until counter done + phase1++; + + } while(phase1 != 0); + // if (phase1 != 0) goto pos48321; + + // restore values and return + A = 1; + mem44 = 1; + *mem66 = Y; + Y = mem49; + return; +} + +// RENDER THE PHONEMES IN THE LIST +// +// The phoneme list is converted into sound through the steps: +// +// 1. Copy each phoneme number of times into the frames list, +// where each frame represents 10 milliseconds of sound. +// +// 2. Determine the transitions lengths between phonemes, and linearly +// interpolate the values across the frames. +// +// 3. Offset the pitches by the fundamental frequency. +// +// 4. Render the each frame. + +//void Code47574() +void STM32SAM::Render() { + unsigned char phase1 = 0; //mem43 + unsigned char phase2 = 0; + unsigned char phase3 = 0; + unsigned char mem66 = 0; + unsigned char mem38 = 0; + unsigned char mem40 = 0; + unsigned char speedcounter = 0; //mem45 + unsigned char mem48 = 0; + int i; + if(phonemeIndexOutput[0] == 255) return; //exit if no data + + A = 0; + X = 0; + mem44 = 0; + + // CREATE FRAMES + // + // The length parameter in the list corresponds to the number of frames + // to expand the phoneme to. Each frame represents 10 milliseconds of time. + // So a phoneme with a length of 7 = 7 frames = 70 milliseconds duration. + // + // The parameters are copied from the phoneme to the frame verbatim. + + // pos47587: + do { + // get the index + Y = mem44; + // get the phoneme at the index + A = phonemeIndexOutput[mem44]; + mem56 = A; + + // if terminal phoneme, exit the loop + if(A == 255) break; + + // period phoneme *. + if(A == 1) { + // add rising inflection + A = 1; + mem48 = 1; + //goto pos48376; + AddInflection(mem48, phase1); + } + /* + if (A == 2) goto pos48372; + */ + + // question mark phoneme? + if(A == 2) { + // create falling inflection + mem48 = 255; + AddInflection(mem48, phase1); + } + // pos47615: + + // get the stress amount (more stress = higher pitch) + phase1 = tab47492[stressOutput[Y] + 1]; + + // get number of frames to write + phase2 = phonemeLengthOutput[Y]; + Y = mem56; + + // copy from the source to the frames list + do { + frequency1[X] = freq1data[Y]; // F1 frequency + frequency2[X] = freq2data[Y]; // F2 frequency + frequency3[X] = freq3data[Y]; // F3 frequency + amplitude1[X] = ampl1data[Y]; // F1 amplitude + amplitude2[X] = ampl2data[Y]; // F2 amplitude + amplitude3[X] = ampl3data[Y]; // F3 amplitude + sampledConsonantFlag[X] = + sampledConsonantFlags[Y]; // phoneme data for sampled consonants + pitches[X] = pitch + phase1; // pitch + X++; + phase2--; + } while(phase2 != 0); + mem44++; + } while(mem44 != 0); + // ------------------- + //pos47694: + + // CREATE TRANSITIONS + // + // Linear transitions are now created to smoothly connect the + // end of one sustained portion of a phoneme to the following + // phoneme. + // + // To do this, three tables are used: + // + // Table Purpose + // ========= ================================================== + // blendRank Determines which phoneme's blend values are used. + // + // blendOut The number of frames at the end of the phoneme that + // will be used to transition to the following phoneme. + // + // blendIn The number of frames of the following phoneme that + // will be used to transition into that phoneme. + // + // In creating a transition between two phonemes, the phoneme + // with the HIGHEST rank is used. Phonemes are ranked on how much + // their identity is based on their transitions. For example, + // vowels are and diphthongs are identified by their sustained portion, + // rather than the transitions, so they are given low values. In contrast, + // stop consonants (P, B, T, K) and glides (Y, L) are almost entirely + // defined by their transitions, and are given high rank values. + // + // Here are the rankings used by SAM: + // + // Rank Type Phonemes + // 2 All vowels IY, IH, etc. + // 5 Diphthong endings YX, WX, ER + // 8 Terminal liquid consonants LX, WX, YX, N, NX + // 9 Liquid consonants L, RX, W + // 10 Glide R, OH + // 11 Glide WH + // 18 Voiceless fricatives S, SH, F, TH + // 20 Voiced fricatives Z, ZH, V, DH + // 23 Plosives, stop consonants P, T, K, KX, DX, CH + // 26 Stop consonants J, GX, B, D, G + // 27-29 Stop consonants (internal) ** + // 30 Unvoiced consonants /H, /X and Q* + // 160 Nasal M + // + // To determine how many frames to use, the two phonemes are + // compared using the blendRank[] table. The phoneme with the + // higher rank is selected. In case of a tie, a blend of each is used: + // + // if blendRank[phoneme1] == blendRank[phomneme2] + // // use lengths from each phoneme + // outBlendFrames = outBlend[phoneme1] + // inBlendFrames = outBlend[phoneme2] + // else if blendRank[phoneme1] > blendRank[phoneme2] + // // use lengths from first phoneme + // outBlendFrames = outBlendLength[phoneme1] + // inBlendFrames = inBlendLength[phoneme1] + // else + // // use lengths from the second phoneme + // // note that in and out are SWAPPED! + // outBlendFrames = inBlendLength[phoneme2] + // inBlendFrames = outBlendLength[phoneme2] + // + // Blend lengths can't be less than zero. + // + // Transitions are assumed to be symetrical, so if the transition + // values for the second phoneme are used, the inBlendLength and + // outBlendLength values are SWAPPED. + // + // For most of the parameters, SAM interpolates over the range of the last + // outBlendFrames-1 and the first inBlendFrames. + // + // The exception to this is the Pitch[] parameter, which is interpolates the + // pitch from the CENTER of the current phoneme to the CENTER of the next + // phoneme. + // + // Here are two examples. First, For example, consider the word "SUN" (S AH N) + // + // Phoneme Duration BlendWeight OutBlendFrames InBlendFrames + // S 2 18 1 3 + // AH 8 2 4 4 + // N 7 8 1 2 + // + // The formant transitions for the output frames are calculated as follows: + // + // flags ampl1 freq1 ampl2 freq2 ampl3 freq3 pitch + // ------------------------------------------------ + // S + // 241 0 6 0 73 0 99 61 Use S (weight 18) for transition instead of AH (weight 2) + // 241 0 6 0 73 0 99 61 <-- (OutBlendFrames-1) = (1-1) = 0 frames + // AH + // 0 2 10 2 66 0 96 59 * <-- InBlendFrames = 3 frames + // 0 4 14 3 59 0 93 57 * + // 0 8 18 5 52 0 90 55 * + // 0 15 22 9 44 1 87 53 + // 0 15 22 9 44 1 87 53 + // 0 15 22 9 44 1 87 53 Use N (weight 8) for transition instead of AH (weight 2). + // 0 15 22 9 44 1 87 53 Since N is second phoneme, reverse the IN and OUT values. + // 0 11 17 8 47 1 98 56 * <-- (InBlendFrames-1) = (2-1) = 1 frames + // N + // 0 8 12 6 50 1 109 58 * <-- OutBlendFrames = 1 + // 0 5 6 5 54 0 121 61 + // 0 5 6 5 54 0 121 61 + // 0 5 6 5 54 0 121 61 + // 0 5 6 5 54 0 121 61 + // 0 5 6 5 54 0 121 61 + // 0 5 6 5 54 0 121 61 + // + // Now, consider the reverse "NUS" (N AH S): + // + // flags ampl1 freq1 ampl2 freq2 ampl3 freq3 pitch + // ------------------------------------------------ + // N + // 0 5 6 5 54 0 121 61 + // 0 5 6 5 54 0 121 61 + // 0 5 6 5 54 0 121 61 + // 0 5 6 5 54 0 121 61 + // 0 5 6 5 54 0 121 61 + // 0 5 6 5 54 0 121 61 Use N (weight 8) for transition instead of AH (weight 2) + // 0 5 6 5 54 0 121 61 <-- (OutBlendFrames-1) = (1-1) = 0 frames + // AH + // 0 8 11 6 51 0 110 59 * <-- InBlendFrames = 2 + // 0 11 16 8 48 0 99 56 * + // 0 15 22 9 44 1 87 53 Use S (weight 18) for transition instead of AH (weight 2) + // 0 15 22 9 44 1 87 53 Since S is second phoneme, reverse the IN and OUT values. + // 0 9 18 5 51 1 90 55 * <-- (InBlendFrames-1) = (3-1) = 2 + // 0 4 14 3 58 1 93 57 * + // S + // 241 2 10 2 65 1 96 59 * <-- OutBlendFrames = 1 + // 241 0 6 0 73 0 99 61 + + A = 0; + mem44 = 0; + mem49 = 0; // mem49 starts at as 0 + X = 0; + while(1) //while No. 1 + { + // get the current and following phoneme + Y = phonemeIndexOutput[X]; + A = phonemeIndexOutput[X + 1]; + X++; + + // exit loop at end token + if(A == 255) break; //goto pos47970; + + // get the ranking of each phoneme + X = A; + mem56 = blendRank[A]; + A = blendRank[Y]; + + // compare the rank - lower rank value is stronger + if(A == mem56) { + // same rank, so use out blend lengths from each phoneme + phase1 = outBlendLength[Y]; + phase2 = outBlendLength[X]; + } else if(A < mem56) { + // first phoneme is stronger, so us it's blend lengths + phase1 = inBlendLength[X]; + phase2 = outBlendLength[X]; + } else { + // second phoneme is stronger, so use it's blend lengths + // note the out/in are swapped + phase1 = outBlendLength[Y]; + phase2 = inBlendLength[Y]; + } + + Y = mem44; + A = mem49 + phonemeLengthOutput[mem44]; // A is mem49 + length + mem49 = A; // mem49 now holds length + position + A = A + phase2; //Maybe Problem because of carry flag + + //47776: ADC 42 + speedcounter = A; + mem47 = 168; + phase3 = mem49 - phase1; // what is mem49 + A = phase1 + phase2; // total transition? + mem38 = A; + + X = A; + X -= 2; + if((X & 128) == 0) + do //while No. 2 + { + //pos47810: + + // mem47 is used to index the tables: + // 168 pitches[] + // 169 frequency1 + // 170 frequency2 + // 171 frequency3 + // 172 amplitude1 + // 173 amplitude2 + // 174 amplitude3 + + mem40 = mem38; + + if(mem47 == 168) // pitch + { + // unlike the other values, the pitches[] interpolates from + // the middle of the current phoneme to the middle of the + // next phoneme + + unsigned char mem36, mem37; + // half the width of the current phoneme + mem36 = phonemeLengthOutput[mem44] >> 1; + // half the width of the next phoneme + mem37 = phonemeLengthOutput[mem44 + 1] >> 1; + // sum the values + mem40 = mem36 + mem37; // length of both halves + mem37 += mem49; // center of next phoneme + mem36 = mem49 - mem36; // center index of current phoneme + A = Read( + mem47, mem37); // value at center of next phoneme - end interpolation value + //A = mem[address]; + + Y = mem36; // start index of interpolation + mem53 = A - Read(mem47, mem36); // value to center of current phoneme + } else { + // value to interpolate to + A = Read(mem47, speedcounter); + // position to start interpolation from + Y = phase3; + // value to interpolate from + mem53 = A - Read(mem47, phase3); + } + + //Code47503(mem40); + // ML : Code47503 is division with remainder, and mem50 gets the sign + + // calculate change per frame + signed char m53 = (signed char)mem53; + mem50 = mem53 & 128; + unsigned char m53abs = abs(m53); + mem51 = m53abs % mem40; //abs((char)m53) % mem40; + mem53 = (unsigned char)((signed char)(m53) / mem40); + + // interpolation range + X = mem40; // number of frames to interpolate over + Y = phase3; // starting frame + + // linearly interpolate values + + mem56 = 0; + //47907: CLC + //pos47908: + while(1) //while No. 3 + { + A = Read(mem47, Y) + mem53; //carry alway cleared + + mem48 = A; + Y++; + X--; + if(X == 0) break; + + mem56 += mem51; + if(mem56 >= mem40) //??? + { + mem56 -= mem40; //carry? is set + //if ((mem56 & 128)==0) + if((mem50 & 128) == 0) { + //47935: BIT 50 + //47937: BMI 47943 + if(mem48 != 0) mem48++; + } else + mem48--; + } + //pos47945: + Write(mem47, Y, mem48); + } //while No. 3 + + //pos47952: + mem47++; + //if (mem47 != 175) goto pos47810; + } while(mem47 != 175); //while No. 2 + //pos47963: + mem44++; + X = mem44; + } //while No. 1 + + //goto pos47701; + //pos47970: + + // add the length of this phoneme + mem48 = mem49 + phonemeLengthOutput[mem44]; + + // ASSIGN PITCH CONTOUR + // + // This subtracts the F1 frequency from the pitch to create a + // pitch contour. Without this, the output would be at a single + // pitch level (monotone). + + // don't adjust pitch if in sing mode + if(!singmode) { + // iterate through the buffer + for(i = 0; i < 256; i++) { + // subtract half the frequency of the formant 1. + // this adds variety to the voice + pitches[i] -= (frequency1[i] >> 1); + } + } + + phase1 = 0; + phase2 = 0; + phase3 = 0; + mem49 = 0; + speedcounter = 72; //sam standard speed + + // RESCALE AMPLITUDE + // + // Rescale volume from a linear scale to decibels. + // + + //amplitude rescaling + for(i = 255; i >= 0; i--) { + amplitude1[i] = amplitudeRescale[amplitude1[i]]; + amplitude2[i] = amplitudeRescale[amplitude2[i]]; + amplitude3[i] = amplitudeRescale[amplitude3[i]]; + } + + Y = 0; + A = pitches[0]; + mem44 = A; + X = A; + mem38 = A - (A >> 2); // 3/4*A ??? + + // PROCESS THE FRAMES + // + // In traditional vocal synthesis, the glottal pulse drives filters, which + // are attenuated to the frequencies of the formants. + // + // SAM generates these formants directly with sin and rectangular waves. + // To simulate them being driven by the glottal pulse, the waveforms are + // reset at the beginning of each glottal pulse. + + //finally the loop for sound output + //pos48078: + while(1) { + // get the sampled information on the phoneme + A = sampledConsonantFlag[Y]; + mem39 = A; + + // unvoiced sampled phoneme? + A = A & 248; + if(A != 0) { + // render the sample for the phoneme + RenderSample(&mem66); + + // skip ahead two in the phoneme buffer + Y += 2; + mem48 -= 2; + } else { + // simulate the glottal pulse and formants + unsigned char ary[5]; + unsigned int p1 = + phase1 * 256; // Fixed point integers because we need to divide later on + unsigned int p2 = phase2 * 256; + unsigned int p3 = phase3 * 256; + int k; + for(k = 0; k < 5; k++) { + signed char sp1 = (signed char)sinus[0xff & (p1 >> 8)]; + signed char sp2 = (signed char)sinus[0xff & (p2 >> 8)]; + signed char rp3 = (signed char)rectangle[0xff & (p3 >> 8)]; + signed int sin1 = sp1 * ((unsigned char)amplitude1[Y] & 0x0f); + signed int sin2 = sp2 * ((unsigned char)amplitude2[Y] & 0x0f); + signed int rect = rp3 * ((unsigned char)amplitude3[Y] & 0x0f); + signed int mux = sin1 + sin2 + rect; + mux /= 32; + mux += 128; // Go from signed to unsigned amplitude + ary[k] = mux; + p1 += frequency1[Y] * 256 / 4; // Compromise, this becomes a shift and works well + p2 += frequency2[Y] * 256 / 4; + p3 += frequency3[Y] * 256 / 4; + } + // output the accumulated value + Output8BitAry(0, ary); + speedcounter--; + if(speedcounter != 0) goto pos48155; + Y++; //go to next amplitude + + // decrement the frame count + mem48--; + } + + // if the frame count is zero, exit the loop + if(mem48 == 0) return; + speedcounter = speed; + pos48155: + + // decrement the remaining length of the glottal pulse + mem44--; + + // finished with a glottal pulse? + if(mem44 == 0) { + pos48159: + // fetch the next glottal pulse length + A = pitches[Y]; + mem44 = A; + A = A - (A >> 2); + mem38 = A; + + // reset the formant wave generators to keep them in + // sync with the glottal pulse + phase1 = 0; + phase2 = 0; + phase3 = 0; + continue; + } + + // decrement the count + mem38--; + + // is the count non-zero and the sampled flag is zero? + if((mem38 != 0) || (mem39 == 0)) { + // reset the phase of the formants to match the pulse + phase1 += frequency1[Y]; + phase2 += frequency2[Y]; + phase3 += frequency3[Y]; + continue; + } + + // voiced sampled phonemes interleave the sample with the + // glottal pulse. The sample flag is non-zero, so render + // the sample for the phoneme. + RenderSample(&mem66); + goto pos48159; + } //while + + // The following code is never reached. It's left over from when + // the voiced sample code was part of this loop, instead of part + // of RenderSample(); + + //pos48315: + int tempA; + phase1 = A ^ 255; + Y = mem66; + do { + //pos48321: + + mem56 = 8; + A = Read(mem47, Y); + + //pos48327: + do { + //48327: ASL A + //48328: BCC 48337 + tempA = A; + A = A << 1; + if((tempA & 128) != 0) { + X = 26; + // mem[54296] = X; + bufferpos += 150; + // + // + // buffer[bufferpos / 50] = (X & 15) * 16; + // + // + + } else { + //mem[54296] = 6; + X = 6; + bufferpos += 150; + // + // buffer[bufferpos / 50] = (X & 15) * 16; + // + // + } + + for(X = wait2; X > 0; X--) + ; //wait + mem56--; + } while(mem56 != 0); + + Y++; + phase1++; + + } while(phase1 != 0); + // if (phase1 != 0) goto pos48321; + A = 1; + mem44 = 1; + mem66 = Y; + Y = mem49; + return; +} + +// Create a rising or falling inflection 30 frames prior to +// index X. A rising inflection is used for questions, and +// a falling inflection is used for statements. + +void STM32SAM::AddInflection(unsigned char mem48, unsigned char phase1) { + //pos48372: + // mem48 = 255; + //pos48376: + + // store the location of the punctuation + mem49 = X; + A = X; + int Atemp = A; + + // backup 30 frames + A = A - 30; + // if index is before buffer, point to start of buffer + if(Atemp <= 30) A = 0; + X = A; + + // FIXME: Explain this fix better, it's not obvious + // ML : A =, fixes a problem with invalid pitch with '.' + while((A = pitches[X]) == 127) X++; + +pos48398: + //48398: CLC + //48399: ADC 48 + + // add the inflection direction + A += mem48; + phase1 = A; + + // set the inflection + pitches[X] = A; +pos48406: + + // increment the position + X++; + + // exit if the punctuation has been reached + if(X == mem49) return; //goto pos47615; + if(pitches[X] == 255) goto pos48406; + A = phase1; + goto pos48398; +} + +/* + SAM's voice can be altered by changing the frequencies of the + mouth formant (F1) and the throat formant (F2). Only the voiced + phonemes (5-29 and 48-53) are altered. +*/ +void STM32SAM::SetMouthThroat() { + unsigned char initialFrequency; + unsigned char newFrequency = 0; + //unsigned char mouth; //mem38880 + //unsigned char throat; //mem38881 + + // mouth formants (F1) 5..29 + unsigned char mouthFormants5_29[30] = {0, 0, 0, 0, 0, 10, 14, 19, 24, 27, + 23, 21, 16, 20, 14, 18, 14, 18, 18, 16, + 13, 15, 11, 18, 14, 11, 9, 6, 6, 6}; + + // throat formants (F2) 5..29 + unsigned char throatFormants5_29[30] = {255, 255, 255, 255, 255, 84, 73, 67, 63, 40, + 44, 31, 37, 45, 73, 49, 36, 30, 51, 37, + 29, 69, 24, 50, 30, 24, 83, 46, 54, 86}; + + // there must be no zeros in this 2 tables + // formant 1 frequencies (mouth) 48..53 + unsigned char mouthFormants48_53[6] = {19, 27, 21, 27, 18, 13}; + + // formant 2 frequencies (throat) 48..53 + unsigned char throatFormants48_53[6] = {72, 39, 31, 43, 30, 34}; + + unsigned char pos = 5; //mem39216 + //pos38942: + // recalculate formant frequencies 5..29 for the mouth (F1) and throat (F2) + while(pos != 30) { + // recalculate mouth frequency + initialFrequency = mouthFormants5_29[pos]; + if(initialFrequency != 0) newFrequency = trans(mouth, initialFrequency); + freq1data[pos] = newFrequency; + + // recalculate throat frequency + initialFrequency = throatFormants5_29[pos]; + if(initialFrequency != 0) newFrequency = trans(throat, initialFrequency); + freq2data[pos] = newFrequency; + pos++; + } + + //pos39059: + // recalculate formant frequencies 48..53 + pos = 48; + Y = 0; + while(pos != 54) { + // recalculate F1 (mouth formant) + initialFrequency = mouthFormants48_53[Y]; + newFrequency = trans(mouth, initialFrequency); + freq1data[pos] = newFrequency; + + // recalculate F2 (throat formant) + initialFrequency = throatFormants48_53[Y]; + newFrequency = trans(throat, initialFrequency); + freq2data[pos] = newFrequency; + Y++; + pos++; + } +} + +//return = (mem39212*mem39213) >> 1 +unsigned char STM32SAM::trans(unsigned char mem39212, unsigned char mem39213) { + //pos39008: + unsigned char carry; + int temp; + unsigned char mem39214, mem39215; + A = 0; + mem39215 = 0; + mem39214 = 0; + X = 8; + do { + carry = mem39212 & 1; + mem39212 = mem39212 >> 1; + if(carry != 0) { + /* + 39018: LSR 39212 + 39021: BCC 39033 + */ + carry = 0; + A = mem39215; + temp = (int)A + (int)mem39213; + A = A + mem39213; + if(temp > 255) carry = 1; + mem39215 = A; + } + temp = mem39215 & 1; + mem39215 = (mem39215 >> 1) | (carry ? 128 : 0); + carry = temp; + //39033: ROR 39215 + X--; + } while(X != 0); + temp = mem39214 & 128; + mem39214 = (mem39214 << 1) | (carry ? 1 : 0); + carry = temp; + temp = mem39215 & 128; + mem39215 = (mem39215 << 1) | (carry ? 1 : 0); + carry = temp; + + return mem39215; +} + +//////////////////////////////////////////////////////////////////////////////////////////// +// +// Sam +// +//////////////////////////////////////////////////////////////////////////////////////////// + +//char input[]={"/HAALAOAO MAYN NAAMAEAE IHSTT SAEBAASTTIHAAN \x9b\x9b\0"}; +//unsigned char input[]={"/HAALAOAO \x9b\0"}; +//unsigned char input[]={"AA \x9b\0"}; +//unsigned char input[] = {"GUH5DEHN TAEG\x9b\0"}; + +//unsigned char input[]={"AY5 AEM EY TAO4LXKIHNX KAX4MPYUX4TAH. GOW4 AH/HEH3D PAHNK.MEYK MAY8 DEY.\x9b\0"}; +//unsigned char input[]={"/HEH3LOW2, /HAW AH YUX2 TUXDEY. AY /HOH3P YUX AH FIYLIHNX OW4 KEY.\x9b\0"}; +//unsigned char input[]={"/HEY2, DHIHS IH3Z GREY2T. /HAH /HAH /HAH.AYL BIY5 BAEK.\x9b\0"}; +//unsigned char input[]={"/HAH /HAH /HAH \x9b\0"}; +//unsigned char input[]={"/HAH /HAH /HAH.\x9b\0"}; +//unsigned char input[]={".TUW BIY5Y3,, OHR NAA3T - TUW BIY5IYIY., DHAE4T IHZ DHAH KWEH4SCHAHN.\x9b\0"}; +//unsigned char input[]={"/HEY2, DHIHS \x9b\0"}; + +//unsigned char input[]={" IYIHEHAEAAAHAOOHUHUXERAXIX \x9b\0"}; +//unsigned char input[]={" RLWWYMNNXBDGJZZHVDH \x9b\0"}; +//unsigned char input[]={" SSHFTHPTKCH/H \x9b\0"}; + +//unsigned char input[]={" EYAYOYAWOWUW ULUMUNQ YXWXRXLX/XDX\x9b\0"}; + +void STM32SAM::SetInput(char* _input) { + int i, l; + l = strlen(_input); + if(l > 254) l = 254; + for(i = 0; i < l; i++) { + input[i] = _input[i]; + } + input[l] = 0; +} + +// 168=pitches +// 169=frequency1 +// 170=frequency2 +// 171=frequency3 +// 172=amplitude1 +// 173=amplitude2 +// 174=amplitude3 + +void STM32SAM::Init() { + bufferpos = 0; + int i; + SetMouthThroat(); + + bufferpos = 0; + // TODO, check for free the memory, 10 seconds of output should be more than enough + //buffer = malloc(22050*10); + + // buffer = (char*) calloc(1, sizeof(char)); + + /* + freq2data = &mem[45136]; + freq1data = &mem[45056]; + freq3data = &mem[45216]; + */ + //pitches = &mem[43008]; + /* + frequency1 = &mem[43264]; + frequency2 = &mem[43520]; + frequency3 = &mem[43776]; + */ + /* + amplitude1 = &mem[44032]; + amplitude2 = &mem[44288]; + amplitude3 = &mem[44544]; + */ + //phoneme = &mem[39904]; + /* + ampl1data = &mem[45296]; + ampl2data = &mem[45376]; + ampl3data = &mem[45456]; + */ + + for(i = 0; i < 256; i++) { + stress[i] = 0; + phonemeLength[i] = 0; + } + + for(i = 0; i < 60; i++) { + phonemeIndexOutput[i] = 0; + stressOutput[i] = 0; + phonemeLengthOutput[i] = 0; + } + phonemeindex[255] = + 255; //to prevent buffer overflow // ML : changed from 32 to 255 to stop freezing with long inputs +} + +//int Code39771() +int STM32SAM::SAMMain() { + Init(); + phonemeindex[255] = 32; //to prevent buffer overflow + + if(!Parser1()) { + return 0; + } + + Parser2(); + CopyStress(); + SetPhonemeLength(); + AdjustLengths(); + Code41240(); + do { + A = phonemeindex[X]; + if(A > 80) { + phonemeindex[X] = 255; + break; // error: delete all behind it + } + X++; + } while(X != 0); + + //pos39848: + InsertBreath(); + + //mem[40158] = 255; + + PrepareOutput(); + + return 1; +} + +//void Code48547() +void STM32SAM::PrepareOutput() { + A = 0; + X = 0; + Y = 0; + + //pos48551: + while(1) { + A = phonemeindex[X]; + if(A == 255) { + A = 255; + phonemeIndexOutput[Y] = 255; + Render(); + return; + } + if(A == 254) { + X++; + int temp = X; + //mem[48546] = X; + phonemeIndexOutput[Y] = 255; + Render(); + //X = mem[48546]; + X = temp; + Y = 0; + continue; + } + + if(A == 0) { + X++; + continue; + } + + phonemeIndexOutput[Y] = A; + phonemeLengthOutput[Y] = phonemeLength[X]; + stressOutput[Y] = stress[X]; + X++; + Y++; + } +} + +//void Code41014() +void STM32SAM::Insert( + unsigned char position /*var57*/, + unsigned char mem60, + unsigned char mem59, + unsigned char mem58) { + int i; + for(i = 253; i >= position; i--) // ML : always keep last safe-guarding 255 + { + phonemeindex[i + 1] = phonemeindex[i]; + phonemeLength[i + 1] = phonemeLength[i]; + stress[i + 1] = stress[i]; + } + + phonemeindex[position] = mem60; + phonemeLength[position] = mem59; + stress[position] = mem58; + return; +} + +//void Code48431() +void STM32SAM::InsertBreath() { + unsigned char mem54; + unsigned char mem55; + unsigned char index; //variable Y + mem54 = 255; + X++; + mem55 = 0; + unsigned char mem66 = 0; + while(1) { + //pos48440: + X = mem66; + index = phonemeindex[X]; + if(index == 255) return; + mem55 += phonemeLength[X]; + + if(mem55 < 232) { + if(index != 254) // ML : Prevents an index out of bounds problem + { + A = flags2[index] & 1; + if(A != 0) { + X++; + mem55 = 0; + Insert(X, 254, mem59, 0); + mem66++; + mem66++; + continue; + } + } + if(index == 0) mem54 = X; + mem66++; + continue; + } + X = mem54; + phonemeindex[X] = 31; // 'Q*' glottal stop + phonemeLength[X] = 4; + stress[X] = 0; + X++; + mem55 = 0; + Insert(X, 254, mem59, 0); + X++; + mem66 = X; + } +} + +// Iterates through the phoneme buffer, copying the stress value from +// the following phoneme under the following circumstance: + +// 1. The current phoneme is voiced, excluding plosives and fricatives +// 2. The following phoneme is voiced, excluding plosives and fricatives, and +// 3. The following phoneme is stressed +// +// In those cases, the stress value+1 from the following phoneme is copied. +// +// For example, the word LOITER is represented as LOY5TER, with as stress +// of 5 on the diphtong OY. This routine will copy the stress value of 6 (5+1) +// to the L that precedes it. + +//void Code41883() +void STM32SAM::CopyStress() { + // loop thought all the phonemes to be output + unsigned char pos = 0; //mem66 + while(1) { + // get the phomene + Y = phonemeindex[pos]; + + // exit at end of buffer + if(Y == 255) return; + + // if CONSONANT_FLAG set, skip - only vowels get stress + if((flags[Y] & 64) == 0) { + pos++; + continue; + } + // get the next phoneme + Y = phonemeindex[pos + 1]; + if(Y == 255) //prevent buffer overflow + { + pos++; + continue; + } else + // if the following phoneme is a vowel, skip + if((flags[Y] & 128) == 0) { + pos++; + continue; + } + + // get the stress value at the next position + Y = stress[pos + 1]; + + // if next phoneme is not stressed, skip + if(Y == 0) { + pos++; + continue; + } + + // if next phoneme is not a VOWEL OR ER, skip + if((Y & 128) != 0) { + pos++; + continue; + } + + // copy stress from prior phoneme to this one + stress[pos] = Y + 1; + + // advance pointer + pos++; + } +} + +// The input[] buffer contains a string of phonemes and stress markers along +// the lines of: +// +// DHAX KAET IHZ AH5GLIY. <0x9B> +// +// The byte 0x9B marks the end of the buffer. Some phonemes are 2 bytes +// long, such as "DH" and "AX". Others are 1 byte long, such as "T" and "Z". +// There are also stress markers, such as "5" and ".". +// +// The first character of the phonemes are stored in the table signInputTable1[]. +// The second character of the phonemes are stored in the table signInputTable2[]. +// The stress characters are arranged in low to high stress order in stressInputTable[]. +// +// The following process is used to parse the input[] buffer: +// +// Repeat until the <0x9B> character is reached: +// +// First, a search is made for a 2 character match for phonemes that do not +// end with the '*' (wildcard) character. On a match, the index of the phoneme +// is added to phonemeIndex[] and the buffer position is advanced 2 bytes. +// +// If this fails, a search is made for a 1 character match against all +// phoneme names ending with a '*' (wildcard). If this succeeds, the +// phoneme is added to phonemeIndex[] and the buffer position is advanced +// 1 byte. +// +// If this fails, search for a 1 character match in the stressInputTable[]. +// If this succeeds, the stress value is placed in the last stress[] table +// at the same index of the last added phoneme, and the buffer position is +// advanced by 1 byte. +// +// If this fails, return a 0. +// +// On success: +// +// 1. phonemeIndex[] will contain the index of all the phonemes. +// 2. The last index in phonemeIndex[] will be 255. +// 3. stress[] will contain the stress value for each phoneme + +// input[] holds the string of phonemes, each two bytes wide +// signInputTable1[] holds the first character of each phoneme +// signInputTable2[] holds te second character of each phoneme +// phonemeIndex[] holds the indexes of the phonemes after parsing input[] +// +// The parser scans through the input[], finding the names of the phonemes +// by searching signInputTable1[] and signInputTable2[]. On a match, it +// copies the index of the phoneme into the phonemeIndexTable[]. +// +// The character <0x9B> marks the end of text in input[]. When it is reached, +// the index 255 is placed at the end of the phonemeIndexTable[], and the +// function returns with a 1 indicating success. +int STM32SAM::Parser1() { + int i; + unsigned char sign1; + unsigned char sign2; + unsigned char position = 0; + X = 0; + A = 0; + Y = 0; + + // CLEAR THE STRESS TABLE + for(i = 0; i < 256; i++) stress[i] = 0; + + // THIS CODE MATCHES THE PHONEME LETTERS TO THE TABLE + // pos41078: + while(1) { + // GET THE FIRST CHARACTER FROM THE PHONEME BUFFER + sign1 = input[X]; + // TEST FOR 155 (�) END OF LINE MARKER + if(sign1 == 155) { + // MARK ENDPOINT AND RETURN + phonemeindex[position] = 255; //mark endpoint + // REACHED END OF PHONEMES, SO EXIT + return 1; //all ok + } + + // GET THE NEXT CHARACTER FROM THE BUFFER + X++; + sign2 = input[X]; + + // NOW sign1 = FIRST CHARACTER OF PHONEME, AND sign2 = SECOND CHARACTER OF PHONEME + + // TRY TO MATCH PHONEMES ON TWO TWO-CHARACTER NAME + // IGNORE PHONEMES IN TABLE ENDING WITH WILDCARDS + + // SET INDEX TO 0 + Y = 0; + pos41095: + + // GET FIRST CHARACTER AT POSITION Y IN signInputTable + // --> should change name to PhonemeNameTable1 + A = signInputTable1[Y]; + + // FIRST CHARACTER MATCHES? + if(A == sign1) { + // GET THE CHARACTER FROM THE PhonemeSecondLetterTable + A = signInputTable2[Y]; + // NOT A SPECIAL AND MATCHES SECOND CHARACTER? + if((A != '*') && (A == sign2)) { + // STORE THE INDEX OF THE PHONEME INTO THE phomeneIndexTable + phonemeindex[position] = Y; + + // ADVANCE THE POINTER TO THE phonemeIndexTable + position++; + // ADVANCE THE POINTER TO THE phonemeInputBuffer + X++; + + // CONTINUE PARSING + continue; + } + } + + // NO MATCH, TRY TO MATCH ON FIRST CHARACTER TO WILDCARD NAMES (ENDING WITH '*') + + // ADVANCE TO THE NEXT POSITION + Y++; + // IF NOT END OF TABLE, CONTINUE + if(Y != 81) goto pos41095; + + // REACHED END OF TABLE WITHOUT AN EXACT (2 CHARACTER) MATCH. + // THIS TIME, SEARCH FOR A 1 CHARACTER MATCH AGAINST THE WILDCARDS + + // RESET THE INDEX TO POINT TO THE START OF THE PHONEME NAME TABLE + Y = 0; + pos41134: + // DOES THE PHONEME IN THE TABLE END WITH '*'? + if(signInputTable2[Y] == '*') { + // DOES THE FIRST CHARACTER MATCH THE FIRST LETTER OF THE PHONEME + if(signInputTable1[Y] == sign1) { + // SAVE THE POSITION AND MOVE AHEAD + phonemeindex[position] = Y; + + // ADVANCE THE POINTER + position++; + + // CONTINUE THROUGH THE LOOP + continue; + } + } + Y++; + if(Y != 81) goto pos41134; //81 is size of PHONEME NAME table + + // FAILED TO MATCH WITH A WILDCARD. ASSUME THIS IS A STRESS + // CHARACTER. SEARCH THROUGH THE STRESS TABLE + + // SET INDEX TO POSITION 8 (END OF STRESS TABLE) + Y = 8; + + // WALK BACK THROUGH TABLE LOOKING FOR A MATCH + while((sign1 != stressInputTable[Y]) && (Y > 0)) { + // DECREMENT INDEX + Y--; + } + + // REACHED THE END OF THE SEARCH WITHOUT BREAKING OUT OF LOOP? + if(Y == 0) { + //mem[39444] = X; + //41181: JSR 42043 //Error + // FAILED TO MATCH ANYTHING, RETURN 0 ON FAILURE + return 0; + } + // SET THE STRESS FOR THE PRIOR PHONEME + stress[position - 1] = Y; + } //while +} + +//change phonemelength depedendent on stress +//void Code41203() +void STM32SAM::SetPhonemeLength() { + unsigned char A; + int position = 0; + while(phonemeindex[position] != 255) { + A = stress[position]; + //41218: BMI 41229 + if((A == 0) || ((A & 128) != 0)) { + phonemeLength[position] = phonemeLengthTable[phonemeindex[position]]; + } else { + phonemeLength[position] = phonemeStressedLengthTable[phonemeindex[position]]; + } + position++; + } +} + +void STM32SAM::Code41240() { + unsigned char pos = 0; + + while(phonemeindex[pos] != 255) { + unsigned char index; //register AC + X = pos; + index = phonemeindex[pos]; + if((flags[index] & 2) == 0) { + pos++; + continue; + } else if((flags[index] & 1) == 0) { + Insert(pos + 1, index + 1, phonemeLengthTable[index + 1], stress[pos]); + Insert(pos + 2, index + 2, phonemeLengthTable[index + 2], stress[pos]); + pos += 3; + continue; + } + + do { + X++; + A = phonemeindex[X]; + } while(A == 0); + + if(A != 255) { + if((flags[A] & 8) != 0) { + pos++; + continue; + } + if((A == 36) || (A == 37)) { + pos++; // '/H' '/X' + continue; + } + } + + Insert(pos + 1, index + 1, phonemeLengthTable[index + 1], stress[pos]); + Insert(pos + 2, index + 2, phonemeLengthTable[index + 2], stress[pos]); + pos += 3; + } +} + +// Rewrites the phonemes using the following rules: +// +// -> WX +// -> YX +// UL -> AX L +// UM -> AX M +// -> Q +// T R -> CH R +// D R -> J R +// R -> RX +// L -> LX +// G S -> G Z +// K -> KX +// G -> GX +// S P -> S B +// S T -> S D +// S K -> S G +// S KX -> S GX +// UW -> UX +// CH -> CH CH' (CH requires two phonemes to represent it) +// J -> J J' (J requires two phonemes to represent it) +// T -> DX +// D -> DX + +//void Code41397() +void STM32SAM::Parser2() { + unsigned char pos = 0; //mem66; + unsigned char mem58 = 0; + + // Loop through phonemes + while(1) { + // SET X TO THE CURRENT POSITION + X = pos; + // GET THE PHONEME AT THE CURRENT POSITION + A = phonemeindex[pos]; + + // Is phoneme pause? + if(A == 0) { + // Move ahead to the + pos++; + continue; + } + + // If end of phonemes flag reached, exit routine + if(A == 255) return; + + // Copy the current phoneme index to Y + Y = A; + + // RULE: + // -> WX + // -> YX + // Example: OIL, COW + + // Check for DIPHTONG + if((flags[A] & 16) == 0) goto pos41457; + + // Not a diphthong. Get the stress + mem58 = stress[pos]; + + // End in IY sound? + A = flags[Y] & 32; + + // If ends with IY, use YX, else use WX + if(A == 0) + A = 20; + else + A = 21; // 'WX' = 20 'YX' = 21 + //pos41443: + // Insert at WX or YX following, copying the stress + + Insert(pos + 1, A, mem59, mem58); + X = pos; + // Jump to ??? + goto pos41749; + + pos41457: + + // RULE: + // UL -> AX L + // Example: MEDDLE + + // Get phoneme + A = phonemeindex[X]; + // Skip this rule if phoneme is not UL + if(A != 78) goto pos41487; // 'UL' + A = 24; // 'L' //change 'UL' to 'AX L' + + pos41466: + // Get current phoneme stress + mem58 = stress[X]; + + // Change UL to AX + phonemeindex[X] = 13; // 'AX' + // Perform insert. Note code below may jump up here with different values + Insert(X + 1, A, mem59, mem58); + pos++; + // Move to next phoneme + continue; + + pos41487: + + // RULE: + // UM -> AX M + // Example: ASTRONOMY + + // Skip rule if phoneme != UM + if(A != 79) goto pos41495; // 'UM' + // Jump up to branch - replaces current phoneme with AX and continues + A = 27; // 'M' //change 'UM' to 'AX M' + + goto pos41466; + pos41495: + + // RULE: + // UN -> AX N + // Example: FUNCTION + + // Skip rule if phoneme != UN + if(A != 80) goto pos41503; // 'UN' + + // Jump up to branch - replaces current phoneme with AX and continues + A = 28; // 'N' //change UN to 'AX N' + + goto pos41466; + pos41503: + + // RULE: + // -> Q + // EXAMPLE: AWAY EIGHT + + Y = A; + // VOWEL set? + A = flags[A] & 128; + + // Skip if not a vowel + if(A != 0) { + // Get the stress + A = stress[X]; + + // If stressed... + if(A != 0) { + // Get the following phoneme + X++; + A = phonemeindex[X]; + // If following phoneme is a pause + + if(A == 0) { + // Get the phoneme following pause + X++; + Y = phonemeindex[X]; + + // Check for end of buffer flag + if(Y == 255) //buffer overflow + // ??? Not sure about these flags + A = 65 & 128; + else + // And VOWEL flag to current phoneme's flags + A = flags[Y] & 128; + + // If following phonemes is not a pause + if(A != 0) { + // If the following phoneme is not stressed + A = stress[X]; + if(A != 0) { + // 31 = 'Q' + Insert(X, 31, mem59, 0); + pos++; + continue; + } + } + } + } + } + + // RULES FOR PHONEMES BEFORE R + // T R -> CH R + // Example: TRACK + + // Get current position and phoneme + X = pos; + A = phonemeindex[pos]; + if(A != 23) goto pos41611; // 'R' + + // Look at prior phoneme + X--; + A = phonemeindex[pos - 1]; + //pos41567: + if(A == 69) // 'T' + { + phonemeindex[pos - 1] = 42; + goto pos41779; + } + + // RULES FOR PHONEMES BEFORE R + // D R -> J R + // Example: DRY + + // Prior phonemes D? + if(A == 57) // 'D' + { + // Change D to J + phonemeindex[pos - 1] = 44; + + goto pos41788; + } + + // RULES FOR PHONEMES BEFORE R + // R -> RX + // Example: ART + + // If vowel flag is set change R to RX + A = flags[A] & 128; + + if(A != 0) phonemeindex[pos] = 18; // 'RX' + + // continue to next phoneme + pos++; + continue; + + pos41611: + + // RULE: + // L -> LX + // Example: ALL + + // Is phoneme L? + if(A == 24) // 'L' + { + // If prior phoneme does not have VOWEL flag set, move to next phoneme + if((flags[phonemeindex[pos - 1]] & 128) == 0) { + pos++; + continue; + } + // Prior phoneme has VOWEL flag set, so change L to LX and move to next phoneme + + phonemeindex[X] = 19; // 'LX' + pos++; + continue; + } + + // RULE: + // G S -> G Z + // + // Can't get to fire - + // 1. The G -> GX rule intervenes + // 2. Reciter already replaces GS -> GZ + + // Is current phoneme S? + if(A == 32) // 'S' + { + // If prior phoneme is not G, move to next phoneme + if(phonemeindex[pos - 1] != 60) { + pos++; + continue; + } + // Replace S with Z and move on + + phonemeindex[pos] = 38; // 'Z' + pos++; + continue; + } + + // RULE: + // K -> KX + // Example: COW + + // Is current phoneme K? + if(A == 72) // 'K' + { + // Get next phoneme + Y = phonemeindex[pos + 1]; + // If at end, replace current phoneme with KX + if(Y == 255) + phonemeindex[pos] = 75; // ML : prevents an index out of bounds problem + else { + // VOWELS AND DIPHTONGS ENDING WITH IY SOUND flag set? + A = flags[Y] & 32; + + // Replace with KX + if(A == 0) phonemeindex[pos] = 75; // 'KX' + } + } else + + // RULE: + // G -> GX + // Example: GO + + // Is character a G? + if(A == 60) // 'G' + { + // Get the following character + unsigned char index = phonemeindex[pos + 1]; + + // At end of buffer? + if(index == 255) //prevent buffer overflow + { + pos++; + continue; + } else + // If diphtong ending with YX, move continue processing next phoneme + if((flags[index] & 32) != 0) { + pos++; + continue; + } + // replace G with GX and continue processing next phoneme + + phonemeindex[pos] = 63; // 'GX' + pos++; + continue; + } + + // RULE: + // S P -> S B + // S T -> S D + // S K -> S G + // S KX -> S GX + // Examples: SPY, STY, SKY, SCOWL + + Y = phonemeindex[pos]; + //pos41719: + // Replace with softer version? + A = flags[Y] & 1; + if(A == 0) goto pos41749; + A = phonemeindex[pos - 1]; + if(A != 32) // 'S' + { + A = Y; + goto pos41812; + } + // Replace with softer version + + phonemeindex[pos] = Y - 12; + pos++; + continue; + + pos41749: + + // RULE: + // UW -> UX + // + // Example: NEW, DEW, SUE, ZOO, THOO, TOO + + // UW -> UX + + A = phonemeindex[X]; + if(A == 53) // 'UW' + { + // ALVEOLAR flag set? + Y = phonemeindex[X - 1]; + A = flags2[Y] & 4; + // If not set, continue processing next phoneme + if(A == 0) { + pos++; + continue; + } + + phonemeindex[X] = 16; + pos++; + continue; + } + pos41779: + + // RULE: + // CH -> CH CH' (CH requires two phonemes to represent it) + // Example: CHEW + + if(A == 42) // 'CH' + { + // pos41783: + + Insert(X + 1, A + 1, mem59, stress[X]); + pos++; + continue; + } + + pos41788: + + // RULE: + // J -> J J' (J requires two phonemes to represent it) + // Example: JAY + + if(A == 44) // 'J' + { + Insert(X + 1, A + 1, mem59, stress[X]); + pos++; + continue; + } + + // Jump here to continue + pos41812: + + // RULE: Soften T following vowel + // NOTE: This rule fails for cases such as "ODD" + // T -> DX + // D -> DX + // Example: PARTY, TARDY + + // Past this point, only process if phoneme is T or D + + if(A != 69) // 'T' + if(A != 57) { + pos++; // 'D' + continue; + } + //pos41825: + + // If prior phoneme is not a vowel, continue processing phonemes + if((flags[phonemeindex[X - 1]] & 128) == 0) { + pos++; + continue; + } + + // Get next phoneme + X++; + A = phonemeindex[X]; + //pos41841 + // Is the next phoneme a pause? + if(A != 0) { + // If next phoneme is not a pause, continue processing phonemes + if((flags[A] & 128) == 0) { + pos++; + continue; + } + // If next phoneme is stressed, continue processing phonemes + // FIXME: How does a pause get stressed? + if(stress[X] != 0) { + pos++; + continue; + } + //pos41856: + // Set phonemes to DX + + phonemeindex[pos] = 30; // 'DX' + } else { + A = phonemeindex[X + 1]; + if(A == 255) //prevent buffer overflow + A = 65 & 128; + else + // Is next phoneme a vowel or ER? + A = flags[A] & 128; + + if(A != 0) phonemeindex[pos] = 30; // 'DX' + } + + pos++; + + } // while +} // parser 2 + +// Applies various rules that adjust the lengths of phonemes +// +// Lengthen or between and by 1.5 +// - decrease length by 1 +// - decrease vowel by 1/8th +// - increase vowel by 1/2 + 1 +// - set nasal = 5, consonant = 6 +// {optional silence} - shorten both to 1/2 + 1 +// - decrease by 2 + +//void Code48619() +void STM32SAM::AdjustLengths() { + // LENGTHEN VOWELS PRECEDING PUNCTUATION + // + // Search for punctuation. If found, back up to the first vowel, then + // process all phonemes between there and up to (but not including) the punctuation. + // If any phoneme is found that is a either a fricative or voiced, the duration is + // increased by (length * 1.5) + 1 + + // loop index + X = 0; + unsigned char index; + + // iterate through the phoneme list + unsigned char loopIndex = 0; + while(1) { + // get a phoneme + index = phonemeindex[X]; + + // exit loop if end on buffer token + if(index == 255) break; + + // not punctuation? + if((flags2[index] & 1) == 0) { + // skip + X++; + continue; + } + + // hold index + loopIndex = X; + + // Loop backwards from this point + pos48644: + + // back up one phoneme + X--; + + // stop once the beginning is reached + if(X == 0) break; + + // get the preceding phoneme + index = phonemeindex[X]; + + if(index != 255) //inserted to prevent access overrun + if((flags[index] & 128) == 0) goto pos48644; // if not a vowel, continue looping + + //pos48657: + do { + // test for vowel + index = phonemeindex[X]; + + if(index != 255) //inserted to prevent access overrun + // test for fricative/unvoiced or not voiced + if(((flags2[index] & 32) == 0) || ((flags[index] & 4) != 0)) //nochmal �berpr�fen + { + //A = flags[Y] & 4; + //if(A == 0) goto pos48688; + + // get the phoneme length + A = phonemeLength[X]; + + // change phoneme length to (length * 1.5) + 1 + A = (A >> 1) + A + 1; + + phonemeLength[X] = A; + } + // keep moving forward + X++; + } while(X != loopIndex); + // if (X != loopIndex) goto pos48657; + X++; + } // while + + // Similar to the above routine, but shorten vowels under some circumstances + + // Loop throught all phonemes + loopIndex = 0; + //pos48697 + + while(1) { + // get a phoneme + X = loopIndex; + index = phonemeindex[X]; + + // exit routine at end token + if(index == 255) return; + + // vowel? + A = flags[index] & 128; + if(A != 0) { + // get next phoneme + X++; + index = phonemeindex[X]; + + // get flags + if(index == 255) + mem56 = 65; // use if end marker + else + mem56 = flags[index]; + + // not a consonant + if((flags[index] & 64) == 0) { + // RX or LX? + if((index == 18) || (index == 19)) // 'RX' & 'LX' + { + // get the next phoneme + X++; + index = phonemeindex[X]; + + // next phoneme a consonant? + if((flags[index] & 64) != 0) { + // RULE: RX | LX + + // decrease length of vowel by 1 frame + phonemeLength[loopIndex]--; + } + // move ahead + loopIndex++; + continue; + } + // move ahead + loopIndex++; + continue; + } + + // Got here if not + + // not voiced + if((mem56 & 4) == 0) { + // Unvoiced + // *, .*, ?*, ,*, -*, DX, S*, SH, F*, TH, /H, /X, CH, P*, T*, K*, KX + + // not an unvoiced plosive? + if((mem56 & 1) == 0) { + // move ahead + loopIndex++; + continue; + } + + // P*, T*, K*, KX + + // RULE: + // + + // move back + X--; + + // decrease length by 1/8th + mem56 = phonemeLength[X] >> 3; + phonemeLength[X] -= mem56; + + // move ahead + loopIndex++; + continue; + } + + // RULE: + // + + // decrease length + A = phonemeLength[X - 1]; + phonemeLength[X - 1] = (A >> 2) + A + 1; // 5/4*A + 1 + + // move ahead + loopIndex++; + continue; + } + + // WH, R*, L*, W*, Y*, M*, N*, NX, Q*, Z*, ZH, V*, DH, J*, B*, D*, G*, GX + + //pos48821: + + // RULE: + // Set punctuation length to 6 + // Set stop consonant length to 5 + + // nasal? + if((flags2[index] & 8) != 0) { + // M*, N*, NX, + + // get the next phoneme + X++; + index = phonemeindex[X]; + + // end of buffer? + if(index == 255) + A = 65 & 2; //prevent buffer overflow + else + A = flags[index] & 2; // check for stop consonant + + // is next phoneme a stop consonant? + if(A != 0) + + // B*, D*, G*, GX, P*, T*, K*, KX + + { + // set stop consonant length to 6 + phonemeLength[X] = 6; + + // set nasal length to 5 + phonemeLength[X - 1] = 5; + } + // move to next phoneme + loopIndex++; + continue; + } + + // WH, R*, L*, W*, Y*, Q*, Z*, ZH, V*, DH, J*, B*, D*, G*, GX + + // RULE: {optional silence} + // Shorten both to (length/2 + 1) + + // (voiced) stop consonant? + if((flags[index] & 2) != 0) { + // B*, D*, G*, GX + + // move past silence + do { + // move ahead + X++; + index = phonemeindex[X]; + } while(index == 0); + + // check for end of buffer + if(index == 255) //buffer overflow + { + // ignore, overflow code + if((65 & 2) == 0) { + loopIndex++; + continue; + } + } else if((flags[index] & 2) == 0) { + // if another stop consonant, move ahead + loopIndex++; + continue; + } + + // RULE: {optional silence} + + // X gets overwritten, so hold prior X value for debug statement + // int debugX = X; + // shorten the prior phoneme length to (length/2 + 1) + phonemeLength[X] = (phonemeLength[X] >> 1) + 1; + X = loopIndex; + + // also shorten this phoneme length to (length/2 +1) + phonemeLength[loopIndex] = (phonemeLength[loopIndex] >> 1) + 1; + + // move ahead + loopIndex++; + continue; + } + + // WH, R*, L*, W*, Y*, Q*, Z*, ZH, V*, DH, J*, **, + + // RULE: + // Decrease by 2 + + // liquic consonant? + if((flags2[index] & 16) != 0) { + // R*, L*, W*, Y* + + // get the prior phoneme + index = phonemeindex[X - 1]; + + // prior phoneme a stop consonant> + if((flags[index] & 2) != 0) { + // Rule: + + // decrease the phoneme length by 2 frames (20 ms) + phonemeLength[X] -= 2; + } + } + + // move to next phoneme + loopIndex++; + continue; + } + // goto pos48701; +} + +// ------------------------------------------------------------------------- +// ML : Code47503 is division with remainder, and mem50 gets the sign +void STM32SAM::Code47503(unsigned char mem52) { + Y = 0; + if((mem53 & 128) != 0) { + mem53 = -mem53; + Y = 128; + } + mem50 = Y; + A = 0; + for(X = 8; X > 0; X--) { + int temp = mem53; + mem53 = mem53 << 1; + A = A << 1; + if(temp >= 128) A++; + if(A >= mem52) { + A = A - mem52; + mem53++; + } + } + + mem51 = A; + if((mem50 & 128) != 0) mem53 = -mem53; +} + +//////////////////////////////////////////////////////////////////////////////////////////// +// +// Reciter +// +//////////////////////////////////////////////////////////////////////////////////////////// + +void STM32SAM::Code37055(unsigned char mem59) { + X = mem59; + X--; + A = inputtemp[X]; + Y = A; + A = tab36376[Y]; + return; +} + +void STM32SAM::Code37066(unsigned char mem58) { + X = mem58; + X++; + A = inputtemp[X]; + Y = A; + A = tab36376[Y]; +} + +unsigned char STM32SAM::GetRuleByte(unsigned short mem62, unsigned char Y) { + unsigned int address = mem62; + + if(mem62 >= 37541) { + address -= 37541; + return rules2[address + Y]; + } + address -= 32000; + return rules[address + Y]; +} + +int STM32SAM::TextToPhonemes(unsigned char* input) // Code36484 +{ + //unsigned char *tab39445 = &mem[39445]; //input and output + //unsigned char mem29; + unsigned char mem56; //output position for phonemes + unsigned char mem57; + unsigned char mem58; + unsigned char mem59; + unsigned char mem60; + unsigned char mem61; + unsigned short mem62; // memory position of current rule + + unsigned char mem64; // position of '=' or current character + unsigned char mem65; // position of ')' + unsigned char mem66; // position of '(' + unsigned char mem36653; + + inputtemp[0] = 32; + + // secure copy of input + // because input will be overwritten by phonemes + X = 1; + Y = 0; + do { + //pos36499: + A = input[Y] & 127; + if(A >= 112) + A = A & 95; + else if(A >= 96) + A = A & 79; + + inputtemp[X] = A; + X++; + Y++; + } while(Y != 255); + + X = 255; + inputtemp[X] = 27; + mem61 = 255; + +pos36550: + A = 255; + mem56 = 255; + +pos36554: + while(1) { + mem61++; + X = mem61; + A = inputtemp[X]; + mem64 = A; + if(A == '[') { + mem56++; + X = mem56; + A = 155; + input[X] = 155; + //goto pos36542; + // Code39771(); //Code39777(); + return 1; + } + + //pos36579: + if(A != '.') break; + X++; + Y = inputtemp[X]; + A = tab36376[Y] & 1; + if(A != 0) break; + mem56++; + X = mem56; + A = '.'; + input[X] = '.'; + } //while + + //pos36607: + A = mem64; + Y = A; + A = tab36376[A]; + mem57 = A; + if((A & 2) != 0) { + mem62 = 37541; + goto pos36700; + } + + //pos36630: + A = mem57; + if(A != 0) goto pos36677; + A = 32; + inputtemp[X] = ' '; + mem56++; + X = mem56; + if(X > 120) goto pos36654; + input[X] = A; + goto pos36554; + + // ----- + + //36653 is unknown. Contains position + +pos36654: + input[X] = 155; + A = mem61; + mem36653 = A; + // mem29 = A; // not used + // Code36538(); das ist eigentlich + return 1; + //Code39771(); + //go on if there is more input ??? + mem61 = mem36653; + goto pos36550; + +pos36677: + A = mem57 & 128; + if(A == 0) { + //36683: BRK + return 0; + } + + // go to the right rules for this character. + X = mem64 - 'A'; + mem62 = tab37489[X] | (tab37515[X] << 8); + + // ------------------------------------- + // go to next rule + // ------------------------------------- + +pos36700: + + // find next rule + Y = 0; + do { + mem62 += 1; + A = GetRuleByte(mem62, Y); + } while((A & 128) == 0); + Y++; + + //pos36720: + // find '(' + while(1) { + A = GetRuleByte(mem62, Y); + if(A == '(') break; + Y++; + } + mem66 = Y; + + //pos36732: + // find ')' + do { + Y++; + A = GetRuleByte(mem62, Y); + } while(A != ')'); + mem65 = Y; + + //pos36741: + // find '=' + do { + Y++; + A = GetRuleByte(mem62, Y); + A = A & 127; + } while(A != '='); + mem64 = Y; + + X = mem61; + mem60 = X; + + // compare the string within the bracket + Y = mem66; + Y++; + //pos36759: + while(1) { + mem57 = inputtemp[X]; + A = GetRuleByte(mem62, Y); + if(A != mem57) goto pos36700; + Y++; + if(Y == mem65) break; + X++; + mem60 = X; + } + + // the string in the bracket is correct + + //pos36787: + A = mem61; + mem59 = mem61; + +pos36791: + while(1) { + mem66--; + Y = mem66; + A = GetRuleByte(mem62, Y); + mem57 = A; + //36800: BPL 36805 + if((A & 128) != 0) goto pos37180; + X = A & 127; + A = tab36376[X] & 128; + if(A == 0) break; + X = mem59 - 1; + A = inputtemp[X]; + if(A != mem57) goto pos36700; + mem59 = X; + } + + //pos36833: + A = mem57; + if(A == ' ') goto pos36895; + if(A == '#') goto pos36910; + if(A == '.') goto pos36920; + if(A == '&') goto pos36935; + if(A == '@') goto pos36967; + if(A == '^') goto pos37004; + if(A == '+') goto pos37019; + if(A == ':') goto pos37040; + // Code42041(); //Error + //36894: BRK + return 0; + + // -------------- + +pos36895: + Code37055(mem59); + A = A & 128; + if(A != 0) goto pos36700; +pos36905: + mem59 = X; + goto pos36791; + + // -------------- + +pos36910: + Code37055(mem59); + A = A & 64; + if(A != 0) goto pos36905; + goto pos36700; + + // -------------- + +pos36920: + Code37055(mem59); + A = A & 8; + if(A == 0) goto pos36700; +pos36930: + mem59 = X; + goto pos36791; + + // -------------- + +pos36935: + Code37055(mem59); + A = A & 16; + if(A != 0) goto pos36930; + A = inputtemp[X]; + if(A != 72) goto pos36700; + X--; + A = inputtemp[X]; + if((A == 67) || (A == 83)) goto pos36930; + goto pos36700; + + // -------------- + +pos36967: + Code37055(mem59); + A = A & 4; + if(A != 0) goto pos36930; + A = inputtemp[X]; + if(A != 72) goto pos36700; + if((A != 84) && (A != 67) && (A != 83)) goto pos36700; + mem59 = X; + goto pos36791; + + // -------------- + +pos37004: + Code37055(mem59); + A = A & 32; + if(A == 0) goto pos36700; + +pos37014: + mem59 = X; + goto pos36791; + + // -------------- + +pos37019: + X = mem59; + X--; + A = inputtemp[X]; + if((A == 'E') || (A == 'I') || (A == 'Y')) goto pos37014; + goto pos36700; + // -------------- + +pos37040: + Code37055(mem59); + A = A & 32; + if(A == 0) goto pos36791; + mem59 = X; + goto pos37040; + + //--------------------------------------- + +pos37077: + X = mem58 + 1; + A = inputtemp[X]; + if(A != 'E') goto pos37157; + X++; + Y = inputtemp[X]; + X--; + A = tab36376[Y] & 128; + if(A == 0) goto pos37108; + X++; + A = inputtemp[X]; + if(A != 'R') goto pos37113; +pos37108: + mem58 = X; + goto pos37184; +pos37113: + if((A == 83) || (A == 68)) goto pos37108; // 'S' 'D' + if(A != 76) goto pos37135; // 'L' + X++; + A = inputtemp[X]; + if(A != 89) goto pos36700; + goto pos37108; + +pos37135: + if(A != 70) goto pos36700; + X++; + A = inputtemp[X]; + if(A != 85) goto pos36700; + X++; + A = inputtemp[X]; + if(A == 76) goto pos37108; + goto pos36700; + +pos37157: + if(A != 73) goto pos36700; + X++; + A = inputtemp[X]; + if(A != 78) goto pos36700; + X++; + A = inputtemp[X]; + if(A == 71) goto pos37108; + //pos37177: + goto pos36700; + + // ----------------------------------------- + +pos37180: + + A = mem60; + mem58 = A; + +pos37184: + Y = mem65 + 1; + + //37187: CPY 64 + // if(? != 0) goto pos37194; + if(Y == mem64) goto pos37455; + mem65 = Y; + //37196: LDA (62),y + A = GetRuleByte(mem62, Y); + mem57 = A; + X = A; + A = tab36376[X] & 128; + if(A == 0) goto pos37226; + X = mem58 + 1; + A = inputtemp[X]; + if(A != mem57) goto pos36700; + mem58 = X; + goto pos37184; +pos37226: + A = mem57; + if(A == 32) goto pos37295; // ' ' + if(A == 35) goto pos37310; // '#' + if(A == 46) goto pos37320; // '.' + if(A == 38) goto pos37335; // '&' + if(A == 64) goto pos37367; // '' + if(A == 94) goto pos37404; // '' + if(A == 43) goto pos37419; // '+' + if(A == 58) goto pos37440; // ':' + if(A == 37) goto pos37077; // '%' + //pos37291: + // Code42041(); //Error + //37294: BRK + return 0; + + // -------------- +pos37295: + Code37066(mem58); + A = A & 128; + if(A != 0) goto pos36700; +pos37305: + mem58 = X; + goto pos37184; + + // -------------- + +pos37310: + Code37066(mem58); + A = A & 64; + if(A != 0) goto pos37305; + goto pos36700; + + // -------------- + +pos37320: + Code37066(mem58); + A = A & 8; + if(A == 0) goto pos36700; + +pos37330: + mem58 = X; + goto pos37184; + + // -------------- + +pos37335: + Code37066(mem58); + A = A & 16; + if(A != 0) goto pos37330; + A = inputtemp[X]; + if(A != 72) goto pos36700; + X++; + A = inputtemp[X]; + if((A == 67) || (A == 83)) goto pos37330; + goto pos36700; + + // -------------- + +pos37367: + Code37066(mem58); + A = A & 4; + if(A != 0) goto pos37330; + A = inputtemp[X]; + if(A != 72) goto pos36700; + if((A != 84) && (A != 67) && (A != 83)) goto pos36700; + mem58 = X; + goto pos37184; + + // -------------- + +pos37404: + Code37066(mem58); + A = A & 32; + if(A == 0) goto pos36700; +pos37414: + mem58 = X; + goto pos37184; + + // -------------- + +pos37419: + X = mem58; + X++; + A = inputtemp[X]; + if((A == 69) || (A == 73) || (A == 89)) goto pos37414; + goto pos36700; + + // ---------------------- + +pos37440: + + Code37066(mem58); + A = A & 32; + if(A == 0) goto pos37184; + mem58 = X; + goto pos37440; +pos37455: + Y = mem64; + mem61 = mem60; + +pos37461: + //37461: LDA (62),y + A = GetRuleByte(mem62, Y); + mem57 = A; + A = A & 127; + if(A != '=') { + mem56++; + X = mem56; + input[X] = A; + } + + //37478: BIT 57 + //37480: BPL 37485 //not negative flag + if((mem57 & 128) == 0) goto pos37485; //??? + goto pos36554; +pos37485: + Y++; + goto pos37461; +} + +// Constructor + +STM32SAM::STM32SAM(uint32_t STM32SAM_SPEED /* = 5 */) { + STM32SAM_SPEED = STM32SAM_SPEED & 0x1f; // limit it from 0 to 31 + + _STM32SAM_SPEED = STM32SAM_SPEED; + + // set default voice + + speed = 72; + pitch = 64; + mouth = 128; + throat = 128; + + phonetic = 0; + singmode = 0; + + wait1 = 7; + wait2 = 6; + + mem59 = 0; + + oldtimetableindex = 0; +} + +STM32SAM::STM32SAM() { + _STM32SAM_SPEED = 7; + + // set default voice + + speed = 72; + pitch = 64; + mouth = 128; + throat = 128; + + phonetic = 0; + singmode = 0; + + wait1 = 7; + wait2 = 6; + + mem59 = 0; + + oldtimetableindex = 0; +} + +/* + STM32SAM::~STM32SAM() { + { + // TODO: end(); + } +*/ + +//////////////////////////////////////////////////////////////////////////////////////////// +// +// STM32SAM sam (variable string, phonetic, sing, pitch, speed, mouth, throat) +// STM32SAM say (sing off, phonetic off) (const string) +// STM32SAM say (sing off, phonetic off) (variable string) +// STM32SAM sing (sing on, phonetic off) (const string) +// STM32SAM sing (sing on, phonetic off) (variable string) +// STM32SAM sayPhonetic (sing off, phonetic on) (const string) +// STM32SAM sayPhonetic (sing off, phonetic on) (variable string) +// STM32SAM singPhonetic (sing on, phonetic on) (const string) +// STM32SAM singPhonetic (sing on, phonetic on) (variable string) +// STM32SAM voice (pitch, speed, mouth, throat) +// STM32SAM setPitch (pitch) +// STM32SAM setSpeed (speed) +// STM32SAM setMouth (mouth) +// STM32SAM setThroat (throat) +// +// +//////////////////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////////////////// +// +// STM32SAM sam (const string, phonetic, sing, pitch, speed, mouth, throat) +// +//////////////////////////////////////////////////////////////////////////////////////////// + +char to_upper_case(char c) { + if(c >= 'a' && c <= 'z') { + return c - 'a' + 'A'; + } + return c; +} + +void STM32SAM::sam( + const char* argv, + unsigned char _phonetic, + unsigned char _singmode, + unsigned char _pitch, + unsigned char _speed, + unsigned char _mouth, + unsigned char _throat) { + phonetic = _phonetic; + singmode = _singmode; + pitch = _pitch; + speed = _speed; + mouth = _mouth; + throat = _throat; + + int i; + + for(i = 0; i < 256; i++) { + input[i] = argv[i]; + } + + for(i = 0; input[i] != 0; i++) { + if(i != 0) { + input[i] = to_upper_case((int)argv[i]); + } + } + + if(!phonetic) { + strncat(input, "[", 256); + if(!TextToPhonemes((unsigned char*)input)) { + // PrintUsage(); + return; + } + + } else { + strncat(input, "\x9b", 256); + } + + SetInput(input); + + if(!SAMMain()) { + return; + } +} + +//////////////////////////////////////////////////////////////////////////////////////////// +// +// STM32SAM sam (variable string, phonetic, sing, pitch, speed, mouth, throat) +// +//////////////////////////////////////////////////////////////////////////////////////////// + +void STM32SAM::sam( + char* argv, + unsigned char _phonetic, + unsigned char _singmode, + unsigned char _pitch, + unsigned char _speed, + unsigned char _mouth, + unsigned char _throat) { + phonetic = _phonetic; + singmode = _singmode; + pitch = _pitch; + speed = _speed; + mouth = _mouth; + throat = _throat; + + int i; + + for(i = 0; i < 256; i++) { + input[i] = argv[i]; + } + + for(i = 0; input[i] != 0; i++) { + if(i != 0) { + input[i] = to_upper_case((int)argv[i]); + } + } + + if(i < 256) { + input[i] = phonetic ? '\x9b' : '['; + } + + if(!phonetic) { + if(!TextToPhonemes((unsigned char*)input)) { + return; + } + } + + SetInput(input); + + if(!SAMMain()) { + return; + } +} + +//////////////////////////////////////////////////////////////////////////////////////////// +// +// STM32SAM say(sing off, phonetic off) (const string) +// +//////////////////////////////////////////////////////////////////////////////////////////// + +void STM32SAM::say(const char* argv) { + int i; + + phonetic = 0; + singmode = 0; + + char const_input[256]; + + for(i = 0; i < 256; i++) { + const_input[i] = argv[i]; + } + + sam(const_input, phonetic, singmode, pitch, speed, mouth, throat); +} + +void STM32SAM::say(char* argv) { + int i; + + phonetic = 0; + singmode = 0; + + char const_input[256]; + + for(i = 0; i < 256; i++) { + const_input[i] = argv[i]; + } + + sam(const_input, phonetic, singmode, pitch, speed, mouth, throat); +} + +//////////////////////////////////////////////////////////////////////////////////////////// +// +// STM32SAM sing (sing on, phonetic off) +// +//////////////////////////////////////////////////////////////////////////////////////////// + +void STM32SAM::sing(const char* argv) { + int i; + + phonetic = 0; + singmode = 1; + + char const_input[256]; + + for(i = 0; i < 256; i++) { + const_input[i] = argv[i]; + } + + sam(const_input, phonetic, singmode, pitch, speed, mouth, throat); +} + +void STM32SAM::sing(char* argv) { + int i; + + phonetic = 0; + singmode = 1; + + char const_input[256]; + + for(i = 0; i < 256; i++) { + const_input[i] = argv[i]; + } + + sam(const_input, phonetic, singmode, pitch, speed, mouth, throat); +} + +//////////////////////////////////////////////////////////////////////////////////////////// +// +// STM32SAM sayPhonetic (sing off, phonetic on) +// +//////////////////////////////////////////////////////////////////////////////////////////// + +void STM32SAM::sayPhonetic(const char* argv) { + int i; + + phonetic = 1; + singmode = 0; + + char const_input[256]; + + for(i = 0; i < 256; i++) { + const_input[i] = argv[i]; + } + + sam(const_input, phonetic, singmode, pitch, speed, mouth, throat); +} + +void STM32SAM::sayPhonetic(char* argv) { + int i; + + phonetic = 1; + singmode = 0; + + char const_input[256]; + + for(i = 0; i < 256; i++) { + const_input[i] = argv[i]; + } + + sam(const_input, phonetic, singmode, pitch, speed, mouth, throat); +} + +//////////////////////////////////////////////////////////////////////////////////////////// +// +// STM32SAM singPhonetic (sing on, phonetic on) +// +//////////////////////////////////////////////////////////////////////////////////////////// + +void STM32SAM::singPhonetic(const char* argv) { + int i; + + phonetic = 1; + singmode = 1; + + char const_input[256]; + + for(i = 0; i < 256; i++) { + const_input[i] = argv[i]; + } + + sam(const_input, phonetic, singmode, pitch, speed, mouth, throat); +} + +void STM32SAM::singPhonetic(char* argv) { + int i; + + phonetic = 1; + singmode = 0; + + char const_input[256]; + + for(i = 0; i < 256; i++) { + const_input[i] = argv[i]; + } + + sam(const_input, phonetic, singmode, pitch, speed, mouth, throat); +} + +//////////////////////////////////////////////////////////////////////////////////////////// +// +// STM32SAM voice (pitch, speed, mouth, throat) +// +//////////////////////////////////////////////////////////////////////////////////////////// + +void STM32SAM::setVoice( + unsigned char _pitch /* = 64 */, + unsigned char _speed /* = 72 */, + unsigned char _mouth /* = 128 */, + unsigned char _throat /* = 128 */) { + pitch = _pitch; + speed = _speed; + mouth = _mouth; + throat = _throat; +} + +//////////////////////////////////////////////////////////////////////////////////////////// +// +// STM32SAM setPitch (pitch) +// +//////////////////////////////////////////////////////////////////////////////////////////// + +void STM32SAM::setPitch(unsigned char _pitch /* = 64 */) { + pitch = _pitch; +} +//////////////////////////////////////////////////////////////////////////////////////////// +// +// STM32SAM setSpeed (speed) +// +//////////////////////////////////////////////////////////////////////////////////////////// + +void STM32SAM::setSpeed(unsigned char _speed /* = 72 */) { + speed = _speed; +} +//////////////////////////////////////////////////////////////////////////////////////////// +// +// STM32SAM setMouth (mouth) +// +//////////////////////////////////////////////////////////////////////////////////////////// + +void STM32SAM::setMouth(unsigned char _mouth /* = 128 */) { + mouth = _mouth; +} + +//////////////////////////////////////////////////////////////////////////////////////////// +// +// STM32SAM setThroat (throat) +// +//////////////////////////////////////////////////////////////////////////////////////////// + +void STM32SAM::setThroat(unsigned char _throat /* = 128 */) { + throat = _throat; +} +//////////////////////////////////////////////////////////////////////////////////////////// +// +// Hardware +// +//////////////////////////////////////////////////////////////////////////////////////////// +// Hardware specifics, for easier porting to other microcontrollers + +// +// Set PA8 pin as PWM, at 256 timer ticks overflow (8bit resolution) + +#include +#include + +#define FURI_HAL_SPEAKER_TIMER TIM16 +#define FURI_HAL_SPEAKER_CHANNEL LL_TIM_CHANNEL_CH1 + +void STM32SAM::begin(void) { +#ifdef USE_ROGER_CORE + + pinMode(PA8, PWM); // audio output pin + + Timer1.setPeriod( + 4); // Can't set at 256 ticks, only in uS. First nearest uS is 4 (Roger core is only for bluepill, that means 72*4=288 ticks, or 128*4=512 ticks when overclocked. It's ok, just overall volume will be lower, because maximum volume will be 256/288 or 256/512) + +#endif + +#ifdef USE_STM32duino_CORE + pinMode(PA8, OUTPUT); + + PWM->pause(); + PWM->setMode(1, TIMER_OUTPUT_COMPARE_PWM1, PA8); // TIM1 CH1 (PA8) + PWM->setPrescaleFactor(1); + PWM->setOverflow(256, TICK_FORMAT); // 256 ticks overflow, no matter the CPU (timer) speed + PWM->resume(); + +#endif + + LL_TIM_InitTypeDef TIM_InitStruct; + memset(&TIM_InitStruct, 0, sizeof(LL_TIM_InitTypeDef)); + TIM_InitStruct.Prescaler = 4; + TIM_InitStruct.Autoreload = 255; + LL_TIM_Init(FURI_HAL_SPEAKER_TIMER, &TIM_InitStruct); + + LL_TIM_OC_InitTypeDef TIM_OC_InitStruct; + memset(&TIM_OC_InitStruct, 0, sizeof(LL_TIM_OC_InitTypeDef)); + TIM_OC_InitStruct.OCMode = LL_TIM_OCMODE_PWM1; + TIM_OC_InitStruct.OCState = LL_TIM_OCSTATE_ENABLE; + TIM_OC_InitStruct.CompareValue = 127; + LL_TIM_OC_Init(FURI_HAL_SPEAKER_TIMER, FURI_HAL_SPEAKER_CHANNEL, &TIM_OC_InitStruct); + + LL_TIM_EnableAllOutputs(FURI_HAL_SPEAKER_TIMER); + LL_TIM_EnableCounter(FURI_HAL_SPEAKER_TIMER); +} // begin + +inline void STM32SAM::SetAUDIO(unsigned char main_volume) { +#ifdef USE_ROGER_CORE + Timer1.setCompare(TIMER_CH1, main_volume); +#endif + +#ifdef USE_STM32duino_CORE + PWM->setCaptureCompare(1, main_volume, TICK_COMPARE_FORMAT); +#endif + + // if(main_volume > 64) { + // LL_TIM_OC_SetCompareCH1(FURI_HAL_SPEAKER_TIMER, 127); + // } else { + // LL_TIM_OC_SetCompareCH1(FURI_HAL_SPEAKER_TIMER, main_volume); + // } + + float data = main_volume; + data /= 255.0f; + data -= 0.5f; + data *= 4.0f; + data = tanhf(data); + + data += 0.5f; + data *= 255.0f; + + if(data < 0) { + data = 0; + } else if(data > 255) { + data = 255; + } + + LL_TIM_OC_SetCompareCH1(FURI_HAL_SPEAKER_TIMER, data); +} \ No newline at end of file diff --git a/applications/external/chess/sam/stm32_sam.h b/applications/external/chess/sam/stm32_sam.h new file mode 100644 index 000000000..910227ac3 --- /dev/null +++ b/applications/external/chess/sam/stm32_sam.h @@ -0,0 +1,96 @@ +#include + +#ifndef __STM32SAM__ +#define __STM32SAM__ + +// SAM Text-To-Speech (TTS), ported from https://github.com/s-macke/SAM + +class STM32SAM { +public: + STM32SAM(uint32_t STM32SAM_SPEED); + STM32SAM(); + + void begin(void); + + void + sam(const char* argv, + unsigned char phonetic, + unsigned char singmode, + unsigned char pitch, + unsigned char speed, + unsigned char mouth, + unsigned char throat); + void + sam(char* argv, + unsigned char phonetic, + unsigned char singmode, + unsigned char pitch, + unsigned char speed, + unsigned char mouth, + unsigned char throat); + + void say(const char* argv); + void say(char* argv); + void sing(const char* argv); + void sing(char* argv); + void sayPhonetic(const char* argv); + void sayPhonetic(char* argv); + void singPhonetic(const char* argv); + void singPhonetic(char* argv); + void setVoice( + unsigned char _pitch = 64, + unsigned char _speed = 72, + unsigned char _mouth = 128, + unsigned char _throat = 128); + void setPitch(unsigned char _pitch = 64); + void setSpeed(unsigned char _speed = 72); + void setMouth(unsigned char _mouth = 128); + void setThroat(unsigned char _throat = 128); + +private: + void SetAUDIO(unsigned char main_volume); + + void Output8BitAry(int index, unsigned char ary[5]); + void Output8Bit(int index, unsigned char A); + unsigned char Read(unsigned char p, unsigned char Y); + void Write(unsigned char p, unsigned char Y, unsigned char value); + void RenderSample(unsigned char* mem66); + void Render(); + void AddInflection(unsigned char mem48, unsigned char phase1); + void SetMouthThroat(); + unsigned char trans(unsigned char mem39212, unsigned char mem39213); + void SetInput(char* _input); + void Init(); + int SAMMain(); + void PrepareOutput(); + void Insert( + unsigned char position /*var57*/, + unsigned char mem60, + unsigned char mem59, + unsigned char mem58); + void InsertBreath(); + void CopyStress(); + int Parser1(); + void SetPhonemeLength(); + void Code41240(); + void Parser2(); + void AdjustLengths(); + void Code47503(unsigned char mem52); + void Code37055(unsigned char mem59); + void Code37066(unsigned char mem58); + unsigned char GetRuleByte(unsigned short mem62, unsigned char Y); + int TextToPhonemes(unsigned char* input); // Code36484 + + uint32_t _STM32SAM_SPEED; + + unsigned char speed; + unsigned char pitch; + unsigned char mouth; + unsigned char throat; + + unsigned char phonetic; + unsigned char singmode; + +}; // STM32SAM class + +#endif \ No newline at end of file diff --git a/applications/external/chess/scenes/flipchess_scene.c b/applications/external/chess/scenes/flipchess_scene.c new file mode 100644 index 000000000..767afbeb2 --- /dev/null +++ b/applications/external/chess/scenes/flipchess_scene.c @@ -0,0 +1,30 @@ +#include "flipchess_scene.h" + +// Generate scene on_enter handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, +void (*const flipchess_on_enter_handlers[])(void*) = { +#include "flipchess_scene_config.h" +}; +#undef ADD_SCENE + +// Generate scene on_event handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event, +bool (*const flipchess_on_event_handlers[])(void* context, SceneManagerEvent event) = { +#include "flipchess_scene_config.h" +}; +#undef ADD_SCENE + +// Generate scene on_exit handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit, +void (*const flipchess_on_exit_handlers[])(void* context) = { +#include "flipchess_scene_config.h" +}; +#undef ADD_SCENE + +// Initialize scene handlers configuration structure +const SceneManagerHandlers flipchess_scene_handlers = { + .on_enter_handlers = flipchess_on_enter_handlers, + .on_event_handlers = flipchess_on_event_handlers, + .on_exit_handlers = flipchess_on_exit_handlers, + .scene_num = FlipChessSceneNum, +}; diff --git a/applications/external/chess/scenes/flipchess_scene.h b/applications/external/chess/scenes/flipchess_scene.h new file mode 100644 index 000000000..c9912727d --- /dev/null +++ b/applications/external/chess/scenes/flipchess_scene.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +// Generate scene id and total number +#define ADD_SCENE(prefix, name, id) FlipChessScene##id, +typedef enum { +#include "flipchess_scene_config.h" + FlipChessSceneNum, +} FlipChessScene; +#undef ADD_SCENE + +extern const SceneManagerHandlers flipchess_scene_handlers; + +// Generate scene on_enter handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); +#include "flipchess_scene_config.h" +#undef ADD_SCENE + +// Generate scene on_event handlers declaration +#define ADD_SCENE(prefix, name, id) \ + bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event); +#include "flipchess_scene_config.h" +#undef ADD_SCENE + +// Generate scene on_exit handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context); +#include "flipchess_scene_config.h" +#undef ADD_SCENE diff --git a/applications/external/chess/scenes/flipchess_scene_config.h b/applications/external/chess/scenes/flipchess_scene_config.h new file mode 100644 index 000000000..a1cc0d51d --- /dev/null +++ b/applications/external/chess/scenes/flipchess_scene_config.h @@ -0,0 +1,4 @@ +ADD_SCENE(flipchess, startscreen, Startscreen) +ADD_SCENE(flipchess, menu, Menu) +ADD_SCENE(flipchess, scene_1, Scene_1) +ADD_SCENE(flipchess, settings, Settings) \ No newline at end of file diff --git a/applications/external/chess/scenes/flipchess_scene_menu.c b/applications/external/chess/scenes/flipchess_scene_menu.c new file mode 100644 index 000000000..448bfc62e --- /dev/null +++ b/applications/external/chess/scenes/flipchess_scene_menu.c @@ -0,0 +1,91 @@ +#include "../flipchess.h" + +enum SubmenuIndex { + SubmenuIndexScene1New = 10, + SubmenuIndexScene1Resume, + SubmenuIndexScene1Import, + SubmenuIndexSettings, +}; + +void flipchess_scene_menu_submenu_callback(void* context, uint32_t index) { + FlipChess* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, index); +} + +void flipchess_scene_menu_on_enter(void* context) { + FlipChess* app = context; + + submenu_add_item( + app->submenu, + "New Game", + SubmenuIndexScene1New, + flipchess_scene_menu_submenu_callback, + app); + + if(app->import_game == 1) { + submenu_add_item( + app->submenu, + "Resume Game", + SubmenuIndexScene1Resume, + flipchess_scene_menu_submenu_callback, + app); + } + + // submenu_add_item( + // app->submenu, + // "Import Game", + // SubmenuIndexScene1Import, + // flipchess_scene_menu_submenu_callback, + // app); + + submenu_add_item( + app->submenu, "Settings", SubmenuIndexSettings, flipchess_scene_menu_submenu_callback, app); + + submenu_set_selected_item( + app->submenu, scene_manager_get_scene_state(app->scene_manager, FlipChessSceneMenu)); + + view_dispatcher_switch_to_view(app->view_dispatcher, FlipChessViewIdMenu); +} + +bool flipchess_scene_menu_on_event(void* context, SceneManagerEvent event) { + FlipChess* app = context; + //UNUSED(app); + if(event.type == SceneManagerEventTypeBack) { + //exit app + scene_manager_stop(app->scene_manager); + view_dispatcher_stop(app->view_dispatcher); + return true; + } else if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubmenuIndexScene1New) { + app->import_game = 0; + scene_manager_set_scene_state( + app->scene_manager, FlipChessSceneMenu, SubmenuIndexScene1New); + scene_manager_next_scene(app->scene_manager, FlipChessSceneScene_1); + return true; + } + if(event.event == SubmenuIndexScene1Resume) { + app->import_game = 1; + scene_manager_set_scene_state( + app->scene_manager, FlipChessSceneMenu, SubmenuIndexScene1Resume); + scene_manager_next_scene(app->scene_manager, FlipChessSceneScene_1); + return true; + } else if(event.event == SubmenuIndexScene1Import) { + app->import_game = 1; + app->input_state = FlipChessTextInputGame; + text_input_set_header_text(app->text_input, "Enter board FEN"); + view_dispatcher_switch_to_view(app->view_dispatcher, FlipChessViewIdTextInput); + return true; + } else if(event.event == SubmenuIndexSettings) { + scene_manager_set_scene_state( + app->scene_manager, FlipChessSceneMenu, SubmenuIndexSettings); + scene_manager_next_scene(app->scene_manager, FlipChessSceneSettings); + return true; + } + } + return false; +} + +void flipchess_scene_menu_on_exit(void* context) { + FlipChess* app = context; + submenu_reset(app->submenu); +} \ No newline at end of file diff --git a/applications/external/chess/scenes/flipchess_scene_scene_1.c b/applications/external/chess/scenes/flipchess_scene_scene_1.c new file mode 100644 index 000000000..11e96b582 --- /dev/null +++ b/applications/external/chess/scenes/flipchess_scene_scene_1.c @@ -0,0 +1,55 @@ +#include "../flipchess.h" +#include "../helpers/flipchess_file.h" +#include "../helpers/flipchess_custom_event.h" +#include "../views/flipchess_scene_1.h" + +void flipchess_scene_1_callback(FlipChessCustomEvent event, void* context) { + furi_assert(context); + FlipChess* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, event); +} + +void flipchess_scene_scene_1_on_enter(void* context) { + furi_assert(context); + FlipChess* app = context; + + flipchess_scene_1_set_callback(app->flipchess_scene_1, flipchess_scene_1_callback, app); + view_dispatcher_switch_to_view(app->view_dispatcher, FlipChessViewIdScene1); +} + +bool flipchess_scene_scene_1_on_event(void* context, SceneManagerEvent event) { + FlipChess* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + switch(event.event) { + case FlipChessCustomEventScene1Left: + case FlipChessCustomEventScene1Right: + break; + case FlipChessCustomEventScene1Up: + case FlipChessCustomEventScene1Down: + break; + case FlipChessCustomEventScene1Back: + notification_message(app->notification, &sequence_reset_red); + notification_message(app->notification, &sequence_reset_green); + notification_message(app->notification, &sequence_reset_blue); + if(!scene_manager_search_and_switch_to_previous_scene( + app->scene_manager, FlipChessSceneMenu)) { + scene_manager_stop(app->scene_manager); + view_dispatcher_stop(app->view_dispatcher); + } + consumed = true; + break; + } + } + + return consumed; +} + +void flipchess_scene_scene_1_on_exit(void* context) { + FlipChess* app = context; + + if(app->import_game == 1 && strlen(app->import_game_text) > 0) { + flipchess_save_file(app->import_game_text, FlipChessFileBoard, NULL, false, true); + } +} \ No newline at end of file diff --git a/applications/external/chess/scenes/flipchess_scene_settings.c b/applications/external/chess/scenes/flipchess_scene_settings.c new file mode 100644 index 000000000..51833b99c --- /dev/null +++ b/applications/external/chess/scenes/flipchess_scene_settings.c @@ -0,0 +1,102 @@ +#include "../flipchess.h" +#include "../helpers/flipchess_voice.h" +#include + +#define TEXT_LABEL_ON "ON" +#define TEXT_LABEL_OFF "OFF" + +const char* const haptic_text[2] = { + TEXT_LABEL_OFF, + TEXT_LABEL_ON, +}; +const uint32_t haptic_value[2] = { + FlipChessHapticOff, + FlipChessHapticOn, +}; + +const char* const player_mode_text[4] = { + "Human", + "CPU 1", + "CPU 2", + "CPU 3", +}; +const uint32_t player_mode_value[4] = { + FlipChessPlayerHuman, + FlipChessPlayerAI1, + FlipChessPlayerAI2, + FlipChessPlayerAI3, +}; + +static void flipchess_scene_settings_set_haptic(VariableItem* item) { + FlipChess* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + variable_item_set_current_value_text(item, haptic_text[index]); + app->haptic = haptic_value[index]; +} + +static void flipchess_scene_settings_set_white_mode(VariableItem* item) { + FlipChess* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + variable_item_set_current_value_text(item, player_mode_text[index]); + app->white_mode = player_mode_value[index]; +} + +static void flipchess_scene_settings_set_black_mode(VariableItem* item) { + FlipChess* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + variable_item_set_current_value_text(item, player_mode_text[index]); + app->black_mode = player_mode_value[index]; +} + +void flipchess_scene_settings_submenu_callback(void* context, uint32_t index) { + FlipChess* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, index); +} + +void flipchess_scene_settings_on_enter(void* context) { + FlipChess* app = context; + VariableItem* item; + uint8_t value_index; + + if(app->sound == 1) { + flipchess_voice_which_side(); + } + + // White mode + item = variable_item_list_add( + app->variable_item_list, "White:", 4, flipchess_scene_settings_set_white_mode, app); + value_index = value_index_uint32(app->white_mode, player_mode_value, 4); + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, player_mode_text[value_index]); + + // Black mode + item = variable_item_list_add( + app->variable_item_list, "Black:", 4, flipchess_scene_settings_set_black_mode, app); + value_index = value_index_uint32(app->black_mode, player_mode_value, 4); + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, player_mode_text[value_index]); + + // Vibro on/off + item = variable_item_list_add( + app->variable_item_list, "Vibro/Haptic:", 2, flipchess_scene_settings_set_haptic, app); + value_index = value_index_uint32(app->haptic, haptic_value, 2); + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, haptic_text[value_index]); + + view_dispatcher_switch_to_view(app->view_dispatcher, FlipChessViewIdSettings); +} + +bool flipchess_scene_settings_on_event(void* context, SceneManagerEvent event) { + FlipChess* app = context; + UNUSED(app); + bool consumed = false; + if(event.type == SceneManagerEventTypeCustom) { + } + return consumed; +} + +void flipchess_scene_settings_on_exit(void* context) { + FlipChess* app = context; + variable_item_list_set_selected_item(app->variable_item_list, 0); + variable_item_list_reset(app->variable_item_list); +} \ No newline at end of file diff --git a/applications/external/chess/scenes/flipchess_scene_startscreen.c b/applications/external/chess/scenes/flipchess_scene_startscreen.c new file mode 100644 index 000000000..f9dfde720 --- /dev/null +++ b/applications/external/chess/scenes/flipchess_scene_startscreen.c @@ -0,0 +1,67 @@ +#include "../flipchess.h" +#include "../helpers/flipchess_voice.h" +#include "../helpers/flipchess_file.h" +#include "../helpers/flipchess_custom_event.h" +#include "../views/flipchess_startscreen.h" + +void flipchess_scene_startscreen_callback(FlipChessCustomEvent event, void* context) { + furi_assert(context); + FlipChess* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, event); +} + +void flipchess_scene_startscreen_on_enter(void* context) { + furi_assert(context); + FlipChess* app = context; + + if(flipchess_has_file(FlipChessFileBoard, NULL, false)) { + if(flipchess_load_file(app->import_game_text, FlipChessFileBoard, NULL)) { + app->import_game = 1; + } + } + + flipchess_startscreen_set_callback( + app->flipchess_startscreen, flipchess_scene_startscreen_callback, app); + view_dispatcher_switch_to_view(app->view_dispatcher, FlipChessViewIdStartscreen); +} + +bool flipchess_scene_startscreen_on_event(void* context, SceneManagerEvent event) { + FlipChess* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + switch(event.event) { + case FlipChessCustomEventStartscreenLeft: + case FlipChessCustomEventStartscreenRight: + break; + case FlipChessCustomEventStartscreenUp: + case FlipChessCustomEventStartscreenDown: + break; + case FlipChessCustomEventStartscreenOk: + scene_manager_next_scene(app->scene_manager, FlipChessSceneMenu); + consumed = true; + break; + case FlipChessCustomEventStartscreenBack: + notification_message(app->notification, &sequence_reset_red); + notification_message(app->notification, &sequence_reset_green); + notification_message(app->notification, &sequence_reset_blue); + if(!scene_manager_search_and_switch_to_previous_scene( + app->scene_manager, FlipChessSceneStartscreen)) { + scene_manager_stop(app->scene_manager); + view_dispatcher_stop(app->view_dispatcher); + } + consumed = true; + break; + } + } + + return consumed; +} + +void flipchess_scene_startscreen_on_exit(void* context) { + FlipChess* app = context; + + if(app->sound == 1) { + flipchess_voice_shall_we_play(); + } +} \ No newline at end of file diff --git a/applications/external/chess/views/flipchess_scene_1.c b/applications/external/chess/views/flipchess_scene_1.c new file mode 100644 index 000000000..82a0864da --- /dev/null +++ b/applications/external/chess/views/flipchess_scene_1.c @@ -0,0 +1,727 @@ +#include "../flipchess.h" +#include +// #include +// #include +#include +#include +//#include +#include +//#include "flipchess_icons.h" +#include "../helpers/flipchess_voice.h" +#include "../helpers/flipchess_haptic.h" + +#define SCL_960_CASTLING 0 // setting to 1 compiles a 960 version of smolchess +#define XBOARD_DEBUG 0 // will create files with xboard communication +#define SCL_EVALUATION_FUNCTION SCL_boardEvaluateStatic +#define SCL_DEBUG_AI 0 + +#include "../chess/smallchesslib.h" + +#define ENABLE_960 0 // setting to 1 enables 960 chess +#define MAX_TEXT_LEN 15 // 15 = max length of text +#define MAX_TEXT_BUF (MAX_TEXT_LEN + 1) // max length of text + null terminator +#define THREAD_WAIT_TIME 20 // time to wait for draw thread to finish + +struct FlipChessScene1 { + View* view; + FlipChessScene1Callback callback; + void* context; +}; +typedef struct { + uint8_t paramPlayerW; + uint8_t paramPlayerB; + + uint8_t paramAnalyze; // depth of analysis + uint8_t paramMoves; + uint8_t paramInfo; + uint8_t paramFlipBoard; + uint8_t paramExit; + uint16_t paramStep; + char* paramFEN; + char* paramPGN; + + int clockSeconds; + SCL_Game game; + SCL_Board startState; + +#if ENABLE_960 + int16_t random960PosNumber; +#endif + + //uint8_t picture[SCL_BOARD_PICTURE_WIDTH * SCL_BOARD_PICTURE_WIDTH]; + uint8_t squareSelected; + uint8_t squareSelectedLast; + + char* msg; + char* msg2; + char* msg3; + char moveString[MAX_TEXT_BUF]; + char moveString2[MAX_TEXT_BUF]; + char moveString3[MAX_TEXT_BUF]; + uint8_t thinking; + + SCL_SquareSet moveHighlight; + uint8_t squareFrom; + uint8_t squareTo; + uint8_t turnState; + +} FlipChessScene1Model; + +static uint8_t picture[SCL_BOARD_PICTURE_WIDTH * SCL_BOARD_PICTURE_WIDTH]; + +void flipchess_putImagePixel(uint8_t pixel, uint16_t index) { + picture[index] = pixel; +} + +uint8_t flipchess_stringsEqual(const char* s1, const char* s2, int max) { + for(int i = 0; i < max; ++i) { + if(*s1 != *s2) return 0; + + if(*s1 == 0) return 1; + + s1++; + s2++; + } + + return 1; +} + +int16_t flipchess_makeAIMove( + SCL_Board board, + uint8_t* s0, + uint8_t* s1, + char* prom, + FlipChessScene1Model* model) { + uint8_t level = SCL_boardWhitesTurn(board) ? model->paramPlayerW : model->paramPlayerB; + uint8_t depth = (level > 0) ? level : 1; + uint8_t extraDepth = 3; + uint8_t endgameDepth = 1; + uint8_t randomness = + model->game.ply < 2 ? 1 : 0; /* in first moves increase randomness for different + openings */ + uint8_t rs0, rs1; + + SCL_gameGetRepetiotionMove(&(model->game), &rs0, &rs1); + + if(model->clockSeconds >= 0) // when using clock, choose AI params accordingly + { + if(model->clockSeconds <= 5) { + depth = 1; + extraDepth = 2; + endgameDepth = 0; + } else if(model->clockSeconds < 15) { + depth = 2; + extraDepth = 2; + } else if(model->clockSeconds < 100) { + depth = 2; + } else if(model->clockSeconds < 5 * 60) { + depth = 3; + } else { + depth = 3; + extraDepth = 4; + } + } + + return SCL_getAIMove( + board, + depth, + extraDepth, + endgameDepth, + SCL_boardEvaluateStatic, + SCL_randomBetter, + randomness, + rs0, + rs1, + s0, + s1, + prom); +} + +bool flipchess_isPlayerTurn(FlipChessScene1Model* model) { + return (SCL_boardWhitesTurn(model->game.board) && model->paramPlayerW == 0) || + (!SCL_boardWhitesTurn(model->game.board) && model->paramPlayerB == 0); +} + +void flipchess_shiftMessages(FlipChessScene1Model* model) { + // shift messages + model->msg3 = model->msg2; + model->msg2 = model->msg; + strncpy(model->moveString3, model->moveString2, MAX_TEXT_LEN); + strncpy(model->moveString2, model->moveString, MAX_TEXT_LEN); +} + +void flipchess_drawBoard(FlipChessScene1Model* model) { + // draw chess board + SCL_drawBoard( + model->game.board, + flipchess_putImagePixel, + model->squareSelected, + model->moveHighlight, + model->paramFlipBoard); +} + +uint8_t flipchess_saveState(FlipChess* app, FlipChessScene1Model* model) { + for(uint8_t i = 0; i < SCL_FEN_MAX_LENGTH; i++) { + app->import_game_text[i] = '\0'; + } + const uint8_t res = SCL_boardToFEN(model->game.board, app->import_game_text); + if(res > 0) { + app->import_game = 1; + } + return res; +} + +uint8_t flipchess_turn(FlipChessScene1Model* model) { + // 0: none, 1: player, 2: AI, 3: undo + uint8_t moveType = FlipChessStatusNone; + + // if(model->paramInfo) { + + // if(model->random960PosNumber >= 0) + // printf("960 random position number: %d\n", model->random960PosNumber); + + // printf("ply number: %d\n", model->game.ply); + + // int16_t eval = SCL_boardEvaluateStatic(model->game.board); + // printf( + // "board static evaluation: %lf (%d)\n", + // ((double)eval) / ((double)SCL_VALUE_PAWN), + // eval); + // printf("board hash: %u\n", SCL_boardHash32(model->game.board)); + // printf("phase: "); + + // switch(SCL_boardEstimatePhase(model->game.board)) { + // case SCL_PHASE_OPENING: + // puts("opening"); + // break; + // case SCL_PHASE_ENDGAME: + // puts("endgame"); + // break; + // default: + // puts("midgame"); + // break; + // } + + // printf( + // "en passant: %d\n", + // ((model->game.board[SCL_BOARD_ENPASSANT_CASTLE_BYTE] & 0x0f) + 1) % 16); + // printf( + // "50 move rule count: %d\n", model->game.board[SCL_BOARD_MOVE_COUNT_BYTE]); + + // if(model->paramFEN == NULL && model->paramPGN == NULL) { + // printf("PGN: "); + // SCL_printPGN(model->game.record, putCharacter, startState); + // putchar('\n'); + // } + // } + + if(model->game.state != SCL_GAME_STATE_PLAYING) { + model->paramExit = FlipChessStatusNone; + + } else { + char movePromote = 'q'; + + if(flipchess_isPlayerTurn(model)) { + // if(stringsEqual(string, "undo", 5)) + // moveType = FlipChessStatusMoveUndo; + // else if(stringsEqual(string, "quit", 5)) + // break; + + if(model->turnState == 0 && model->squareSelected != 255) { + model->squareFrom = model->squareSelected; + model->turnState = 1; + } else if(model->turnState == 1 && model->squareSelected != 255) { + model->squareTo = model->squareSelected; + model->turnState = 2; + model->squareSelectedLast = model->squareSelected; + //model->squareSelected = 255; + } + + if(model->turnState == 1 && model->squareFrom != 255) { + if((model->game.board[model->squareFrom] != '.') && + (SCL_pieceIsWhite(model->game.board[model->squareFrom]) == + SCL_boardWhitesTurn(model->game.board))) { + SCL_boardGetMoves(model->game.board, model->squareFrom, model->moveHighlight); + } + } else if(model->turnState == 2) { + if(SCL_squareSetContains(model->moveHighlight, model->squareTo)) { + moveType = FlipChessStatusMovePlayer; + } + model->turnState = 0; + SCL_squareSetClear(model->moveHighlight); + } + + } else { + model->squareSelected = 255; + flipchess_makeAIMove( + model->game.board, &(model->squareFrom), &(model->squareTo), &movePromote, model); + moveType = FlipChessStatusMoveAI; + model->turnState = 0; + } + + if(moveType == FlipChessStatusMovePlayer || moveType == FlipChessStatusMoveAI) { + flipchess_shiftMessages(model); + + SCL_moveToString( + model->game.board, + model->squareFrom, + model->squareTo, + movePromote, + model->moveString); + + SCL_gameMakeMove(&(model->game), model->squareFrom, model->squareTo, movePromote); + + SCL_squareSetClear(model->moveHighlight); + SCL_squareSetAdd(model->moveHighlight, model->squareFrom); + SCL_squareSetAdd(model->moveHighlight, model->squareTo); + } else if(moveType == FlipChessStatusMoveUndo) { + flipchess_shiftMessages(model); + + if(model->paramPlayerW != 0 || model->paramPlayerB != 0) + SCL_gameUndoMove(&(model->game)); + + SCL_gameUndoMove(&(model->game)); + SCL_squareSetClear(model->moveHighlight); + } + + switch(model->game.state) { + case SCL_GAME_STATE_WHITE_WIN: + model->msg = "white wins"; + model->paramExit = FlipChessStatusReturn; + break; + + case SCL_GAME_STATE_BLACK_WIN: + model->msg = "black wins"; + model->paramExit = FlipChessStatusReturn; + break; + + case SCL_GAME_STATE_DRAW_STALEMATE: + model->msg = "stalemate"; + model->paramExit = FlipChessStatusReturn; + break; + + case SCL_GAME_STATE_DRAW_REPETITION: + model->msg = "draw-repetition"; + model->paramExit = FlipChessStatusReturn; + break; + + case SCL_GAME_STATE_DRAW_DEAD: + model->msg = "draw-dead pos."; + model->paramExit = FlipChessStatusReturn; + break; + + case SCL_GAME_STATE_DRAW: + model->msg = "draw"; + model->paramExit = FlipChessStatusReturn; + break; + + case SCL_GAME_STATE_DRAW_50: + model->msg = "draw-50 moves"; + model->paramExit = FlipChessStatusReturn; + break; + + default: + if(model->game.ply > 0) { + const uint8_t whitesTurn = SCL_boardWhitesTurn(model->game.board); + + if(SCL_boardCheck(model->game.board, whitesTurn)) { + model->msg = (whitesTurn ? "black: check!" : "white: check!"); + } else { + model->msg = (whitesTurn ? "black played" : "white played"); + } + + uint8_t s0, s1; + char p; + + SCL_recordGetMove(model->game.record, model->game.ply - 1, &s0, &s1, &p); + SCL_moveToString(model->game.board, s0, s1, p, model->moveString); + } + break; + model->paramExit = moveType; + } + } + + model->thinking = 0; + return model->paramExit; +} + +void flipchess_scene_1_set_callback( + FlipChessScene1* instance, + FlipChessScene1Callback callback, + void* context) { + furi_assert(instance); + furi_assert(callback); + instance->callback = callback; + instance->context = context; +} + +void flipchess_scene_1_draw(Canvas* canvas, FlipChessScene1Model* model) { + //UNUSED(model); + canvas_clear(canvas); + canvas_set_color(canvas, ColorBlack); + + //canvas_draw_icon(canvas, 0, 0, &I_FLIPR_128x64); + + // Frame + canvas_draw_frame(canvas, 0, 0, 66, 64); + + // Message + canvas_set_font(canvas, FontSecondary); + if(model->thinking) { + canvas_draw_str(canvas, 68, 10, "thinking..."); + } else { + canvas_draw_str(canvas, 68, 10, model->msg); + } + canvas_draw_str(canvas, 68, 19, model->moveString); + canvas_draw_str(canvas, 68, 31, model->msg2); + canvas_draw_str(canvas, 68, 40, model->moveString2); + canvas_draw_str(canvas, 68, 52, model->msg3); + canvas_draw_str(canvas, 68, 61, model->moveString3); + + // Board + for(uint16_t y = 0; y < SCL_BOARD_PICTURE_WIDTH; y++) { + for(uint16_t x = 0; x < SCL_BOARD_PICTURE_WIDTH; x++) { + if(!picture[x + (y * SCL_BOARD_PICTURE_WIDTH)]) { + canvas_draw_dot(canvas, x + 1, y); + } + } + } +} + +static int flipchess_scene_1_model_init( + FlipChessScene1Model* const model, + const int white_mode, + const int black_mode, + char* import_game_text) { + model->paramPlayerW = white_mode; + model->paramPlayerB = black_mode; + + model->paramAnalyze = 255; // depth of analysis + model->paramMoves = 0; + model->paramInfo = 1; + model->paramFlipBoard = 0; + model->paramExit = FlipChessStatusNone; + model->paramStep = 0; + model->paramFEN = import_game_text; + model->paramPGN = NULL; + model->clockSeconds = -1; + + SCL_Board emptyStartState = SCL_BOARD_START_STATE; + memcpy(model->startState, &emptyStartState, sizeof(SCL_Board)); + +#if ENABLE_960 + model->random960PosNumber = -1; +#endif + + model->squareSelected = 255; + model->squareSelectedLast = 28; // start selector near middle + + model->msg = "init"; + model->moveString[0] = '\0'; + model->msg2 = ""; + model->moveString2[0] = '\0'; + model->msg3 = ""; + model->moveString3[0] = '\0'; + model->thinking = 0; + + SCL_SquareSet emptySquareSet = SCL_SQUARE_SET_EMPTY; + memcpy(model->moveHighlight, &emptySquareSet, sizeof(SCL_SquareSet)); + model->squareFrom = 255; + model->squareTo = 255; + model->turnState = 0; + + SCL_randomBetterSeed(furi_hal_random_get()); + +#if ENABLE_960 +#if SCL_960_CASTLING + if(model->random960PosNumber < 0) model->random960PosNumber = SCL_randomBetter(); +#endif + if(model->random960PosNumber >= 0) model->random960PosNumber %= 960; +#endif + + if(model->paramFEN != NULL) + SCL_boardFromFEN(model->startState, model->paramFEN); + else if(model->paramPGN != NULL) { + SCL_Record record; + SCL_recordFromPGN(record, model->paramPGN); + SCL_boardInit(model->startState); + SCL_recordApply(record, model->startState, model->paramStep); + } + +#if ENABLE_960 +#if SCL_960_CASTLING + else + SCL_boardInit960(model->startState, model->random960PosNumber); +#endif +#endif + + SCL_gameInit(&(model->game), model->startState); + + if(model->paramAnalyze != 255) { + char p; + uint8_t move[] = {0, 0}; + + model->paramPlayerW = model->paramAnalyze; + model->paramPlayerB = model->paramAnalyze; + + int16_t evaluation = + flipchess_makeAIMove(model->game.board, &(move[0]), &(move[1]), &p, model); + + if(model->paramAnalyze == 0) evaluation = SCL_boardEvaluateStatic(model->game.board); + + char moveStr[5]; + moveStr[4] = 0; + + SCL_squareToString(move[0], moveStr); + SCL_squareToString(move[1], moveStr + 2); + + //printf("%lf (%d)\n", ((double)evaluation) / ((double)SCL_VALUE_PAWN), evaluation); + //puts(moveStr); + + return evaluation; + } + + if(model->paramMoves) { + char string[256]; + + for(int i = 0; i < 64; ++i) + if(model->game.board[i] != '.' && + SCL_pieceIsWhite(model->game.board[i]) == SCL_boardWhitesTurn(model->game.board)) { + SCL_SquareSet possibleMoves = SCL_SQUARE_SET_EMPTY; + + SCL_boardGetMoves(model->game.board, i, possibleMoves); + + SCL_SQUARE_SET_ITERATE_BEGIN(possibleMoves) + SCL_moveToString(model->game.board, i, iteratedSquare, 'q', string); + //printf("%s ", string); + SCL_SQUARE_SET_ITERATE_END + } + + return FlipChessStatusReturn; + } + + model->msg = (SCL_boardWhitesTurn(model->game.board) ? "white to move" : "black to move"); + + // 0 = success + return FlipChessStatusNone; +} + +bool flipchess_scene_1_input(InputEvent* event, void* context) { + furi_assert(context); + FlipChessScene1* instance = context; + FlipChess* app = instance->context; + + if(event->type == InputTypeRelease) { + switch(event->key) { + case InputKeyBack: + with_view_model( + instance->view, + FlipChessScene1Model * model, + { + if(model->turnState == 1) { + model->turnState = 0; + SCL_squareSetClear(model->moveHighlight); + flipchess_drawBoard(model); + } else { + instance->callback(FlipChessCustomEventScene1Back, instance->context); + } + }, + true); + break; + case InputKeyRight: + with_view_model( + instance->view, + FlipChessScene1Model * model, + { + if(model->squareSelectedLast != 255 && model->squareSelected == 255) { + model->squareSelected = model->squareSelectedLast; + } else { + model->squareSelected = (model->squareSelected + 1) % 64; + } + flipchess_drawBoard(model); + }, + true); + break; + case InputKeyDown: + with_view_model( + instance->view, + FlipChessScene1Model * model, + { + if(model->squareSelectedLast != 255 && model->squareSelected == 255) { + model->squareSelected = model->squareSelectedLast; + } else { + model->squareSelected = (model->squareSelected + 56) % 64; + } + flipchess_drawBoard(model); + }, + true); + break; + case InputKeyLeft: + with_view_model( + instance->view, + FlipChessScene1Model * model, + { + if(model->squareSelectedLast != 255 && model->squareSelected == 255) { + model->squareSelected = model->squareSelectedLast; + } else { + model->squareSelected = (model->squareSelected + 63) % 64; + } + flipchess_drawBoard(model); + }, + true); + break; + case InputKeyUp: + with_view_model( + instance->view, + FlipChessScene1Model * model, + { + if(model->squareSelectedLast != 255 && model->squareSelected == 255) { + model->squareSelected = model->squareSelectedLast; + } else { + model->squareSelected = (model->squareSelected + 8) % 64; + } + flipchess_drawBoard(model); + }, + true); + break; + case InputKeyOk: + with_view_model( + instance->view, + FlipChessScene1Model * model, + { + // if(model->paramExit == FlipChessStatusReturn) { + // instance->callback(FlipChessCustomEventScene1Back, instance->context); + // break; + // } + if(!flipchess_isPlayerTurn(model)) { + model->thinking = 1; + } + }, + true); + furi_thread_flags_wait(0, FuriFlagWaitAny, THREAD_WAIT_TIME); + + with_view_model( + instance->view, + FlipChessScene1Model * model, + { + // first turn of round, probably player but could be AI + if(flipchess_turn(model) == FlipChessStatusReturn) { + if(app->sound == 1) flipchess_voice_a_strange_game(); + flipchess_play_long_bump(app); + } + flipchess_saveState(app, model); + flipchess_drawBoard(model); + }, + true); + + with_view_model( + instance->view, + FlipChessScene1Model * model, + { + if(!flipchess_isPlayerTurn(model)) { + model->thinking = 1; + } + }, + true); + furi_thread_flags_wait(0, FuriFlagWaitAny, THREAD_WAIT_TIME); + + with_view_model( + instance->view, + FlipChessScene1Model * model, + { + // if player played, let AI play + if(!flipchess_isPlayerTurn(model)) { + if(flipchess_turn(model) == FlipChessStatusReturn) { + if(app->sound == 1) flipchess_voice_a_strange_game(); + flipchess_play_long_bump(app); + } + flipchess_saveState(app, model); + flipchess_drawBoard(model); + } + }, + true); + break; + case InputKeyMAX: + break; + } + } + return true; +} + +void flipchess_scene_1_exit(void* context) { + furi_assert(context); + FlipChessScene1* instance = (FlipChessScene1*)context; + + with_view_model( + instance->view, FlipChessScene1Model * model, { model->paramExit = 0; }, true); +} + +void flipchess_scene_1_enter(void* context) { + furi_assert(context); + FlipChessScene1* instance = (FlipChessScene1*)context; + FlipChess* app = instance->context; + + flipchess_play_happy_bump(app); + + with_view_model( + instance->view, + FlipChessScene1Model * model, + { + // load imported game if applicable + char* import_game_text = NULL; + if(app->import_game == 1 && strlen(app->import_game_text) > 0) { + import_game_text = app->import_game_text; + } else { + if(app->sound == 1) flipchess_voice_how_about_chess(); + } + + int init = flipchess_scene_1_model_init( + model, app->white_mode, app->black_mode, import_game_text); + + if(init == FlipChessStatusNone) { + // perform initial turn, sets up and lets white + // AI play if applicable + const uint8_t turn = flipchess_turn(model); + if(turn == FlipChessStatusReturn) { + init = turn; + } else { + flipchess_saveState(app, model); + flipchess_drawBoard(model); + } + } + + // if return status, return from scene immediately + // if(init == FlipChessStatusReturn) { + // instance->callback(FlipChessCustomEventScene1Back, instance->context); + // } + }, + true); +} + +FlipChessScene1* flipchess_scene_1_alloc() { + FlipChessScene1* instance = malloc(sizeof(FlipChessScene1)); + instance->view = view_alloc(); + view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(FlipChessScene1Model)); + view_set_context(instance->view, instance); // furi_assert crashes in events without this + view_set_draw_callback(instance->view, (ViewDrawCallback)flipchess_scene_1_draw); + view_set_input_callback(instance->view, flipchess_scene_1_input); + view_set_enter_callback(instance->view, flipchess_scene_1_enter); + view_set_exit_callback(instance->view, flipchess_scene_1_exit); + + return instance; +} + +void flipchess_scene_1_free(FlipChessScene1* instance) { + furi_assert(instance); + + with_view_model( + instance->view, FlipChessScene1Model * model, { UNUSED(model); }, true); + + view_free(instance->view); + free(instance); +} + +View* flipchess_scene_1_get_view(FlipChessScene1* instance) { + furi_assert(instance); + return instance->view; +} \ No newline at end of file diff --git a/applications/external/chess/views/flipchess_scene_1.h b/applications/external/chess/views/flipchess_scene_1.h new file mode 100644 index 000000000..a57a90781 --- /dev/null +++ b/applications/external/chess/views/flipchess_scene_1.h @@ -0,0 +1,19 @@ +#pragma once + +#include +#include "../helpers/flipchess_custom_event.h" + +typedef struct FlipChessScene1 FlipChessScene1; + +typedef void (*FlipChessScene1Callback)(FlipChessCustomEvent event, void* context); + +void flipchess_scene_1_set_callback( + FlipChessScene1* flipchess_scene_1, + FlipChessScene1Callback callback, + void* context); + +View* flipchess_scene_1_get_view(FlipChessScene1* flipchess_static); + +FlipChessScene1* flipchess_scene_1_alloc(); + +void flipchess_scene_1_free(FlipChessScene1* flipchess_static); \ No newline at end of file diff --git a/applications/external/chess/views/flipchess_startscreen.c b/applications/external/chess/views/flipchess_startscreen.c new file mode 100644 index 000000000..2f4ff801d --- /dev/null +++ b/applications/external/chess/views/flipchess_startscreen.c @@ -0,0 +1,155 @@ +#include "../flipchess.h" +#include +#include +#include +#include +#include "flipchess_icons.h" + +struct FlipChessStartscreen { + View* view; + FlipChessStartscreenCallback callback; + void* context; +}; + +typedef struct { + int some_value; +} FlipChessStartscreenModel; + +void flipchess_startscreen_set_callback( + FlipChessStartscreen* instance, + FlipChessStartscreenCallback callback, + void* context) { + furi_assert(instance); + furi_assert(callback); + instance->callback = callback; + instance->context = context; +} + +void flipchess_startscreen_draw(Canvas* canvas, FlipChessStartscreenModel* model) { + UNUSED(model); + canvas_clear(canvas); + canvas_set_color(canvas, ColorBlack); + + canvas_draw_icon(canvas, 0, 0, &I_FLIPR_128x64); + + canvas_set_font(canvas, FontPrimary); + canvas_draw_str(canvas, 4, 11, "Chess"); + canvas_set_font(canvas, FontSecondary); + canvas_draw_str(canvas, 62, 11, FLIPCHESS_VERSION); + + //canvas_set_font(canvas, FontSecondary); + //canvas_draw_str(canvas, 10, 11, "How about a nice game of..."); + //canvas_draw_str(canvas, 99, 40, FLIPCHESS_VERSION); + + //canvas_set_font(canvas, FontPrimary); + //canvas_draw_str(canvas, 10, 23, "Chess"); + //canvas_draw_icon(canvas, 0, 40, &I_Background_128x11); + //canvas_draw_str(canvas, 10, 61, "FLIPR"); + + elements_button_left(canvas, "Sound"); + elements_button_right(canvas, "Silent"); +} + +static void flipchess_startscreen_model_init(FlipChessStartscreenModel* const model) { + model->some_value = 1; +} + +bool flipchess_startscreen_input(InputEvent* event, void* context) { + furi_assert(context); + FlipChessStartscreen* instance = context; + FlipChess* app = instance->context; + + if(event->type == InputTypeRelease) { + switch(event->key) { + case InputKeyBack: + with_view_model( + instance->view, + FlipChessStartscreenModel * model, + { + UNUSED(model); + instance->callback(FlipChessCustomEventStartscreenBack, instance->context); + }, + true); + break; + case InputKeyLeft: + // sound on, haptic off + app->sound = 1; + app->haptic = FlipChessHapticOff; + with_view_model( + instance->view, + FlipChessStartscreenModel * model, + { + UNUSED(model); + instance->callback(FlipChessCustomEventStartscreenOk, instance->context); + }, + true); + break; + case InputKeyRight: + // sound off, haptic on + app->sound = 0; + app->haptic = FlipChessHapticOn; + with_view_model( + instance->view, + FlipChessStartscreenModel * model, + { + UNUSED(model); + instance->callback(FlipChessCustomEventStartscreenOk, instance->context); + }, + true); + break; + case InputKeyUp: + case InputKeyDown: + case InputKeyOk: + case InputKeyMAX: + break; + } + } + return true; +} + +void flipchess_startscreen_exit(void* context) { + furi_assert(context); +} + +void flipchess_startscreen_enter(void* context) { + furi_assert(context); + FlipChessStartscreen* instance = (FlipChessStartscreen*)context; + with_view_model( + instance->view, + FlipChessStartscreenModel * model, + { flipchess_startscreen_model_init(model); }, + true); +} + +FlipChessStartscreen* flipchess_startscreen_alloc() { + FlipChessStartscreen* instance = malloc(sizeof(FlipChessStartscreen)); + instance->view = view_alloc(); + view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(FlipChessStartscreenModel)); + view_set_context(instance->view, instance); // furi_assert crashes in events without this + view_set_draw_callback(instance->view, (ViewDrawCallback)flipchess_startscreen_draw); + view_set_input_callback(instance->view, flipchess_startscreen_input); + //view_set_enter_callback(instance->view, flipchess_startscreen_enter); + //view_set_exit_callback(instance->view, flipchess_startscreen_exit); + + with_view_model( + instance->view, + FlipChessStartscreenModel * model, + { flipchess_startscreen_model_init(model); }, + true); + + return instance; +} + +void flipchess_startscreen_free(FlipChessStartscreen* instance) { + furi_assert(instance); + + with_view_model( + instance->view, FlipChessStartscreenModel * model, { UNUSED(model); }, true); + view_free(instance->view); + free(instance); +} + +View* flipchess_startscreen_get_view(FlipChessStartscreen* instance) { + furi_assert(instance); + return instance->view; +} diff --git a/applications/external/chess/views/flipchess_startscreen.h b/applications/external/chess/views/flipchess_startscreen.h new file mode 100644 index 000000000..7c6203569 --- /dev/null +++ b/applications/external/chess/views/flipchess_startscreen.h @@ -0,0 +1,19 @@ +#pragma once + +#include +#include "../helpers/flipchess_custom_event.h" + +typedef struct FlipChessStartscreen FlipChessStartscreen; + +typedef void (*FlipChessStartscreenCallback)(FlipChessCustomEvent event, void* context); + +void flipchess_startscreen_set_callback( + FlipChessStartscreen* flipchess_startscreen, + FlipChessStartscreenCallback callback, + void* context); + +View* flipchess_startscreen_get_view(FlipChessStartscreen* flipchess_static); + +FlipChessStartscreen* flipchess_startscreen_alloc(); + +void flipchess_startscreen_free(FlipChessStartscreen* flipchess_static); \ No newline at end of file