MX 2 лет назад
Сommit
fd8c164295

+ 30 - 0
.github/workflows/build.yml

@@ -0,0 +1,30 @@
+name: Build
+
+on:
+  push:
+    branches:
+      - main
+      - develop
+
+env:
+  firmware_version: '0.86.1'
+
+jobs:
+  build:
+    name: Build
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout Flipper Zero Firmware
+        uses: actions/checkout@v3
+        with:
+          repository: 'flipperdevices/flipperzero-firmware'
+          ref: ${{ env.firmware_version }}
+          submodules: true
+      - name: Checkout flip-chess
+        uses: actions/checkout@v3
+        with:
+          path: 'applications_user/flipper-chess'
+      - name: Build FAPs
+        run: ./fbt COMPACT=1 DEBUG=0 faps
+      - name: Check flip-chess Built
+        run: test -f build/f7-firmware-C/.extapps/flipchess.fap

+ 44 - 0
.github/workflows/release.yml

@@ -0,0 +1,44 @@
+name: Release
+
+on:
+  push:
+    tags:
+      - 'v[0-9]+.[0-9]+.[0-9]+'
+
+env:
+  firmware_version: '0.86.1'
+
+jobs:
+  build:
+    name: Build
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout Flipper Zero Firmware
+        uses: actions/checkout@v3
+        with:
+          repository: 'flipperdevices/flipperzero-firmware'
+          ref: ${{ env.firmware_version }}
+          submodules: true
+      - name: Checkout flipper-chess
+        uses: actions/checkout@v3
+        with:
+          path: 'applications_user/flipper-chess'
+      - name: Build FAPs
+        run: ./fbt COMPACT=1 DEBUG=0 faps
+      - name: Check flipper-chess Built
+        run: test -f build/f7-firmware-C/.extapps/flipchess.fap
+      - name: Get Tag
+        id: tag
+        uses: dawidd6/action-get-tag@v1
+        with:
+          strip_v: false
+      - name: Publish flipper-chess
+        uses: softprops/action-gh-release@v1
+        with:
+          files: |
+            build/f7-firmware-C/.extapps/flipchess.fap
+            applications_user/flipper-chess/README.md
+          name: ${{steps.tag.outputs.tag}}
+          body: Built against Flipper Zero firmware v${{ env.firmware_version }}
+          generate_release_notes: true
+          fail_on_unmatched_files: true

+ 52 - 0
.gitignore

@@ -0,0 +1,52 @@
+# Prerequisites
+*.d
+
+# Object files
+*.o
+*.ko
+*.obj
+*.elf
+
+# Linker output
+*.ilk
+*.map
+*.exp
+
+# Precompiled Headers
+*.gch
+*.pch
+
+# Libraries
+*.lib
+*.a
+*.la
+*.lo
+
+# Shared objects (inc. Windows DLLs)
+*.dll
+*.so
+*.so.*
+*.dylib
+
+# Executables
+*.exe
+*.out
+*.app
+*.i*86
+*.x86_64
+*.hex
+
+# Debug files
+*.dSYM/
+*.su
+*.idb
+*.pdb
+
+# Kernel Module Compile Results
+*.mod*
+*.cmd
+.tmp_versions/
+modules.order
+Module.symvers
+Mkfile.old
+dkms.conf

+ 21 - 0
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.

+ 31 - 0
README.md

