Sfoglia il codice sorgente

update and add new app

MX 2 anni fa
parent
commit
7fe606edbf
29 ha cambiato i file con 5388 aggiunte e 10 eliminazioni
  1. 2 1
      ReadMe.md
  2. BIN
      apps/Games/flipchess.fap
  3. BIN
      apps/RFID/flipper_rfidbeacon.fap
  4. 30 0
      non_catalog_apps/chess/.github/workflows/build.yml
  5. 44 0
      non_catalog_apps/chess/.github/workflows/release.yml
  6. 52 0
      non_catalog_apps/chess/.gitignore
  7. 21 0
      non_catalog_apps/chess/LICENSE
  8. 31 0
      non_catalog_apps/chess/README.md
  9. 17 0
      non_catalog_apps/chess/application.fam
  10. 3697 0
      non_catalog_apps/chess/chess/smallchesslib.h
  11. 174 0
      non_catalog_apps/chess/flipchess.c
  12. 74 0
      non_catalog_apps/chess/flipchess.h
  13. BIN
      non_catalog_apps/chess/flipchess_10px.png
  14. 16 0
      non_catalog_apps/chess/helpers/flipchess_custom_event.h
  15. 35 0
      non_catalog_apps/chess/helpers/flipchess_haptic.c
  16. 7 0
      non_catalog_apps/chess/helpers/flipchess_haptic.h
  17. BIN
      non_catalog_apps/chess/icons/Background_128x11.png
  18. 30 0
      non_catalog_apps/chess/scenes/flipchess_scene.c
  19. 29 0
      non_catalog_apps/chess/scenes/flipchess_scene.h
  20. 4 0
      non_catalog_apps/chess/scenes/flipchess_scene_config.h
  21. 78 0
      non_catalog_apps/chess/scenes/flipchess_scene_menu.c
  22. 50 0
      non_catalog_apps/chess/scenes/flipchess_scene_scene_1.c
  23. 97 0
      non_catalog_apps/chess/scenes/flipchess_scene_settings.c
  24. 55 0
      non_catalog_apps/chess/scenes/flipchess_scene_startscreen.c
  25. 637 0
      non_catalog_apps/chess/views/flipchess_scene_1.c
  26. 19 0
      non_catalog_apps/chess/views/flipchess_scene_1.h
  27. 130 0
      non_catalog_apps/chess/views/flipchess_startscreen.c
  28. 19 0
      non_catalog_apps/chess/views/flipchess_startscreen.h
  29. 40 9
      non_catalog_apps/flipper_rfidbeacon/flipper_rfidbeacon.c

+ 2 - 1
ReadMe.md

@@ -20,7 +20,7 @@ Sources of "integrated/bundled" apps are added now in this repo too, to allow pu
 
 The Flipper and its community wouldn't be as rich as it is without your contributions and support. Thank you for all you have done.
 