@@ -0,0 +1,31 @@
+# flipper-chess
+
+## Chess game for Flipper Zero
+- Built against `0.86.1` Flipper Zero firmware release
+- Uses [smallchesslib](https://codeberg.org/drummyfish/smallchesslib)
+
+### DONATE IF YOU LIKE THE GAME
+  - ETH (or ERC-20): `xtruan.eth` or `0xa9Ad79502cdaf4F6881f3C2ef260713e5B771CE2`
+  - BTC: `16RP5Ui5QrWrVh2rR7NKAPwE5A4uFjCfbs`
+
+### Installation
+
+- Download [last release fap file](https://github.com/xtruan/flipper-chess/releases/latest)
+- Copy fap file to the apps folder of your Flipper SD card
+
+### Usage
+
+- Start "Chess" plugin
+
+### Build
+
+- Recursively clone your base firmware (official or not)
+- Clone this repository in `applications_user`
+- Build with `./fbt fap_dist APPSRC=applications_user/flipper-chess`
+- Retrieve build fap in dist subfolders
+
+(More info about build tool [here](https://github.com/flipperdevices/flipperzero-firmware/blob/dev/documentation/fbt.md))
+
+### 
+
+

+ 17 - 0
application.fam

@@ -0,0 +1,17 @@
+App(
+    appid="flipchess",
+    name="Chess",
+    apptype=FlipperAppType.EXTERNAL,
+    entry_point="flipchess_app",
+        requires=[
+        "gui",
+    ],
+    stack_size=2 * 1024,
+    order=40,
+    fap_icon="flipchess_10px.png",
+    fap_icon_assets="icons",
+    fap_category="Games",
+    fap_description="Chess for Flipper",
+    fap_author="Struan Clark (xtruan)",
+    fap_weburl="https://github.com/xtruan/flipper-chess",
+)

+ 3697 - 0
chess/smallchesslib.h

@@ -0,0 +1,3697 @@
+#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 <stdint.h>
+
+#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

+ 174 - 0
flipchess.c

@@ -0,0 +1,174 @@
+#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) {
+                strcpy(app->import_game_text, app->input_text);
+
+                int status = FlipChessStatusSuccess;
+                
+                if(status == FlipChessStatusSuccess) {
+                    //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;
+
+    // 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_power_suppress_charge_enter();
+
+    view_dispatcher_run(app->view_dispatcher);
+
+    furi_hal_power_suppress_charge_exit();
+    flipchess_app_free(app);
+
+    return 0;
+}

+ 74 - 0
flipchess.h

@@ -0,0 +1,74 @@
+#pragma once
+
+#include <furi.h>
+#include <furi_hal.h>
+#include <gui/gui.h>
+#include <input/input.h>
+#include <stdlib.h>
+#include <notification/notification_messages.h>
+#include <gui/view_dispatcher.h>
+#include <gui/modules/submenu.h>
+#include <gui/scene_manager.h>
+#include <gui/modules/variable_item_list.h>
+#include <gui/modules/text_input.h>
+#include "scenes/flipchess_scene.h"
+#include "views/flipchess_startscreen.h"
+#include "views/flipchess_scene_1.h"
+
+#define FLIPCHESS_VERSION "v0.1.0"
+
+#define TEXT_BUFFER_SIZE 256
+
+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;
+    // Main menu options
+    int import_game;
+    // Text input
+    int 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 {
+    FlipChessStatusSuccess = 0,
+    FlipChessStatusReturn = 10,
+    FlipChessStatusLoadError = 11,
+    FlipChessStatusSaveError = 12,
+} FlipChessStatus;

BIN
flipchess_10px.png


+ 16 - 0
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;

+ 35 - 0
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);
+    }
+}

+ 7 - 0
helpers/flipchess_haptic.h

@@ -0,0 +1,7 @@
+#include <notification/notification_messages.h>
+
+void flipchess_play_happy_bump(void* context);
+
+void flipchess_play_bad_bump(void* context);
+
+void flipchess_play_long_bump(void* context);

BIN
icons/Background_128x11.png


+ 30 - 0
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,
+};

+ 29 - 0
scenes/flipchess_scene.h

@@ -0,0 +1,29 @@
+#pragma once
+
+#include <gui/scene_manager.h>
+
+// 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

+ 4 - 0
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)

+ 78 - 0
scenes/flipchess_scene_menu.c

@@ -0,0 +1,78 @@
+#include "../flipchess.h"
+
+enum SubmenuIndex {
+    SubmenuIndexScene1New = 10,
+    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);
+
+    // 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;
+        } else if(event.event == SubmenuIndexScene1Import) {
+            app->import_game = 1;
+            app->input_state = FlipChessTextInputGame;
+            text_input_set_header_text(app->text_input, "Enter game phrase");
+            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);
+}

+ 50 - 0
scenes/flipchess_scene_scene_1.c

@@ -0,0 +1,50 @@
+#include "../flipchess.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;
+    UNUSED(app);
+}

+ 97 - 0
scenes/flipchess_scene_settings.c

@@ -0,0 +1,97 @@
+#include "../flipchess.h"
+#include <lib/toolbox/value_index.h>
+
+#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;
+
+    // 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);
+}

+ 55 - 0
scenes/flipchess_scene_startscreen.c

@@ -0,0 +1,55 @@
+#include "../flipchess.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;
+    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;
+    UNUSED(app);
+}

+ 637 - 0
views/flipchess_scene_1.c

@@ -0,0 +1,637 @@
+#include "../flipchess.h"
+#include <furi.h>
+#include <furi_hal.h>
+#include <furi_hal_random.h>
+#include <input/input.h>
+#include <gui/elements.h>
+//#include <dolphin/dolphin.h>
+#include <string.h>
+//#include "flipchess_icons.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 MAX_TEXT_LEN 30 // 30 = max length of text
+#define MAX_TEXT_BUF (MAX_TEXT_LEN + 1) // max length of text + null terminator
+
+struct FlipChessScene1 {
+    View* view;
+    FlipChessScene1Callback callback;
+    void* context;
+};
+typedef struct {
+    uint8_t paramPlayerW;
+    uint8_t paramPlayerB;
+
+    // uint8_t paramBoard = 1;
+    uint8_t paramAnalyze; // depth of analysis
+    uint8_t paramMoves;
+    //uint8_t paramXboard = 0;
+    uint8_t paramInfo;
+    //uint8_t paramDraw = 1;
+    uint8_t paramFlipBoard;
+    //uint8_t paramHelp = 0;
+    uint8_t paramExit;
+    uint16_t paramStep;
+    char* paramFEN;
+    char* paramPGN;
+    //uint16_t paramRandom = 0;
+    //uint8_t paramBlind = 0;
+
+    int clockSeconds;
+    SCL_Game game;
+    SCL_Board startState;
+    int16_t random960PosNumber;
+
+    //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[16];
+    char moveString2[16];
+    char moveString3[16];
+
+    SCL_SquareSet moveHighlight;
+    uint8_t squareFrom;
+    uint8_t squareTo;
+    uint8_t turnState;
+
+} FlipChessScene1Model;
+
+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, 15);
+    strncpy(model->moveString2, model->moveString, 15);
+}
+
+void flipchess_drawBoard(FlipChessScene1Model* model) {
+    // draw chess board
+    SCL_drawBoard(
+        model->game.board,
+        flipchess_putImagePixel,
+        model->squareSelected,
+        model->moveHighlight,
+        model->paramFlipBoard);
+}
+
+uint8_t flipchess_turn(FlipChessScene1Model* model) {
+    // 0: none, 1: player, 2: AI, 3: undo
+    uint8_t moveType = 0;
+
+    // if(model->paramInfo) {
+        
+    //     if(model->random960PosNumber >= 0)
+    //         printf("960 random position number: %d\n", model->random960PosNumber);
+
+    //     printf("ply number: %d\n", model->game.ply);
+
+    //     SCL_boardToFEN(model->game.board, string);
+    //     printf("FEN: %s\n", string);
+
+    //     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)
+        return FlipChessStatusReturn;
+
+    char movePromote = 'q';
+
+    if(flipchess_isPlayerTurn(model)) {
+        
+        // if(stringsEqual(string, "undo", 5))
+        //     moveType = 3;
+        // 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 = 1;
+            }
+            model->turnState = 0;
+            SCL_squareSetClear(model->moveHighlight);
+        }
+        
+    } else {
+        model->squareSelected = 255;
+        flipchess_makeAIMove(
+            model->game.board, &(model->squareFrom), &(model->squareTo), &movePromote, model);
+        moveType = 2;
+        model->turnState = 0;
+    }
+
+    if(moveType == 1 || moveType == 2) {
+        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 == 3) {
+        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";
+        break;
+
+    case SCL_GAME_STATE_BLACK_WIN:
+        model->msg = "black wins";
+        break;
+
+    case SCL_GAME_STATE_DRAW_STALEMATE:
+        model->msg = "draw (stalemate)";
+        break;
+
+    case SCL_GAME_STATE_DRAW_REPETITION:
+        model->msg = "draw (repetition)";
+        break;
+
+    case SCL_GAME_STATE_DRAW_DEAD:
+        model->msg = "draw (dead pos.)";
+        break;
+
+    case SCL_GAME_STATE_DRAW:
+        model->msg = "draw";
+        break;
+
+    case SCL_GAME_STATE_DRAW_50:
+        model->msg = "draw (50 moves)";
+        break;
+
+    default:
+        if(model->game.ply > 0) {
+            model->msg = (SCL_boardWhitesTurn(model->game.board) ? "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;
+    }
+
+    return moveType;
+}
+
+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);
+
+    // Frame
+    canvas_draw_frame(canvas, 0, 0, 66, 64);
+
+    // Message
+    canvas_set_font(canvas, FontSecondary);
+    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) {
+    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 = 0;
+    model->paramStep = 0;
+    model->paramFEN = NULL;
+    model->paramPGN = NULL;
+
+    model->clockSeconds = -1;
+    SCL_Board emptyStartState = SCL_BOARD_START_STATE;
+    memcpy(model->startState, &emptyStartState, sizeof(SCL_Board));
+    model->random960PosNumber = -1;
+
+    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';
+
+    SCL_SquareSet emptySquareSet = SCL_SQUARE_SET_EMPTY;
+    memcpy(model->moveHighlight, &emptySquareSet, sizeof(SCL_SquareSet));
+    model->squareFrom = 255;
+    model->squareTo = 255;
+    model->turnState = 0;
+
+    furi_hal_random_init();
+    SCL_randomBetterSeed(furi_hal_random_get());
+
+#if SCL_960_CASTLING
+    if(model->random960PosNumber < 0) model->random960PosNumber = SCL_randomBetter();
+#endif
+
+    if(model->random960PosNumber >= 0) model->random960PosNumber %= 960;
+
+    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 SCL_960_CASTLING
+    else
+        SCL_boardInit960(model->startState, model->random960PosNumber);
+#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 FlipChessStatusSuccess;
+}
+
+bool flipchess_scene_1_input(InputEvent* event, void* context) {
+    furi_assert(context);
+    FlipChessScene1* instance = context;
+
+    if(event->type == InputTypeRelease) {
+        switch(event->key) {
+        case InputKeyBack:
+            with_view_model(
+                instance->view,
+                FlipChessScene1Model * model,
+                {
+                    UNUSED(model);
+                    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, 
+                { 
+                    // first turn of round, probably player but could be AI
+                    uint8_t turn = flipchess_turn(model);
+                    if(turn == FlipChessStatusReturn) {
+                        instance->callback(FlipChessCustomEventScene1Back, instance->context);
+                    } else {
+                        flipchess_drawBoard(model);
+                    }
+ 
+                    // if player played, let AI play
+                    if (!flipchess_isPlayerTurn(model))
+                    {
+                        turn = flipchess_turn(model);
+                        if(turn == FlipChessStatusReturn) {
+                            instance->callback(FlipChessCustomEventScene1Back, instance->context);
+                        } else {
+                            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->squareSelected = 255; }, 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,
+        {
+            int init =
+                flipchess_scene_1_model_init(model, app->white_mode, app->black_mode);
+
+            // nonzero status
+            if(init == FlipChessStatusSuccess) {
+                // 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_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;
+}

+ 19 - 0
views/flipchess_scene_1.h

@@ -0,0 +1,19 @@
+#pragma once
+
+#include <gui/view.h>
+#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);

+ 130 - 0
views/flipchess_startscreen.c

@@ -0,0 +1,130 @@
+#include "../flipchess.h"
+#include <furi.h>
+#include <furi_hal.h>
+#include <input/input.h>
+#include <gui/elements.h>
+#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_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_right(canvas, "Start");
+}
+
+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;
+    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:
+        case InputKeyRight:
+        case InputKeyUp:
+        case InputKeyDown:
+        case InputKeyOk:
+            with_view_model(
+                instance->view,
+                FlipChessStartscreenModel * model,
+                {
+                    UNUSED(model);
+                    instance->callback(FlipChessCustomEventStartscreenOk, instance->context);
+                },
+                true);
+            break;
+        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;
+}

+ 19 - 0
views/flipchess_startscreen.h

@@ -0,0 +1,19 @@
+#pragma once
+
+#include <gui/view.h>
+#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);