-### Apps checked & updated at `11 Jul 14:06 GMT +3`
+### Apps checked & updated at `13 Jul 23:41 GMT +3`
 
 ## Games
 - [Pong (By nmrr)](https://github.com/nmrr/flipperzero-pong) - Modified by [SimplyMinimal](https://github.com/SimplyMinimal/FlipperZero-Pong)
@@ -50,6 +50,7 @@ The Flipper and its community wouldn't be as rich as it is without your contribu
 - [Rubik's Cube Scrambler (By RaZeSloth)](https://github.com/RaZeSloth/flipperzero-rubiks-cube-scrambler)
 - [Etch-A-Sketch (By SimplyMinimal)](https://github.com/SimplyMinimal/FlipperZero-Etch-A-Sketch)
 - [Paint (By n-o-T-I-n-s-a-n-e)](https://github.com/n-o-T-I-n-s-a-n-e)
+- [Chess (By xtruan)](https://github.com/xtruan/flipper-chess)
 
 ## Media
 - [Tuning Fork (By besya)](https://github.com/besya/flipperzero-tuning-fork) - Fixes [(by Willy-JL)](https://github.com/ClaraCrazy/Flipper-Xtreme/commit/44023851f7349b6ae9ca9f9bd9228d795a7e04c0)

BIN
apps/Games/flipchess.fap


BIN
apps/RFID/flipper_rfidbeacon.fap


+ 30 - 0
non_catalog_apps/chess/.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
non_catalog_apps/chess/.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
non_catalog_apps/chess/.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
non_catalog_apps/chess/LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2023 Struan Clark
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 31 - 0
non_catalog_apps/chess/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
non_catalog_apps/chess/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
non_catalog_apps/chess/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
non_catalog_apps/chess/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
non_catalog_apps/chess/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
non_catalog_apps/chess/flipchess_10px.png


+ 16 - 0
non_catalog_apps/chess/helpers/flipchess_custom_event.h

@@ -0,0 +1,16 @@
+#pragma once
+
+typedef enum {
+    FlipChessCustomEventStartscreenUp,
+    FlipChessCustomEventStartscreenDown,
+    FlipChessCustomEventStartscreenLeft,
+    FlipChessCustomEventStartscreenRight,
+    FlipChessCustomEventStartscreenOk,
+    FlipChessCustomEventStartscreenBack,
+    FlipChessCustomEventScene1Up,
+    FlipChessCustomEventScene1Down,
+    FlipChessCustomEventScene1Left,
+    FlipChessCustomEventScene1Right,
+    FlipChessCustomEventScene1Ok,
+    FlipChessCustomEventScene1Back,
+} FlipChessCustomEvent;

+ 35 - 0
non_catalog_apps/chess/helpers/flipchess_haptic.c

@@ -0,0 +1,35 @@
+#include "flipchess_haptic.h"
+#include "../flipchess.h"
+
+void flipchess_play_happy_bump(void* context) {
+    FlipChess* app = context;
+    if(app->haptic != 1) {
+        return;
+    }
+    notification_message(app->notification, &sequence_set_vibro_on);
+    furi_thread_flags_wait(0, FuriFlagWaitAny, 20);
+    notification_message(app->notification, &sequence_reset_vibro);
+}
+
+void flipchess_play_bad_bump(void* context) {
+    FlipChess* app = context;
+    if(app->haptic != 1) {
+        return;
+    }
+    notification_message(app->notification, &sequence_set_vibro_on);
+    furi_thread_flags_wait(0, FuriFlagWaitAny, 100);
+    notification_message(app->notification, &sequence_reset_vibro);
+}
+
+void flipchess_play_long_bump(void* context) {
+    FlipChess* app = context;
+    if(app->haptic != 1) {
+        return;
+    }
+    for(int i = 0; i < 4; i++) {
+        notification_message(app->notification, &sequence_set_vibro_on);
+        furi_thread_flags_wait(0, FuriFlagWaitAny, 50);
+        notification_message(app->notification, &sequence_reset_vibro);
+        furi_thread_flags_wait(0, FuriFlagWaitAny, 100);
+    }
+}

+ 7 - 0
non_catalog_apps/chess/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
non_catalog_apps/chess/icons/Background_128x11.png


+ 30 - 0
non_catalog_apps/chess/scenes/flipchess_scene.c

@@ -0,0 +1,30 @@
+#include "flipchess_scene.h"
+
+// Generate scene on_enter handlers array
+#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter,
+void (*const flipchess_on_enter_handlers[])(void*) = {
+#include "flipchess_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Generate scene on_event handlers array
+#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event,
+bool (*const flipchess_on_event_handlers[])(void* context, SceneManagerEvent event) = {
+#include "flipchess_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Generate scene on_exit handlers array
+#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit,
+void (*const flipchess_on_exit_handlers[])(void* context) = {
+#include "flipchess_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Initialize scene handlers configuration structure
+const SceneManagerHandlers flipchess_scene_handlers = {
+    .on_enter_handlers = flipchess_on_enter_handlers,
+    .on_event_handlers = flipchess_on_event_handlers,
+    .on_exit_handlers = flipchess_on_exit_handlers,
+    .scene_num = FlipChessSceneNum,
+};

+ 29 - 0
non_catalog_apps/chess/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
non_catalog_apps/chess/scenes/flipchess_scene_config.h

@@ -0,0 +1,4 @@
+ADD_SCENE(flipchess, startscreen, Startscreen)
+ADD_SCENE(flipchess, menu, Menu)
+ADD_SCENE(flipchess, scene_1, Scene_1)
+ADD_SCENE(flipchess, settings, Settings)

+ 78 - 0
non_catalog_apps/chess/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
non_catalog_apps/chess/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
non_catalog_apps/chess/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
non_catalog_apps/chess/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
non_catalog_apps/chess/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
non_catalog_apps/chess/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
non_catalog_apps/chess/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
non_catalog_apps/chess/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);

+ 40 - 9
non_catalog_apps/flipper_rfidbeacon/flipper_rfidbeacon.c

@@ -19,9 +19,9 @@ typedef struct {
     InputEvent input;
 } EventApp;
 
-const char CW_char[36] = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
-const uint8_t CW_size[36] = {2, 4, 4, 3, 1, 4, 3, 4, 2, 4, 3, 4, 2, 2, 3, 4, 4, 3, 3, 1, 3, 4, 3, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5};
-const uint8_t CW_value[36] = {0b01000000, 0b10000000, 0b10100000, 0b10000000, 0b00000000, 0b00100000, 0b11000000, 0b00000000, 0b00000000, 0b01110000, 0b10100000, 0b01000000, 0b11000000, 0b10000000, 0b11100000, 0b01100000, 0b11010000, 0b01000000, 0b00000000, 0b10000000, 0b00100000, 0b00010000, 0b01100000, 0b10010000, 0b10110000, 0b11000000, 0b11111000, 0b01111000, 0b00111000, 0b00011000, 0b00001000, 0b00000000, 0b10000000, 0b11000000, 0b11100000, 0b11110000};
+const char CW_char[54] = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', ',', '?', '\'', '!', '/', '(', ')', '&', ':', ';', '=', '+', '-', '_', '"', '$', '@'};
+const uint8_t CW_size[54] = {2, 4, 4, 3, 1, 4, 3, 4, 2, 4, 3, 4, 2, 2, 3, 4, 4, 3, 3, 1, 3, 4, 3, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 5, 5, 6, 5, 6, 6, 5, 5, 6, 6, 6, 7, 6};
+const uint8_t CW_value[54] = {0b01000000, 0b10000000, 0b10100000, 0b10000000, 0b00000000, 0b00100000, 0b11000000, 0b00000000, 0b00000000, 0b01110000, 0b10100000, 0b01000000, 0b11000000, 0b10000000, 0b11100000, 0b01100000, 0b11010000, 0b01000000, 0b00000000, 0b10000000, 0b00100000, 0b00010000, 0b01100000, 0b10010000, 0b10110000, 0b11000000, 0b11111000, 0b01111000, 0b00111000, 0b00011000, 0b00001000, 0b00000000, 0b10000000, 0b11000000, 0b11100000, 0b11110000, 0b01010100, 0b11001100, 0b00110000, 0b01111000, 0b10101100, 0b10010000, 0b10110000, 0b10110100, 0b01000000, 0b11100000, 0b10101000, 0b10001000, 0b01010000, 0b10000100, 0b00110100, 0b01001000, 0b00010010, 0b01101000};
 
 typedef struct {
     FuriMutex* mutex;
@@ -42,8 +42,35 @@ static void draw_callback(Canvas* canvas, void* ctx)
     canvas_set_font(canvas, FontPrimary);
     canvas_draw_str_aligned(canvas, 64, 5, AlignCenter, AlignCenter, "RFID Beacon");
 
-    char buffer[8];
-    snprintf(buffer, sizeof(buffer), "%c", CW_char[mutexVal->status]);
+    char buffer[24];
+    uint8_t positionBuffer = 0;
+    buffer[positionBuffer++] = CW_char[mutexVal->status];
+    buffer[positionBuffer++] = ' ';
+    buffer[positionBuffer++] = ' ';
+    buffer[positionBuffer++] = ' ';
+    buffer[positionBuffer++] = ' ';
+
+    uint8_t maskMorse = 0b10000000;
+    for (uint8_t i=0;i<CW_size[mutexVal->status];i++)
+    {
+        if (i != 0)
+        {
+            buffer[positionBuffer++] = ' ';
+            maskMorse >>= 1;
+        }
+
+        if ((CW_value[mutexVal->status] & maskMorse) != 0)
+        {
+            buffer[positionBuffer++] = '_';
+        }
+        else
+        {
+            buffer[positionBuffer++] = '.';
+        }
+    }
+
+    buffer[positionBuffer++] = '\0';
+
     canvas_draw_str_aligned(canvas, 64, 32, AlignCenter, AlignCenter, buffer);
 
     if (mutexVal->enableCW_mutex == 0) canvas_draw_str_aligned(canvas, 64, 59, AlignCenter, AlignCenter, "OFF");
@@ -143,7 +170,11 @@ int32_t flipper_rfidbeacon_app()
                     letterPosition = 0;
                     draw = 0;
                 }
-                else enableCW = 0;
+                else 
+                {
+                    RFID_OFF(notification);
+                    enableCW = 0;
+                }
 
                 mutexVal.enableCW_mutex = enableCW;
                 screenRefresh = 1;
@@ -154,7 +185,7 @@ int32_t flipper_rfidbeacon_app()
                 furi_mutex_acquire(mutexVal.mutex, FuriWaitForever);
 
                 if (mutexVal.status != 0) mutexVal.status--;
-                else mutexVal.status = 35;
+                else mutexVal.status = 53;
 
                 screenRefresh = 1;
                 furi_mutex_release(mutexVal.mutex);
@@ -163,7 +194,7 @@ int32_t flipper_rfidbeacon_app()
             {
                 furi_mutex_acquire(mutexVal.mutex, FuriWaitForever);
 
-                if (mutexVal.status != 35) mutexVal.status++;
+                if (mutexVal.status != 53) mutexVal.status++;
                 else mutexVal.status = 0;
 
                 screenRefresh = 1;
@@ -261,4 +292,4 @@ int32_t flipper_rfidbeacon_app()
 
 
     return 0;
-}
+}