From bb219c44848249516d4187e25752efebc79f33f9 Mon Sep 17 00:00:00 2001 From: Fraxy V Date: Wed, 6 Dec 2023 19:12:42 +0200 Subject: [PATCH 01/15] wchess: whisper assisted chess --- examples/CMakeLists.txt | 2 + examples/helpers.js | 1 + examples/wchess/CMakeLists.txt | 9 + examples/wchess/libwchess/CMakeLists.txt | 19 + examples/wchess/libwchess/Chessboard.cpp | 794 +++++++ examples/wchess/libwchess/Chessboard.h | 33 + examples/wchess/libwchess/WChess.cpp | 183 ++ examples/wchess/libwchess/WChess.h | 55 + examples/wchess/libwchess/test-chessboard.cpp | 106 + examples/wchess/wchess.cmd/CMakeLists.txt | 8 + examples/wchess/wchess.cmd/wchess.cmd.cpp | 243 +++ examples/wchess/wchess.wasm/CMakeLists.txt | 51 + .../chessboardjs-1.0.0/CHANGELOG.md | 32 + .../wchess.wasm/chessboardjs-1.0.0/LICENSE.md | 20 + .../wchess.wasm/chessboardjs-1.0.0/README.md | 82 + .../css/chessboard-1.0.0.css | 54 + .../css/chessboard-1.0.0.min.css | 2 + .../img/chesspieces/wikipedia/bB.png | Bin 0 -> 1405 bytes .../img/chesspieces/wikipedia/bK.png | Bin 0 -> 3009 bytes .../img/chesspieces/wikipedia/bN.png | Bin 0 -> 1875 bytes .../img/chesspieces/wikipedia/bP.png | Bin 0 -> 777 bytes .../img/chesspieces/wikipedia/bQ.png | Bin 0 -> 2648 bytes .../img/chesspieces/wikipedia/bR.png | Bin 0 -> 748 bytes .../img/chesspieces/wikipedia/wB.png | Bin 0 -> 2374 bytes .../img/chesspieces/wikipedia/wK.png | Bin 0 -> 2823 bytes .../img/chesspieces/wikipedia/wN.png | Bin 0 -> 2388 bytes .../img/chesspieces/wikipedia/wP.png | Bin 0 -> 1571 bytes .../img/chesspieces/wikipedia/wQ.png | Bin 0 -> 3812 bytes .../img/chesspieces/wikipedia/wR.png | Bin 0 -> 1097 bytes .../chessboardjs-1.0.0/js/chessboard-1.0.0.js | 1817 +++++++++++++++++ .../js/chessboard-1.0.0.min.js | 2 + .../chessboardjs-1.0.0/package.json | 29 + examples/wchess/wchess.wasm/index-tmpl.html | 371 ++++ .../wchess/wchess.wasm/jquery-3.7.1.min.js | 2 + examples/wchess/wchess.wasm/wchess.wasm.cpp | 132 ++ 35 files changed, 4047 insertions(+) create mode 100644 examples/wchess/CMakeLists.txt create mode 100644 examples/wchess/libwchess/CMakeLists.txt create mode 100644 examples/wchess/libwchess/Chessboard.cpp create mode 100644 examples/wchess/libwchess/Chessboard.h create mode 100644 examples/wchess/libwchess/WChess.cpp create mode 100644 examples/wchess/libwchess/WChess.h create mode 100644 examples/wchess/libwchess/test-chessboard.cpp create mode 100644 examples/wchess/wchess.cmd/CMakeLists.txt create mode 100644 examples/wchess/wchess.cmd/wchess.cmd.cpp create mode 100644 examples/wchess/wchess.wasm/CMakeLists.txt create mode 100644 examples/wchess/wchess.wasm/chessboardjs-1.0.0/CHANGELOG.md create mode 100644 examples/wchess/wchess.wasm/chessboardjs-1.0.0/LICENSE.md create mode 100644 examples/wchess/wchess.wasm/chessboardjs-1.0.0/README.md create mode 100644 examples/wchess/wchess.wasm/chessboardjs-1.0.0/css/chessboard-1.0.0.css create mode 100644 examples/wchess/wchess.wasm/chessboardjs-1.0.0/css/chessboard-1.0.0.min.css create mode 100644 examples/wchess/wchess.wasm/chessboardjs-1.0.0/img/chesspieces/wikipedia/bB.png create mode 100644 examples/wchess/wchess.wasm/chessboardjs-1.0.0/img/chesspieces/wikipedia/bK.png create mode 100644 examples/wchess/wchess.wasm/chessboardjs-1.0.0/img/chesspieces/wikipedia/bN.png create mode 100644 examples/wchess/wchess.wasm/chessboardjs-1.0.0/img/chesspieces/wikipedia/bP.png create mode 100644 examples/wchess/wchess.wasm/chessboardjs-1.0.0/img/chesspieces/wikipedia/bQ.png create mode 100644 examples/wchess/wchess.wasm/chessboardjs-1.0.0/img/chesspieces/wikipedia/bR.png create mode 100644 examples/wchess/wchess.wasm/chessboardjs-1.0.0/img/chesspieces/wikipedia/wB.png create mode 100644 examples/wchess/wchess.wasm/chessboardjs-1.0.0/img/chesspieces/wikipedia/wK.png create mode 100644 examples/wchess/wchess.wasm/chessboardjs-1.0.0/img/chesspieces/wikipedia/wN.png create mode 100644 examples/wchess/wchess.wasm/chessboardjs-1.0.0/img/chesspieces/wikipedia/wP.png create mode 100644 examples/wchess/wchess.wasm/chessboardjs-1.0.0/img/chesspieces/wikipedia/wQ.png create mode 100644 examples/wchess/wchess.wasm/chessboardjs-1.0.0/img/chesspieces/wikipedia/wR.png create mode 100644 examples/wchess/wchess.wasm/chessboardjs-1.0.0/js/chessboard-1.0.0.js create mode 100644 examples/wchess/wchess.wasm/chessboardjs-1.0.0/js/chessboard-1.0.0.min.js create mode 100644 examples/wchess/wchess.wasm/chessboardjs-1.0.0/package.json create mode 100644 examples/wchess/wchess.wasm/index-tmpl.html create mode 100644 examples/wchess/wchess.wasm/jquery-3.7.1.min.js create mode 100644 examples/wchess/wchess.wasm/wchess.wasm.cpp diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index c6a2c305e9c..e6f837f3a77 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -73,3 +73,5 @@ else() add_subdirectory(talk-llama) add_subdirectory(lsp) endif() + +add_subdirectory(wchess) diff --git a/examples/helpers.js b/examples/helpers.js index 98cc5c63ec4..23f18ab5697 100644 --- a/examples/helpers.js +++ b/examples/helpers.js @@ -22,6 +22,7 @@ var printTextarea = (function() { async function clearCache() { if (confirm('Are you sure you want to clear the cache?\nAll the models will be downloaded again.')) { indexedDB.deleteDatabase(dbName); + location.reload(); } } diff --git a/examples/wchess/CMakeLists.txt b/examples/wchess/CMakeLists.txt new file mode 100644 index 00000000000..4bbc85ab6b2 --- /dev/null +++ b/examples/wchess/CMakeLists.txt @@ -0,0 +1,9 @@ +set(CMAKE_CXX_STANDARD 11) + +add_subdirectory(libwchess) + +if (EMSCRIPTEN) + add_subdirectory(wchess.wasm) +else() + add_subdirectory(wchess.cmd) +endif() diff --git a/examples/wchess/libwchess/CMakeLists.txt b/examples/wchess/libwchess/CMakeLists.txt new file mode 100644 index 00000000000..32857224a6b --- /dev/null +++ b/examples/wchess/libwchess/CMakeLists.txt @@ -0,0 +1,19 @@ +add_library(libwchess + WChess.cpp + WChess.h + Chessboard.cpp + Chessboard.h +) + +target_link_libraries(libwchess + PUBLIC + whisper + common +) + +target_include_directories(libwchess + PUBLIC + "$" +) + +# add_executable(test-chessboard test-chessboard.cpp Chessboard.cpp) diff --git a/examples/wchess/libwchess/Chessboard.cpp b/examples/wchess/libwchess/Chessboard.cpp new file mode 100644 index 00000000000..e321faeff83 --- /dev/null +++ b/examples/wchess/libwchess/Chessboard.cpp @@ -0,0 +1,794 @@ +#include "Chessboard.h" +#include +#include +#include +#include +#include +#include + +namespace { +// remove std::string_view, c++17 -> c++11 +constexpr std::array positions = { + "a1", "b1", "c1", "d1", "e1", "f1", "g1", "h1", + "a2", "b2", "c2", "d2", "e2", "f2", "g2", "h2", + "a3", "b3", "c3", "d3", "e3", "f3", "g3", "h3", + "a4", "b4", "c4", "d4", "e4", "f4", "g4", "h4", + "a5", "b5", "c5", "d5", "e5", "f5", "g5", "h5", + "a6", "b6", "c6", "d6", "e6", "f6", "g6", "h6", + "a7", "b7", "c7", "d7", "e7", "f7", "g7", "h7", + "a8", "b8", "c8", "d8", "e8", "f8", "g8", "h8", +}; +constexpr char INVALID_POS = positions.size(); +constexpr int R = 0; // rank index +constexpr int F = 1; // file index +#define FILE (c[F] - '1') +#define RANK (c[R] - 'a') +constexpr char operator ""_P(const char * c, size_t size) { + return size < 2 || RANK < 0 || RANK > 7 || + FILE < 0 || FILE > 7 ? INVALID_POS : FILE * 8 + RANK; +} +#undef FILE +#undef RANK + +struct sview { + const char * ptr = nullptr; + size_t size = 0; + + sview() = default; + sview(const char * p, size_t s) : ptr(p), size(s) {} + sview(const std::string& s) : ptr(s.data()), size(s.size()) {} + + size_t find(char del, size_t pos) { + while (pos < size && ptr[pos] != del) ++pos; + return pos < size ? pos : std::string::npos; + } +}; + +std::vector split(sview str, char del) { + std::vector res; + size_t cur = 0; + size_t last = 0; + while (cur != std::string::npos) { + if (str.ptr[last] == ' ') { + ++last; + continue; + } + cur = str.find(del, last); + size_t len = cur == std::string::npos ? str.size - last : cur - last; + res.emplace_back(str.ptr + last, len); + last = cur + 1; + } + return res; +} + +char strToPos(sview str) { + return operator ""_P(str.ptr, str.size); +} + +constexpr std::array pieceNames = { + "pawn", "knight", "bishop", "rook", "queen", "king", +}; + +static constexpr std::array blackShort = { + 'p', 'n', 'b', 'r', 'q', 'k', +}; +static constexpr std::array whiteShort = { + 'P', 'N', 'B', 'R', 'Q', 'K', +}; + +char strToType(sview str) { + auto it = std::find_if(pieceNames.begin(), pieceNames.end(), [str] (const char* name) { return strncmp(name, str.ptr, str.size) == 0; }); + return it != pieceNames.end() ? it - pieceNames.begin() : pieceNames.size(); +} + +// directions +using Direction = std::array; +constexpr Direction N = {0, 1}; +constexpr Direction NNE = {1, 2}; +constexpr Direction NE = {1, 1}; +constexpr Direction ENE = {2, 1}; +constexpr Direction E = {1, 0}; +constexpr Direction ESE = {2, -1}; +constexpr Direction SE = {1, -1}; +constexpr Direction SSE = {1, -2}; +constexpr Direction S = {0, -1}; +constexpr Direction SSW = {-1, -2}; +constexpr Direction SW = {-1, -1}; +constexpr Direction WSW = {-2, -1}; +constexpr Direction W = {-1, 0}; +constexpr Direction WNW = {-2, 1}; +constexpr Direction NW = {-1, 1}; +constexpr Direction NNW = {-1, 2}; + +char makeStep(char pos, const Direction& d) { + char next[2] = { char(positions[pos][R] + d[R]) , char(positions[pos][F] + d[F]) }; + return strToPos(sview{next, sizeof(next)}); +} + +template +char traverse(char pos, const Direction& d, const Modifier& m, char count = 8) { + while (--count >= 0) { + pos = makeStep(pos, d); + if (pos == INVALID_POS || m(pos)) break; + } + return pos; +} + +Direction normalize(const Direction& distance) { + return {char((distance[R] > 0) - (distance[R] < 0)), char((distance[F] > 0) - (distance[F] < 0))}; +} + +struct Pin { + Direction d; + Piece* pinner; + Piece* pinned; +}; +using Pins = std::list; +using Board = std::array; + +std::vector filter(const Direction& pin, std::initializer_list directions) { + if (pin[R] == 0 && pin[F] == 0) return directions; + std::vector result; + for (auto& d : directions) { + if ((d[R] == pin[R] || d[R] == -pin[R]) && (d[F] == pin[F] || d[F] == -pin[F])) result.push_back(d); + } + return result; +} +} + +class Piece { +public: + enum Types : char { + Pawn, + Knight, + Bishop, + Rook, + Queen, + King, + // + NUM_PIECES + }; + + enum Colors : char { + White, + Black, + }; + + const char* name() const; + char initial() const; + Types type() const { return m_type; } + Colors color() const { return m_color; } + char pos() const { return m_pos; } + void setPos(char pos) { + m_pos = pos; + invalidate(); + } + const char* coord() const; + const std::set& allowed() const { return m_allowed; } + bool canReach(char pos) const; + virtual bool movePattern(char pos) const = 0; + void take(); + virtual void reinit(const State& state) = 0; + void invalidate(); +protected: + Piece(Types type, Colors color, char pos, std::set allowed) + : m_type(type), m_color(color), m_pos(pos), m_allowed(std::move(allowed)) {} + Piece(const Piece&) = delete; + ~Piece() = default; + + const Types m_type; + const Colors m_color; + char m_pos; + std::set m_allowed; + bool m_update = false; +}; + +struct Pawn : public Piece { + Pawn(Colors color, char pos, std::set next) : Piece(Types::Pawn, color, pos, std::move(next)) {} + + bool is_first_move() const { + return m_color ? coord()[F] == '7' : coord()[F] == '2'; + } + + virtual bool movePattern(char pos) const override { + if (m_pos == INVALID_POS) return false; + auto cur = coord(); + auto next = positions[pos]; + Direction distance = {char(next[R] - cur[R]), char(next[F] - cur[F])}; + char forward = m_color ? -1 : 1; + return (forward == distance[F] && distance[R] * distance[R] <= 1) + || (is_first_move() && 2 * forward == distance[F] && distance[R] == 0); + } + + virtual void reinit(const State& state) override; +}; + +struct Knight : public Piece { + Knight(Colors color, char pos, std::set next) : Piece(Types::Knight, color, pos, std::move(next)) {} + + virtual bool movePattern(char pos) const override { + if (m_pos == INVALID_POS) return false; + auto cur = coord(); + auto next = positions[pos]; + Direction diff = {char(next[R] - cur[R]), char(next[F] - cur[F])}; + return diff[R]*diff[R] + diff[F]*diff[F] == 5; + } + + virtual void reinit(const State& state) override; +}; + +struct Bishop : public Piece { + Bishop(Colors color, char pos) : Piece(Types::Bishop, color, pos, {}) {} + + virtual bool movePattern(char pos) const override { + if (m_pos == INVALID_POS) return false; + auto cur = coord(); + auto next = positions[pos]; + return cur[R] - cur[F] == next[R] - next[F] || cur[R] + cur[F] == next[R] + next[F]; + } + + virtual void reinit(const State& state) override; +}; + +struct Rook : public Piece { + Rook(Colors color, char pos) : Piece(Types::Rook, color, pos, {}) {} + + virtual bool movePattern(char pos) const override { + if (m_pos == INVALID_POS) return false; + auto cur = coord(); + auto next = positions[pos]; + return cur[R] == next[R] || cur[F] == next[F]; + } + + virtual void reinit(const State& state) override; +}; + +struct Queen : public Piece { + Queen(Colors color, char pos) : Piece(Types::Queen, color, pos, {}) {} + + virtual bool movePattern(char pos) const override { + if (m_pos == INVALID_POS) return false; + auto cur = coord(); + auto next = positions[pos]; + return cur[R] == next[R] || cur[F] == next[F] || cur[R] - cur[F] == next[R] - next[F] || cur[R] + cur[F] == next[R] + next[F]; + } + + virtual void reinit(const State& state) override; +}; + +struct King : public Piece { + King(Colors color, char pos) : Piece(Types::King, color, pos, {}) {} + + virtual bool movePattern(char pos) const override { + if (m_pos == INVALID_POS) return false; + auto cur = coord(); + auto next = positions[pos]; + Direction diff = {char(next[R] - cur[R]), char(next[F] - cur[F])}; + return diff[R]*diff[R] + diff[F]*diff[F] <= 2; + } + + virtual void reinit(const State& state) override; +}; + +struct PieceSet { + Piece* begin() { return &p1; } + Piece* end() { return &r2 + 1; } + const Piece* begin() const { return &p1; } + const Piece* end() const { return &r2 + 1; } + Piece& operator[](int i) { return *(begin() + i); } + const Piece& operator[](int i) const { return *(begin() + i); } + + Pawn p1; + Pawn p2; + Pawn p3; + Pawn p4; + Pawn p5; + Pawn p6; + Pawn p7; + Pawn p8; + Rook r1; + Knight n1; + Bishop b1; + Queen q; + King k; + Bishop b2; + Knight n2; + Rook r2; +}; + +struct State { + State(); + PieceSet blacks; + PieceSet whites; + Board board; + Pins blackPins; + Pins whitePins; +}; + +Direction findPin(const Piece& piece, const State& state) { + auto& pins = piece.color() ? state.blackPins : state.whitePins; + auto it = std::find_if(pins.begin(), pins.end(), [&] (const Pin& pin) { return pin.pinned == &piece; }); + if (it != pins.end()) return it->d; + return {0, 0}; +} + +struct Find { + Find(const Board& board) : m_board(board) {} + bool operator() (char pos) const { return m_board[pos]; } + const Board& m_board; +}; + +struct Add { + Add(const Board& board, std::set& moves, Piece::Colors color) : m_board(board), m_moves(moves), m_color(color) {} + bool operator() (char pos) const { + if (!m_board[pos] || m_board[pos]->color() != m_color) m_moves.insert(pos); + return m_board[pos]; + } + const Board& m_board; + std::set& m_moves; + Piece::Colors m_color; +}; + +void Pawn::reinit(const State& state) { + if (m_pos == INVALID_POS) return; + if (!m_update) return; + m_update = false; + m_allowed.clear(); + + auto pin = findPin(*this, state); + + auto & left = m_color ? SW : NW; + auto & right = m_color ? SE : NE; + + for (auto& direction : filter(pin, { left, right })) { + auto pos = makeStep(m_pos, direction); + if (pos != INVALID_POS && state.board[pos] && state.board[pos]->color() != m_color) m_allowed.insert(pos); + } + + auto & forward = m_color ? S : N; + if (!filter(pin, {forward}).empty()) { + traverse(m_pos, forward, [&] (char pos) { + if (!state.board[pos]) m_allowed.insert(pos); + return state.board[pos] || !is_first_move(); + }, 2); + } +} + +void Knight::reinit(const State& state) { + if (m_pos == INVALID_POS) return; + if (!m_update) return; + m_update = false; + m_allowed.clear(); + auto pin = findPin(*this, state); + if (pin[R] != 0 || pin[F] != 0) return; + for (auto& direction : { NNE, ENE, ESE, SSE, SSW, WSW, WNW, NNW }) { + auto pos = makeStep(m_pos, direction); + if (pos != INVALID_POS && (!state.board[pos] || state.board[pos]->color() != m_color)) m_allowed.insert(pos); + } +} + +void Bishop::reinit(const State& state) { + if (m_pos == INVALID_POS) return; + if (!m_update) return; + m_update = false; + m_allowed.clear(); + auto pin = findPin(*this, state); + for (auto& direction : filter(pin, { NE, SE, SW, NW })) { + traverse(m_pos, direction, Add(state.board, m_allowed, m_color)); + } +} + +void Rook::reinit(const State& state) { + if (m_pos == INVALID_POS) return; + if (!m_update) return; + m_update = false; + m_allowed.clear(); + auto pin = findPin(*this, state); + for (auto& direction : filter(pin, { N, E, S, W })) { + traverse(m_pos, direction, Add(state.board, m_allowed, m_color)); + } +} + +void Queen::reinit(const State& state) { + if (m_pos == INVALID_POS) return; + if (!m_update) return; + m_update = false; + m_allowed.clear(); + auto pin = findPin(*this, state); + for (auto& direction : filter(pin, { N, NE, E, SE, S, SW, W, NW })) { + traverse(m_pos, direction, Add(state.board, m_allowed, m_color)); + } +} + +void King::reinit(const State& state) { + if (m_pos == INVALID_POS) return; + if (!m_update) return; + m_update = false; + m_allowed.clear(); + auto& enemyPieces = m_color ? state.whites : state.blacks; + auto& pawnAttackLeft = m_color ? SW : NW; + auto& pawnAttackRight = m_color ? SE : NE; + for (auto& direction : { N, NE, E, SE, S, SW, W, NW }) { + auto pos = makeStep(m_pos, direction); + bool accept = pos != INVALID_POS && !(state.board[pos] && state.board[pos]->color() == m_color); + if (accept) { + for (auto& p : enemyPieces) { + if (!p.movePattern(pos)) continue; + if (p.type() == Piece::Knight || p.type() == Piece::King) { + accept = false; + break; + } + else if (p.type() == Piece::Pawn) { + auto from = positions[pos]; + auto to = p.coord(); + Direction d {char(to[R] - from[R]), char(to[F] - from[F])}; + if (d == pawnAttackLeft || d == pawnAttackRight) { + accept = false; + break; + } + } + else { + auto from = positions[pos]; + auto to = p.coord(); + Direction d = normalize({char(to[R] - from[R]), char(to[F] - from[F])}); + auto reached = traverse(pos, d, Find(state.board)); + if (p.pos() == reached) { + accept = false; + break; + } + } + } + } + if (accept) m_allowed.insert(pos); + } +} + +const char* Piece::name() const { + static_assert(pieceNames.size() == Piece::NUM_PIECES, "Mismatch between piece names and types"); + return pieceNames[m_type]; +} + +char Piece::initial() const { + static_assert(blackShort.size() == Piece::NUM_PIECES, "Mismatch between piece names and types"); + static_assert(whiteShort.size() == Piece::NUM_PIECES, "Mismatch between piece names and types"); + return m_color ? blackShort[m_type] : whiteShort[m_type]; +} + +void Piece::invalidate() { + m_update = true; +} + + +const char* Piece::coord() const { + if (m_pos == INVALID_POS) return ""; + return positions[m_pos]; +} + +bool Piece::canReach(char pos) const { + return movePattern(pos) && m_allowed.count(pos); +} + +void Piece::take() { + m_pos = INVALID_POS; + m_allowed = {}; +} + +State::State() + : blacks { + {Piece::Black, "a7"_P, {"a5"_P, "a6"_P} }, + {Piece::Black, "b7"_P, {"b5"_P, "b6"_P} }, + {Piece::Black, "c7"_P, {"c5"_P, "c6"_P} }, + {Piece::Black, "d7"_P, {"d5"_P, "d6"_P} }, + {Piece::Black, "e7"_P, {"e5"_P, "e6"_P} }, + {Piece::Black, "f7"_P, {"f5"_P, "f6"_P} }, + {Piece::Black, "g7"_P, {"g5"_P, "g6"_P} }, + {Piece::Black, "h7"_P, {"h5"_P, "h6"_P} }, + {Piece::Black, "a8"_P}, + {Piece::Black, "b8"_P, {"a6"_P, "c6"_P} }, + {Piece::Black, "c8"_P}, + {Piece::Black, "d8"_P}, + {Piece::Black, "e8"_P}, + {Piece::Black, "f8"_P}, + {Piece::Black, "g8"_P, {"f6"_P, "h6"_P} }, + {Piece::Black, "h8"_P}, + } + , whites { + {Piece::White, "a2"_P, {"a3"_P, "a4"_P} }, + {Piece::White, "b2"_P, {"b3"_P, "b4"_P} }, + {Piece::White, "c2"_P, {"c3"_P, "c4"_P} }, + {Piece::White, "d2"_P, {"d3"_P, "d4"_P} }, + {Piece::White, "e2"_P, {"e3"_P, "e4"_P} }, + {Piece::White, "f2"_P, {"f3"_P, "f4"_P} }, + {Piece::White, "g2"_P, {"g3"_P, "g4"_P} }, + {Piece::White, "h2"_P, {"h3"_P, "h4"_P} }, + {Piece::White, "a1"_P}, + {Piece::White, "b1"_P, {"a3"_P, "c3"_P} }, + {Piece::White, "c1"_P}, + {Piece::White, "d1"_P}, + {Piece::White, "e1"_P}, + {Piece::White, "f1"_P}, + {Piece::White, "g1"_P, {"f3"_P, "h3"_P} }, + {Piece::White, "h1"_P}, + } + , board {{ + &whites[ 8], &whites[ 9], &whites[10], &whites[11], &whites[12], &whites[13], &whites[14], &whites[15], + &whites[ 0], &whites[ 1], &whites[ 2], &whites[ 3], &whites[ 4], &whites[ 5], &whites[ 6], &whites[ 7], + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, + &blacks[ 0], &blacks[ 1], &blacks[ 2], &blacks[ 3], &blacks[ 4], &blacks[ 5], &blacks[ 6], &blacks[ 7], + &blacks[ 8], &blacks[ 9], &blacks[10], &blacks[11], &blacks[12], &blacks[13], &blacks[14], &blacks[15], + }} +{} + +Chessboard::Chessboard() + : m_state(new State()) +{ + setGrammar(); +} + +Chessboard::~Chessboard() = default; + +void Chessboard::setPrompt(const std::string& prompt) { + m_prompt = prompt; + setGrammar(); +} + +void Chessboard::setGrammar() { + m_grammar.clear(); + std::string result; + if (m_prompt.empty()) { + result += "move ::= \" \" ((piece | frompos) \" \" \"to \"?)? topos\n"; + //result += "move ::= \" \" frompos \" \" \"to \"? topos\n"; + } + else { + // result += "move ::= prompt \" \" ((piece | frompos) \" \" \"to \"?)? topos\n" + result += "move ::= prompt \" \" frompos \" \" \"to \"? topos\n" + "prompt ::= \" " + m_prompt + "\"\n"; + } + + std::set pieceTypes; + std::set from_pos; + std::set to_pos; + auto& pieces = m_moveCounter % 2 ? m_state->blacks : m_state->whites; + std::set flags; + for (auto& p : pieces) { + if (p.allowed().empty()) continue; + bool addPiece = false; + if (!m_inCheck || p.type() == Piece::King) { + to_pos.insert(p.allowed().begin(), p.allowed().end()); + addPiece = !p.allowed().empty(); + } + else { + for (auto move : p.allowed()) { + if (m_allowedInCheck.count(move)) { + to_pos.insert(move); + addPiece = true; + } + } + } + if (addPiece) { + pieceTypes.insert(p.type()); + from_pos.insert(p.pos()); + } + } + if (pieceTypes.empty()) return; + + result += "piece ::= ("; + for (auto& p : pieceTypes) result += " \"" + std::string(pieceNames[p]) + "\" |"; + result.pop_back(); + result += ")\n\n"; + + result += "frompos ::= ("; + for (auto& p : from_pos) result += " \"" + std::string(positions[p]) + "\" |"; + result.pop_back(); + result += ")\n"; + + result += "topos ::= ("; + for (auto& p : to_pos) result += " \"" + std::string(positions[p]) + "\" |"; + result.pop_back(); + result += ")\n"; + + m_grammar = std::move(result); +} + +std::string Chessboard::stringifyBoard() { + std::string result; + result.reserve(16 + 2 * 64 + 16); + for (char rank = 'a'; rank <= 'h'; ++rank) { + result.push_back(rank); + result.push_back(' '); + } + result.back() = '\n'; + for (int i = 7; i >= 0; --i) { + for (int j = 0; j < 8; ++j) { + auto p = m_state->board[i * 8 + j]; + if (p) result.push_back(p->initial()); + else result.push_back((i + j) % 2 ? '.' : '*'); + result.push_back(' '); + } + result.push_back('0' + i + 1); + result.push_back('\n'); + } + return result; +} + +std::string Chessboard::process(const std::string& command) { + const auto t_start = std::chrono::high_resolution_clock::now(); + auto color = Piece::Colors(m_moveCounter % 2); + Piece* piece = nullptr; + auto pos_to = INVALID_POS; + if (!parseCommand(command, piece, pos_to)) return ""; + + auto pos_from = piece->pos(); + + if (!move(*piece, pos_to)) return ""; + + flagUpdates(pos_from, pos_to); + + detectChecks(); + + auto& enemyPieces = color ? m_state->whites : m_state->blacks; + for (auto& p : enemyPieces) p.reinit(*m_state); // only enemy moves needed next + + std::string result = {positions[pos_from][R], positions[pos_from][F], '-', positions[pos_to][R], positions[pos_to][F]}; + ++m_moveCounter; + setGrammar(); + const auto t_end = std::chrono::high_resolution_clock::now(); + auto t_ms = std::chrono::duration_cast(t_end - t_start).count(); + fprintf(stdout, "%s: Move '%s%s%s', (t = %d ms)\n", __func__, "\033[1m", result.data(), "\033[0m", (int) t_ms); + if (m_grammar.empty()) result.push_back('#'); + return result; +} + +bool Chessboard::parseCommand(const std::string& command, Piece*& piece, char& pos_to) { + auto color = Piece::Colors(m_moveCounter % 2); + fprintf(stdout, "%s: Command to %s: '%s%.*s%s'\n", __func__, (color ? "Black" : "White"), "\033[1m", int(command.size()), command.data(), "\033[0m"); + + if (command.empty()) return false; + auto tokens = split(command, ' '); + auto pos_from = INVALID_POS; + auto type = Piece::Types::NUM_PIECES; + if (tokens.size() == 1) { + type = Piece::Types::Pawn; + pos_to = strToPos(tokens.front()); + } + else { + pos_from = strToPos(tokens.front()); + if (pos_from == INVALID_POS) type = Piece::Types(strToType(tokens.front())); + pos_to = strToPos(tokens.back()); + } + if (pos_to == INVALID_POS) return false; + if (pos_from == INVALID_POS) { + if (type == Piece::Types::NUM_PIECES) return false; + auto& pieces = color ? m_state->blacks : m_state->whites; + for (auto& p : pieces) { + if (p.type() == type && p.canReach(pos_to)) { + pos_from = p.pos(); + break; + } + } + } + if (pos_from == INVALID_POS) return false; + if (m_state->board[pos_from] == nullptr) return false; + piece = m_state->board[pos_from]; + if (piece->color() != color) return false; + return true; +} + +void Chessboard::flagUpdates(char pos_from, char pos_to) { + auto color = Piece::Colors(m_moveCounter % 2); + auto& enemyPieces = color ? m_state->whites : m_state->blacks; + auto& ownPieces = color ? m_state->blacks : m_state->whites; + for (auto& p : enemyPieces) { + if (p.movePattern(pos_to) || p.movePattern(pos_from)) { + updatePins(p); + p.invalidate(); + } + } + + for (auto& p : ownPieces) { + if (p.movePattern(pos_to) || p.movePattern(pos_from)) { + updatePins(p); + p.invalidate(); + } + } +} + +void Chessboard::updatePins(Piece& piece) { + if (piece.type() == Piece::Pawn || piece.type() == Piece::Knight) return; + auto& enemyPieces = piece.color() ? m_state->whites : m_state->blacks; + auto& enemyPins = piece.color() ? m_state->whitePins : m_state->blackPins; + auto& king = enemyPieces.k; + auto it = std::find_if(enemyPins.begin(), enemyPins.end(), [&] (const Pin& pin) { return pin.pinner == &piece; }); + if (it != enemyPins.end()) { + it->pinned->invalidate(); + enemyPins.erase(it); + } + if (piece.movePattern(king.pos())) { + auto to = positions[king.pos()]; + auto from = piece.coord(); + Direction d = normalize({char(to[R] - from[R]), char(to[F] - from[F])}); + + auto reached = traverse(piece.pos(), d, Find(m_state->board)); + auto foundPiece = m_state->board[reached]; + if (&king == foundPiece) { + // check + king.invalidate(); + } + else if (foundPiece && foundPiece->color() != piece.color()) { + reached = traverse(reached, d, Find(m_state->board)); + if (&king == m_state->board[reached]) { + enemyPins.push_back({d, &piece, foundPiece}); + foundPiece->invalidate(); + } + } + } +} + +void Chessboard::detectChecks() { + auto color = Piece::Colors(m_moveCounter % 2); + auto& enemyPieces = color ? m_state->whites : m_state->blacks; + auto& ownPieces = color ? m_state->blacks : m_state->whites; + auto& king = enemyPieces.k; + auto& pawnAttackLeft = color ? SW : NW; + auto& pawnAttackRight = color ? SE : NE; + for (auto& p : ownPieces) { + if (!p.movePattern(king.pos())) continue; + auto to = positions[king.pos()]; + auto from = p.coord(); + + if (p.type() == Piece::Knight) { + if (!m_inCheck) { + m_allowedInCheck = { p.pos() }; + } + else { + m_allowedInCheck.clear(); + } + m_inCheck = true; + } + else if (p.type() == Piece::Pawn) { + Direction d {char(to[R] - from[R]), char(to[F] - from[F])}; + if (d == pawnAttackLeft || d == pawnAttackRight) { + if (!m_inCheck) { + m_allowedInCheck = { p.pos() }; + } + else { + m_allowedInCheck.clear(); + } + m_inCheck = true; + } + } + else { + Direction d = normalize({char(to[R] - from[R]), char(to[F] - from[F])}); + std::set tmp; + auto pos = traverse(p.pos(), d, Add(m_state->board, tmp, king.color())); + if (pos == king.pos()) { + if (!m_inCheck) { + m_allowedInCheck = std::move(tmp); + } + else { + m_allowedInCheck.clear(); + } + m_inCheck = true; + } + } + } +} + +bool Chessboard::move(Piece& piece, char pos_to) { + auto& allowed = piece.allowed(); + + if (allowed.count(pos_to) == 0 || (m_inCheck && piece.type() != Piece::King && m_allowedInCheck.count(pos_to) == 0)) return false; + if (m_state->board[pos_to] && m_state->board[pos_to]->color() == piece.color()) return false; + if (m_state->board[pos_to]) m_state->board[pos_to]->take(); + m_state->board[piece.pos()] = nullptr; + m_state->board[pos_to] = &piece; + piece.setPos(pos_to); + + m_inCheck = false; + m_allowedInCheck.clear(); + + return true; +} diff --git a/examples/wchess/libwchess/Chessboard.h b/examples/wchess/libwchess/Chessboard.h new file mode 100644 index 00000000000..588546addcd --- /dev/null +++ b/examples/wchess/libwchess/Chessboard.h @@ -0,0 +1,33 @@ +#pragma once +#include +#include +#include + +// just basic validation +// fixme: missing en passant, castling, promotion, etc. +struct State; +class Piece; +class Chessboard { +public: + Chessboard(); + ~Chessboard(); + std::string process(const std::string& command); + std::string stringifyBoard(); + const std::string& grammar() { return m_grammar; } + const std::string& prompt() { return m_prompt; } + void setPrompt(const std::string& prompt); +private: + bool parseCommand(const std::string& command, Piece*& piece, char& pos_to); + bool move(Piece& piece, char pos); + void flagUpdates(char pos_from, char pos_to); + void updatePins(Piece& piece); + void detectChecks(); + void setGrammar(); + + std::unique_ptr m_state; + std::set m_allowedInCheck; + bool m_inCheck = false; + int m_moveCounter = 0; + std::string m_grammar; + std::string m_prompt; +}; diff --git a/examples/wchess/libwchess/WChess.cpp b/examples/wchess/libwchess/WChess.cpp new file mode 100644 index 00000000000..be415a98bb0 --- /dev/null +++ b/examples/wchess/libwchess/WChess.cpp @@ -0,0 +1,183 @@ +#include "WChess.h" +#include "Chessboard.h" +#include "grammar-parser.h" +#include "common.h" +#include + +WChess::WChess(whisper_context * ctx, + const whisper_full_params & wparams, + callbacks cb, + settings s) + : m_ctx(ctx) + , m_wparams(wparams) + , m_cb(cb) + , m_settings(s) + , m_board(new Chessboard()) +{} + +WChess::~WChess() = default; + +void WChess::set_move(const std::string& moves, float prob) const { + if (m_cb.set_move) (*m_cb.set_move)(moves, prob); +} + +bool WChess::get_audio(std::vector& pcmf32) const { + if (m_cb.get_audio) return (*m_cb.get_audio)(pcmf32); + return false; +} + +std::string WChess::stringify_board() const { + return m_board->stringifyBoard(); +} + +void WChess::run() { + bool have_prompt = true; + bool ask_prompt = !have_prompt; + + float logprob_min = 0.0f; + + float logprob_sum = 0.0f; + + int n_tokens = 0; + + std::vector pcmf32_cur; + std::vector pcmf32_prompt; + + const std::string k_prompt = have_prompt ? "" : "root to d4, f3"; + int64_t t_ms = 0; + + if (ask_prompt) { + fprintf(stdout, "\n"); + fprintf(stdout, "%s: Say the following phrase: '%s%s%s'\n", __func__, "\033[1m", k_prompt.c_str(), "\033[0m"); + fprintf(stdout, "\n"); + + ask_prompt = false; + } + while (get_audio(pcmf32_cur)) { + if (!pcmf32_cur.empty()) { + // fprintf(stdout, "%s: Processing ...\n", __func__); + + if (!have_prompt) { + const auto txt = ::trim(transcribe(pcmf32_cur, logprob_min, logprob_sum, n_tokens, t_ms)); + + fprintf(stdout, "%s: Heard '%s%s%s', (t = %d ms)\n", __func__, "\033[1m", txt.c_str(), "\033[0m", (int) t_ms); + + const float sim = similarity(txt, k_prompt); + + if (txt.length() < 0.8*k_prompt.length() || txt.length() > 1.2*k_prompt.length() || sim < 0.8f) { + fprintf(stdout, "%s: WARNING: prompt not recognized, try again\n", __func__); + ask_prompt = true; + } else { + fprintf(stdout, "\n"); + fprintf(stdout, "%s: The prompt has been recognized!\n", __func__); + fprintf(stdout, "%s: Waiting for voice commands ...\n", __func__); + fprintf(stdout, "\n"); + + // save the audio for the prompt + pcmf32_prompt = pcmf32_cur; + have_prompt = true; + m_board->setPrompt(k_prompt); + } + } else { + if (!pcmf32_prompt.empty()) pcmf32_cur.insert(pcmf32_cur.begin(), pcmf32_prompt.begin(), pcmf32_prompt.end()); + constexpr size_t MIN_SIZE = 1.2 * WHISPER_SAMPLE_RATE; + if (MIN_SIZE > pcmf32_cur.size()) pcmf32_cur.insert(pcmf32_cur.begin(), MIN_SIZE - pcmf32_cur.size(), 0.0f); + + // fprintf(stdout, "%s: grammar rules:\n'%s'\n", __func__, m_board->grammar().c_str()); + + auto grammar_parsed = grammar_parser::parse(m_board->grammar().c_str()); + auto grammar_rules = grammar_parsed.c_rules(); + + m_wparams.grammar_rules = grammar_rules.data(); + m_wparams.n_grammar_rules = grammar_rules.size(); + + m_wparams.i_start_rule = grammar_parsed.symbol_ids.at("move"); + auto txt = ::trim(transcribe(pcmf32_cur, logprob_min, logprob_sum, n_tokens, t_ms)); + + const float p = 100.0f * std::exp(logprob_min); + + fprintf(stdout, "%s: heard '%s'\n", __func__, txt.c_str()); + + // find the prompt in the text + float best_sim = 0.0f; + size_t best_len = 0; + for (int n = 0.8*k_prompt.size(); n <= 1.2*k_prompt.size(); ++n) { + const auto prompt = txt.substr(0, n); + + const float sim = similarity(prompt, k_prompt); + + //fprintf(stderr, "%s: prompt = '%s', sim = %f\n", __func__, prompt.c_str(), sim); + + if (sim > best_sim) { + best_sim = sim; + best_len = n; + } + } + + fprintf(stdout, "%s: DEBUG: txt = '%s', prob = %.2f%%\n", __func__, txt.c_str(), p); + std::string command = ::trim(txt.substr(best_len)); + + fprintf(stdout, "%s: Command '%s%s%s', (t = %d ms)\n", __func__, "\033[1m", command.c_str(), "\033[0m", (int) t_ms); + fprintf(stdout, "\n"); + + if (!command.empty()) { + set_move(m_board->process(command), p); + } + if (m_board->grammar().empty()) { + fprintf(stdout, "%s: No more moves possible\n", __func__); + break; + } + } + } + + if (ask_prompt) { + fprintf(stdout, "\n"); + fprintf(stdout, "%s: Say the following phrase: '%s%s%s'\n", __func__, "\033[1m", k_prompt.c_str(), "\033[0m"); + fprintf(stdout, "\n"); + + ask_prompt = false; + } + } +} + +std::string WChess::transcribe( + const std::vector & pcmf32, + float & logprob_min, + float & logprob_sum, + int & n_tokens, + int64_t & t_ms) { + const auto t_start = std::chrono::high_resolution_clock::now(); + + logprob_min = 0.0f; + logprob_sum = 0.0f; + n_tokens = 0; + t_ms = 0; + + if (whisper_full(m_ctx, m_wparams, pcmf32.data(), pcmf32.size()) != 0) { + return {}; + } + + std::string result; + + const int n_segments = whisper_full_n_segments(m_ctx); + for (int i = 0; i < n_segments; ++i) { + const char * text = whisper_full_get_segment_text(m_ctx, i); + + result += text; + + const int n = whisper_full_n_tokens(m_ctx, i); + for (int j = 0; j < n; ++j) { + const auto token = whisper_full_get_token_data(m_ctx, i, j); + + if(token.plog > 0.0f) return {}; + logprob_min = std::min(logprob_min, token.plog); + logprob_sum += token.plog; + ++n_tokens; + } + } + + const auto t_end = std::chrono::high_resolution_clock::now(); + t_ms = std::chrono::duration_cast(t_end - t_start).count(); + + return result; +} diff --git a/examples/wchess/libwchess/WChess.h b/examples/wchess/libwchess/WChess.h new file mode 100644 index 00000000000..b606b2b6cb9 --- /dev/null +++ b/examples/wchess/libwchess/WChess.h @@ -0,0 +1,55 @@ +#pragma once +#include "whisper.h" +#include +#include +#include + +class Chessboard; + +class WChess { +public: + using CheckRunningCb = bool (*)(); + using GetAudioCb = bool (*)(std::vector &); + using SetMovesCb = void (*)(const std::string &, float); + using ClearAudioCb = void (*)(); + + struct callbacks { + GetAudioCb get_audio = nullptr; + SetMovesCb set_move = nullptr; + }; + + struct settings { + int32_t vad_ms = 2000; + int32_t prompt_ms = 5000; + int32_t command_ms = 4000; + float vad_thold = 0.2f; + float freq_thold = 100.0f; + bool print_energy = false; + }; + + WChess( + whisper_context * ctx, + const whisper_full_params & wparams, + callbacks cb, + settings s + ); + ~WChess(); + + void run(); + std::string stringify_board() const; +private: + bool get_audio(std::vector& pcmf32) const; + void set_move(const std::string& moves, float prob) const; + std::string transcribe( + const std::vector & pcmf32, + float & logprob_min, + float & logprob_sum, + int & n_tokens, + int64_t & t_ms); + + whisper_context * m_ctx; + whisper_full_params m_wparams; + const callbacks m_cb; + const settings m_settings; + std::unique_ptr m_board; +}; diff --git a/examples/wchess/libwchess/test-chessboard.cpp b/examples/wchess/libwchess/test-chessboard.cpp new file mode 100644 index 00000000000..dc2ab3b152d --- /dev/null +++ b/examples/wchess/libwchess/test-chessboard.cpp @@ -0,0 +1,106 @@ +#include "Chessboard.h" + +#define ASSERT(x) \ + do { \ + if (!(x)) { \ + fprintf(stderr, "ASSERT: %s:%d: %s\n", __FILE__, __LINE__, #x); \ + fflush(stderr); \ + exit(1); \ + } \ + } while (0) + + +int main() { + { + Chessboard chess; + + ASSERT(chess.process("pawn to d4") == "d2-d4"); + ASSERT(chess.process("e5") == "e7-e5"); + ASSERT(chess.process("c1 h6") == "c1-h6"); + ASSERT(chess.process("queen h4") == "d8-h4"); + ASSERT(chess.process("bishop to g5") == "h6-g5"); + ASSERT(chess.process("bishop to b4") == "f8-b4"); + ASSERT(chess.process("c4") == ""); + ASSERT(chess.process("knight c3") == "b1-c3"); + ASSERT(chess.process("knight c6") == "b8-c6"); + ASSERT(chess.process("f3") == ""); + } + + { + Chessboard chess; + + ASSERT(chess.process("d4") == "d2-d4"); + ASSERT(chess.process("e5") == "e7-e5"); + ASSERT(chess.process("e4") == "e2-e4"); + ASSERT(chess.process("queen h4") == "d8-h4"); + ASSERT(chess.process("queen h5") == "d1-h5"); + ASSERT(chess.process("f5") == ""); + ASSERT(chess.process("g6") == "g7-g6"); + ASSERT(chess.process("knight e2") == "g1-e2"); + ASSERT(chess.process("f5") == "f7-f5"); + ASSERT(chess.process("knight g3") == "e2-g3"); + ASSERT(chess.process("g5") == ""); + ASSERT(chess.process("king e7") == "e8-e7"); + ASSERT(chess.process("f4") == "f2-f4"); + ASSERT(chess.process("g5") == "g6-g5"); + } + + { + Chessboard chess; + + ASSERT(chess.process("e4") == "e2-e4"); + ASSERT(chess.process("c5") == "c7-c5"); + ASSERT(chess.process("e5") == "e4-e5"); + ASSERT(chess.process("c4") == "c5-c4"); + ASSERT(chess.process("e6") == "e5-e6"); + ASSERT(chess.process("c3") == "c4-c3"); + ASSERT(chess.process("e7") == ""); + ASSERT(chess.process("f7") == "e6-f7"); + ASSERT(chess.process("d2") == ""); + ASSERT(chess.process("king to f7") == "e8-f7"); + ASSERT(chess.process("f4") == "f2-f4"); + ASSERT(chess.process("d2") == "c3-d2"); + ASSERT(chess.process("f5") == ""); + ASSERT(chess.process("king to e2") == "e1-e2"); + ASSERT(chess.process("king to g6") == "f7-g6"); + ASSERT(chess.process("f5") == "f4-f5"); + ASSERT(chess.process("e6") == ""); + ASSERT(chess.process("king to h5") == "g6-h5"); + ASSERT(chess.process("g4") == "g2-g4"); + ASSERT(chess.process("king to g5") == "h5-g5"); + ASSERT(chess.process("h4") == "h2-h4"); + ASSERT(chess.process("king to h5") == ""); + ASSERT(chess.process("king to g6") == ""); + ASSERT(chess.process("king to h6") == "g5-h6"); + ASSERT(chess.process("bishop to d2") == "c1-d2"); + ASSERT(chess.process("king to g5") == ""); + ASSERT(chess.process("g5") == "g7-g5"); + } + + { + Chessboard chess; + ASSERT(chess.process("f4") == "f2-f4"); + ASSERT(chess.process("e5") == "e7-e5"); + ASSERT(chess.process("g4") == "g2-g4"); + ASSERT(chess.process("queen to h4") == "d8-h4#"); + ASSERT(chess.process("knight f3") == ""); + ASSERT(chess.grammar().empty()); + } + + { + Chessboard chess; + ASSERT(chess.process("knight c3") == "b1-c3"); + ASSERT(chess.process("knight c6") == "b8-c6"); + ASSERT(chess.process("knight b5") == "c3-b5"); + ASSERT(chess.process("knight f6") == "g8-f6"); + ASSERT(chess.process("knight d6") == "b5-d6"); + ASSERT(chess.process("knight d4") == ""); + ASSERT(chess.process("d6") == "c7-d6"); + ASSERT(chess.process("e4") == "e2-e4"); + ASSERT(chess.process("knight d4") == "c6-d4"); + ASSERT(chess.process("d3") == "d2-d3"); + ASSERT(chess.process("knight e4") == "f6-e4"); + ASSERT(chess.process("king to e2") == ""); + ASSERT(chess.process("king to d2") == ""); + } +} \ No newline at end of file diff --git a/examples/wchess/wchess.cmd/CMakeLists.txt b/examples/wchess/wchess.cmd/CMakeLists.txt new file mode 100644 index 00000000000..a976d93fb4f --- /dev/null +++ b/examples/wchess/wchess.cmd/CMakeLists.txt @@ -0,0 +1,8 @@ +if (WHISPER_SDL2) + set(TARGET wchess) + add_executable(${TARGET} wchess.cmd.cpp) + + include(DefaultTargetOptions) + + target_link_libraries(${TARGET} PRIVATE libwchess common-sdl ${CMAKE_THREAD_LIBS_INIT}) +endif () \ No newline at end of file diff --git a/examples/wchess/wchess.cmd/wchess.cmd.cpp b/examples/wchess/wchess.cmd/wchess.cmd.cpp new file mode 100644 index 00000000000..1a8c2092a24 --- /dev/null +++ b/examples/wchess/wchess.cmd/wchess.cmd.cpp @@ -0,0 +1,243 @@ +// Command line voice assisted chess +// +// Speak chess move commands to the microphone. +// The moves will translated to chessboard positions. +// +// + +#include "WChess.h" +#include "common-sdl.h" +#include + +#include +#include + +// command-line parameters +struct whisper_params { + int32_t n_threads = std::min(4, (int32_t) std::thread::hardware_concurrency()); + int32_t prompt_ms = 5000; + int32_t command_ms = 8000; + int32_t capture_id = -1; + int32_t max_tokens = 32; + int32_t audio_ctx = 0; + + float vad_thold = 0.6f; + float freq_thold = 100.0f; + + float grammar_penalty = 100.0f; + + bool speed_up = false; + bool translate = false; + bool print_special = false; + bool print_energy = false; + bool no_timestamps = true; + bool use_gpu = true; + + std::string language = "en"; + std::string model = "models/ggml-base.en.bin"; + std::string fname_out; + std::string commands; + std::string prompt; + std::string context; + std::string grammar; +}; + +void whisper_print_usage(int /*argc*/, char ** argv, const whisper_params & params) { + fprintf(stderr, "\n"); + fprintf(stderr, "usage: %s [options]\n", argv[0]); + fprintf(stderr, "\n"); + fprintf(stderr, "options:\n"); + fprintf(stderr, " -h, --help [default] show this help message and exit\n"); + fprintf(stderr, " -t N, --threads N [%-7d] number of threads to use during computation\n", params.n_threads); + fprintf(stderr, " -pms N, --prompt-ms N [%-7d] prompt duration in milliseconds\n", params.prompt_ms); + fprintf(stderr, " -cms N, --command-ms N [%-7d] command duration in milliseconds\n", params.command_ms); + fprintf(stderr, " -c ID, --capture ID [%-7d] capture device ID\n", params.capture_id); + fprintf(stderr, " -mt N, --max-tokens N [%-7d] maximum number of tokens per audio chunk\n", params.max_tokens); + fprintf(stderr, " -ac N, --audio-ctx N [%-7d] audio context size (0 - all)\n", params.audio_ctx); + fprintf(stderr, " -vth N, --vad-thold N [%-7.2f] voice activity detection threshold\n", params.vad_thold); + fprintf(stderr, " -fth N, --freq-thold N [%-7.2f] high-pass frequency cutoff\n", params.freq_thold); + fprintf(stderr, " -su, --speed-up [%-7s] speed up audio by x2 (reduced accuracy)\n", params.speed_up ? "true" : "false"); + fprintf(stderr, " -tr, --translate [%-7s] translate from source language to english\n", params.translate ? "true" : "false"); + fprintf(stderr, " -ps, --print-special [%-7s] print special tokens\n", params.print_special ? "true" : "false"); + fprintf(stderr, " -pe, --print-energy [%-7s] print sound energy (for debugging)\n", params.print_energy ? "true" : "false"); + fprintf(stderr, " -ng, --no-gpu [%-7s] disable GPU\n", params.use_gpu ? "false" : "true"); + fprintf(stderr, " -l LANG, --language LANG [%-7s] spoken language\n", params.language.c_str()); + fprintf(stderr, " -m FNAME, --model FNAME [%-7s] model path\n", params.model.c_str()); + fprintf(stderr, " -f FNAME, --file FNAME [%-7s] text output file name\n", params.fname_out.c_str()); + fprintf(stderr, " -cmd FNAME, --commands FNAME [%-7s] text file with allowed commands\n", params.commands.c_str()); + fprintf(stderr, " -p, --prompt [%-7s] the required activation prompt\n", params.prompt.c_str()); + fprintf(stderr, " -ctx, --context [%-7s] sample text to help the transcription\n", params.context.c_str()); + fprintf(stderr, " --grammar-penalty N [%-7.1f] scales down logits of nongrammar tokens\n", params.grammar_penalty); + fprintf(stderr, "\n"); +} + +bool whisper_params_parse(int argc, char ** argv, whisper_params & params) { + for (int i = 1; i < argc; i++) { + std::string arg = argv[i]; + + if (arg == "-h" || arg == "--help") { + whisper_print_usage(argc, argv, params); + exit(0); + } + else if (arg == "-t" || arg == "--threads") { params.n_threads = std::stoi(argv[++i]); } + else if (arg == "-pms" || arg == "--prompt-ms") { params.prompt_ms = std::stoi(argv[++i]); } + else if (arg == "-cms" || arg == "--command-ms") { params.command_ms = std::stoi(argv[++i]); } + else if (arg == "-c" || arg == "--capture") { params.capture_id = std::stoi(argv[++i]); } + else if (arg == "-mt" || arg == "--max-tokens") { params.max_tokens = std::stoi(argv[++i]); } + else if (arg == "-ac" || arg == "--audio-ctx") { params.audio_ctx = std::stoi(argv[++i]); } + else if (arg == "-vth" || arg == "--vad-thold") { params.vad_thold = std::stof(argv[++i]); } + else if (arg == "-fth" || arg == "--freq-thold") { params.freq_thold = std::stof(argv[++i]); } + else if (arg == "-su" || arg == "--speed-up") { params.speed_up = true; } + else if (arg == "-tr" || arg == "--translate") { params.translate = true; } + else if (arg == "-ps" || arg == "--print-special") { params.print_special = true; } + else if (arg == "-pe" || arg == "--print-energy") { params.print_energy = true; } + else if (arg == "-ng" || arg == "--no-gpu") { params.use_gpu = false; } + else if (arg == "-l" || arg == "--language") { params.language = argv[++i]; } + else if (arg == "-m" || arg == "--model") { params.model = argv[++i]; } + else if (arg == "-f" || arg == "--file") { params.fname_out = argv[++i]; } + else if (arg == "-cmd" || arg == "--commands") { params.commands = argv[++i]; } + else if (arg == "-p" || arg == "--prompt") { params.prompt = argv[++i]; } + else if (arg == "-ctx" || arg == "--context") { params.context = argv[++i]; } + else if ( arg == "--grammar-penalty") { params.grammar_penalty = std::stof(argv[++i]); } + else { + fprintf(stderr, "error: unknown argument: %s\n", arg.c_str()); + whisper_print_usage(argc, argv, params); + exit(0); + } + } + + return true; +} + +std::unique_ptr g_wchess; +int g_moveCount = 0; +void set_move(const std::string & move, float) { + if (!move.empty()) { + g_moveCount++; + fprintf(stdout, "Move: %s\n\n", move.c_str()); + } + else fprintf(stdout, "Move rejected\n\n"); + fprintf(stdout, "%s\n", g_wchess->stringify_board().c_str()); + fprintf(stdout, "%s\n", g_moveCount ? "White's turn" : "Black's turn"); +} + +audio_async g_audio(30*1000); +bool g_listening = false; +std::vector g_pcmf32; + +bool read_input() { + std::string input; + while (true) { + fprintf(stdout, "[(l)isten/(p)ause/(q)uit]: "); + std::cin >> input; + fprintf(stdout, "\n"); + if (input[0] == 'q') { + fprintf(stdout, "Quitting\n"); + return false; + } + if (input[0] == 'l') { + if (!g_listening) { + fprintf(stdout, "Listening\n"); + g_listening = true; + g_pcmf32.clear(); + g_audio.resume(); + g_audio.clear(); + } + else fprintf(stdout, "Still listening\n"); + return true; + } + else { + if (g_listening) { + g_listening = false; + g_audio.get(0, g_pcmf32); + g_audio.pause(); + fprintf(stdout, "Processing\n"); + } + else fprintf(stdout, "Not listening\n"); + return true; + } + } + return true; +} + +bool get_audio(std::vector & pcmf32_cur) { + if (!read_input()) return false; + if (!g_pcmf32.empty()) pcmf32_cur = std::move(g_pcmf32); + else pcmf32_cur.clear(); + return true; +} + +int main(int argc, char ** argv) { + whisper_params params; + + if (whisper_params_parse(argc, argv, params) == false) { + return 1; + } + + if (whisper_lang_id(params.language.c_str()) == -1) { + fprintf(stderr, "error: unknown language '%s'\n", params.language.c_str()); + whisper_print_usage(argc, argv, params); + exit(0); + } + + // whisper init + + struct whisper_context_params cparams; + cparams.use_gpu = params.use_gpu; + + struct whisper_context * ctx = whisper_init_from_file_with_params(params.model.c_str(), cparams); + + // init audio + + if (!g_audio.init(params.capture_id, WHISPER_SAMPLE_RATE)) { + fprintf(stderr, "%s: audio.init() failed!\n", __func__); + return 1; + } + + struct whisper_full_params wparams = whisper_full_default_params(whisper_sampling_strategy::WHISPER_SAMPLING_GREEDY); + wparams.offset_ms = 0; + wparams.translate = false; + wparams.no_context = true; + wparams.single_segment = true; + wparams.print_realtime = false; + wparams.print_progress = false; + wparams.print_timestamps = true; + wparams.print_special = false; + wparams.no_timestamps = true; + + wparams.max_tokens = 32; + wparams.audio_ctx = 768; // partial encoder context for better performance + + wparams.temperature = 0.0f; + wparams.temperature_inc = 2.0f; + wparams.greedy.best_of = 1; + + wparams.beam_search.beam_size = 1; + + wparams.language = "en"; + + wparams.grammar_penalty = 100.0; + + wparams.initial_prompt = params.context.data(); + + WChess::callbacks cb; + cb.get_audio = get_audio; + cb.set_move = set_move; + + WChess::settings s; + s.vad_ms = 2000; + s.prompt_ms = params.prompt_ms; + s.command_ms = params.command_ms; + s.vad_thold = params.vad_thold; + s.freq_thold = params.freq_thold; + s.print_energy = params.print_energy; + + g_wchess.reset(new WChess(ctx, wparams, cb, s)); + set_move("start", 0); + g_wchess->run(); + + whisper_print_timings(ctx); + whisper_free(ctx); + + return 0; +} diff --git a/examples/wchess/wchess.wasm/CMakeLists.txt b/examples/wchess/wchess.wasm/CMakeLists.txt new file mode 100644 index 00000000000..588a50e0a1e --- /dev/null +++ b/examples/wchess/wchess.wasm/CMakeLists.txt @@ -0,0 +1,51 @@ +set(TARGET wchess.wasm) + +add_executable(${TARGET} + wchess.wasm.cpp + ) + +include(DefaultTargetOptions) + +target_link_libraries(${TARGET} PRIVATE + common + libwchess + ) + +unset(EXTRA_FLAGS) + +if (WHISPER_WASM_SINGLE_FILE) + set(EXTRA_FLAGS "-s SINGLE_FILE=1") + message(STATUS "Embedding WASM inside chess.js") + + add_custom_command( + TARGET ${TARGET} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy + ${CMAKE_BINARY_DIR}/bin/${TARGET}.js + ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/js/chess.js + ) +endif() + +set_target_properties(${TARGET} PROPERTIES LINK_FLAGS " \ + --bind \ + -s USE_PTHREADS=1 \ + -s PTHREAD_POOL_SIZE=8 \ + -s INITIAL_MEMORY=1024MB \ + -s TOTAL_MEMORY=1024MB \ + -s FORCE_FILESYSTEM=1 \ + -s EXPORTED_RUNTIME_METHODS=\"['print', 'printErr', 'ccall', 'cwrap']\" \ + ${EXTRA_FLAGS} \ + ") + + +add_custom_command( + TARGET ${TARGET} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory + ${CMAKE_CURRENT_SOURCE_DIR}/chessboardjs-1.0.0 + ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/ + COMMAND ${CMAKE_COMMAND} -E copy + ${CMAKE_CURRENT_SOURCE_DIR}/jquery-3.7.1.min.js + ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/js/ + ) + +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/index-tmpl.html ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/index.html @ONLY) +configure_file(${CMAKE_SOURCE_DIR}/examples/helpers.js ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/js/helpers.js @ONLY) diff --git a/examples/wchess/wchess.wasm/chessboardjs-1.0.0/CHANGELOG.md b/examples/wchess/wchess.wasm/chessboardjs-1.0.0/CHANGELOG.md new file mode 100644 index 00000000000..c0c6b2fe2c9 --- /dev/null +++ b/examples/wchess/wchess.wasm/chessboardjs-1.0.0/CHANGELOG.md @@ -0,0 +1,32 @@ +# chessboard.js Change Log + +All notable changes to this project will be documented in this file. + +## [1.0.0] - 2019-06-11 +- Orientation methods now return current orientation. [Issue #64] +- Drop support for IE8 +- Do not check for `window.JSON` (Error #1004) +- Rename `ChessBoard` to `Chessboard` (`ChessBoard` is still supported, however) +- id query selectors are now supported as the first argument to `Chessboard()` +- Remove Error #1002 +- Format code according to [StandardJS] +- Bump minimum jQuery version to 1.8.3 +- Throttle piece drag functions + +## [0.3.0] - 2013-08-10 +- Added `appearSpeed` animation config property +- Added `onSnapbackEnd` event +- Added `onMoveEnd` event + +## [0.2.0] - 2013-08-05 +- Added `onMouseoverSquare` and `onMouseoutSquare` events +- Added `onSnapEnd` event +- Added square code as CSS class on the squares +- Added [chess.js] integration examples + +## [0.1.0] - 2013-05-21 +- Initial release + +[chess.js]:https://github.com/jhlywa/chess.js +[Issue #64]:https://github.com/oakmac/chessboardjs/issues/64 +[StandardJS]:https://standardjs.com/ diff --git a/examples/wchess/wchess.wasm/chessboardjs-1.0.0/LICENSE.md b/examples/wchess/wchess.wasm/chessboardjs-1.0.0/LICENSE.md new file mode 100644 index 00000000000..20b7d615ca6 --- /dev/null +++ b/examples/wchess/wchess.wasm/chessboardjs-1.0.0/LICENSE.md @@ -0,0 +1,20 @@ +Copyright 2019 Chris Oakman + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/examples/wchess/wchess.wasm/chessboardjs-1.0.0/README.md b/examples/wchess/wchess.wasm/chessboardjs-1.0.0/README.md new file mode 100644 index 00000000000..60c8997e53e --- /dev/null +++ b/examples/wchess/wchess.wasm/chessboardjs-1.0.0/README.md @@ -0,0 +1,82 @@ +# chessboard.js + +chessboard.js is a JavaScript chessboard component. It depends on [jQuery]. + +Please see [chessboardjs.com] for documentation and examples. + +## What is chessboard.js? + +chessboard.js is a JavaScript chessboard component with a flexible "just a +board" API that + +chessboard.js is a standalone JavaScript Chess Board. It is designed to be "just +a board" and expose a powerful API so that it can be used in different ways. +Here's a non-exhaustive list of things you can do with chessboard.js: + +- Use chessboard.js to show game positions alongside your expert commentary. +- Use chessboard.js to have a tactics website where users have to guess the best + move. +- Integrate chessboard.js and [chess.js] with a PGN database and allow people to + search and playback games (see [Example 5000]) +- Build a chess server and have users play their games out using the + chessboard.js board. + +chessboard.js is flexible enough to handle any of these situations with relative +ease. + +## What can chessboard.js **not** do? + +The scope of chessboard.js is limited to "just a board." This is intentional and +makes chessboard.js flexible for handling a multitude of chess-related problems. + +This is a common source of confusion for new users. [remove?] + +Specifically, chessboard.js does not understand anything about how the game of +chess is played: how a knight moves, who's turn is it, is White in check?, etc. + +Fortunately, the powerful [chess.js] library deals with exactly this sort of +problem domain and plays nicely with chessboard.js's flexible API. Some examples +of chessboard.js combined with chess.js: 5000, 5001, 5002 + +Please see the powerful [chess.js] library for an API to deal with these sorts +of questions. + + +This logic is distinct from the logic of the board. Please see the powerful +[chess.js] library for this aspect of your application. + + + +Here is a list of things that chessboard.js is **not**: + +- A chess engine +- A legal move validator +- A PGN parser + +chessboard.js is designed to work well with any of those things, but the idea +behind chessboard.js is that the logic that controls the board should be +independent of those other problems. + +## Docs and Examples + +- Docs - +- Examples - + +## Developer Tools + +```sh +# create a build in the build/ directory +npm run build + +# re-build the website +npm run website +``` + +## License + +[MIT License](LICENSE.md) + +[jQuery]:https://jquery.com/ +[chessboardjs.com]:http://chessboardjs.com +[chess.js]:https://github.com/jhlywa/chess.js +[Example 5000]:http://chessboardjs.com/examples#5000 diff --git a/examples/wchess/wchess.wasm/chessboardjs-1.0.0/css/chessboard-1.0.0.css b/examples/wchess/wchess.wasm/chessboardjs-1.0.0/css/chessboard-1.0.0.css new file mode 100644 index 00000000000..8de95f47023 --- /dev/null +++ b/examples/wchess/wchess.wasm/chessboardjs-1.0.0/css/chessboard-1.0.0.css @@ -0,0 +1,54 @@ +/*! chessboard.js v1.0.0 | (c) 2019 Chris Oakman | MIT License chessboardjs.com/license */ + +.clearfix-7da63 { + clear: both; +} + +.board-b72b1 { + border: 2px solid #404040; + box-sizing: content-box; +} + +.square-55d63 { + float: left; + position: relative; + + /* disable any native browser highlighting */ + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.white-1e1d7 { + background-color: #f0d9b5; + color: #b58863; +} + +.black-3c85d { + background-color: #b58863; + color: #f0d9b5; +} + +.highlight1-32417, .highlight2-9c5d2 { + box-shadow: inset 0 0 3px 3px yellow; +} + +.notation-322f9 { + cursor: default; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 14px; + position: absolute; +} + +.alpha-d2270 { + bottom: 1px; + right: 3px; +} + +.numeric-fc462 { + top: 2px; + left: 2px; +} diff --git a/examples/wchess/wchess.wasm/chessboardjs-1.0.0/css/chessboard-1.0.0.min.css b/examples/wchess/wchess.wasm/chessboardjs-1.0.0/css/chessboard-1.0.0.min.css new file mode 100644 index 00000000000..73f844a827d --- /dev/null +++ b/examples/wchess/wchess.wasm/chessboardjs-1.0.0/css/chessboard-1.0.0.min.css @@ -0,0 +1,2 @@ +/*! chessboard.js v1.0.0 | (c) 2019 Chris Oakman | MIT License chessboardjs.com/license */ +.clearfix-7da63{clear:both}.board-b72b1{border:2px solid #404040;box-sizing:content-box}.square-55d63{float:left;position:relative;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.white-1e1d7{background-color:#f0d9b5;color:#b58863}.black-3c85d{background-color:#b58863;color:#f0d9b5}.highlight1-32417,.highlight2-9c5d2{box-shadow:inset 0 0 3px 3px #ff0}.notation-322f9{cursor:default;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;position:absolute}.alpha-d2270{bottom:1px;right:3px}.numeric-fc462{top:2px;left:2px} \ No newline at end of file diff --git a/examples/wchess/wchess.wasm/chessboardjs-1.0.0/img/chesspieces/wikipedia/bB.png b/examples/wchess/wchess.wasm/chessboardjs-1.0.0/img/chesspieces/wikipedia/bB.png new file mode 100644 index 0000000000000000000000000000000000000000..be3007dd0cdc7662268e103458b30a035da1cebf GIT binary patch literal 1405 zcmV-@1%mpCP);OAg&;29>pXtLFL9VGS_o&_2gy))=`i$DX3vw-9Gh)}RQ7JcwuWkdbRXt|A`xA>c4EW@MX> zj1K`bMy|`TiE&}qk z)5K?tT>s;C;y#C_DqWhf#67pwvT!jB9HSn^b1L&UY}D79cyZ!hpTfBbxfeETrsfTo zDW224pVh+TdWo{)C`Y)3gggu%T4E{j;}FCv7V#rpMSR#zUWI`}!j4;@#o3 zwq!Dyxf++t==OPfWGs*%U_4=RzH(`}WqAcL=rA~|k z8bl;wh$ODZe5|BZM;X(Y;=zPth-6yGt$ruZ0ZgQw$T8d!)h(;@rPja_mvT-ukYl(u zs?GS<5H=0`@|P$NBKsGCfS*QLz?~s(UwK`PycA^t`CY|i6-2asO<*4~(nwcPz*lwH zOyD`#=M@yu_Hk5blorehk!O_$wBqr2etHrF*_N1}&3aa7n1s2Jc$^=wp`pRbX0!Q? zf^xw&*cRJ_c;ARZQo5G7nMF*cQgUo;Oa=wE#Wo>cLTHTVxt7$`)ybx&CfU{1CA+)3 z%OcxgTWk|G@Ht(@yARWfFb5I~K9ahL$MeAh6&Q)t7fM>?%RDUH^ZtS+=Mk>cNx)rI zSjxSui+C~W>_5P$Ysv6ov9F$kz?Yr25HN<*lqbT}EQgl6svu9+Hr%txLo=goZiOOR&&?PZU1ak|MYyb7l^<6GG!O zUsX)rSl|~ve_;JO<3qd|+{5<{VFMxZqVewU3gVr_)x>+TsJ|bZn-3D#ibCe-dTAJ#4RquQl+UL@cWj+ls}qQ@x6~8_nh*F-!cKc!KyX@f7g~ z;u+#k#B;>oh$F-?G&xy9Q+6togUu26I0v6+5W^|NbOJGEvAMyqKU-C93njH_WNeCt*&$TPie{tCooB3#mtf7LOCfFK8;> zLZ3-&4stj7of;6YSj2aO+|}j}oF}i)8Rt-D9IFA9~G_GA#Y&zMRQ2aT?G3jCO<1$YN-Hcm_$1rNmBAD+);Xe%ck8oZgZpVBqMSKWM zVSdJQwJpSL#3qGX(M52%yNJtNVpxVUDcrNQ$ksOa*Z`ku9P1F<4#c_#v44+eu}9=% z=trP|Z1_lNO1_lNO2I1mAaAmM>MWmh600000 LNkvXXu0mjfilMA( literal 0 HcmV?d00001 diff --git a/examples/wchess/wchess.wasm/chessboardjs-1.0.0/img/chesspieces/wikipedia/bK.png b/examples/wchess/wchess.wasm/chessboardjs-1.0.0/img/chesspieces/wikipedia/bK.png new file mode 100644 index 0000000000000000000000000000000000000000..de9880ce6390d6a53464f1b028bff7be0fd0f131 GIT binary patch literal 3009 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 DNo%uI literal 0 HcmV?d00001 diff --git a/examples/wchess/wchess.wasm/chessboardjs-1.0.0/img/chesspieces/wikipedia/bN.png b/examples/wchess/wchess.wasm/chessboardjs-1.0.0/img/chesspieces/wikipedia/bN.png new file mode 100644 index 0000000000000000000000000000000000000000..e31a6d0224819791210e91218fc99b7cd3b848ed GIT binary patch literal 1875 zcmV-Z2dwysP)9wVNxLjD`@*iP6__$6VA8`tL&-X#RW zUP5D_3pg!{fgB^uc5!V9p(tNV|Gp5&;zh}#i3&mRJj2PgnS>Jx@#yvSfh=IOEEuS) zYKSmaaa{!A4~pxQ{4vmn@SiF{sCCuL0>==(p%PfhDZ)^~NW!o{6tLqymZi2z(Zy#! z%lA1-SS~L#j}it2f`CuC5C&X`M>(%N&&tYzef##^$h?u1g{eeo%(*%pyZ{SquvcgUrlKsIIPto}M0{~GzX|xd+VWPeTnQG71|}zqobjzsYw#CySp0~m(fM`;oP82 zZGH9i^^$O+D2*(Lii*-?NRv4b8X6j;u1}sk2@w$yzwz!f78Cxfe2Mkz*MkzBot>J_ zi$Os_*r&8Y@7}!|&wt@nJdE3Qsb2tBLnYGF(?RRS1y|{tH*doH{Jgkk%^Ga`oJRp) zC0tNHd~4?}Qpu;v0H3knLPDTMfpq8zX-;z>M=lnglY`8ICc2#t-6uxHO6 zAgiHI#6#j2ck0wB>5DX*&Di!WX?utl0V`cS{E8JTd}et{CMPGu?c29ucz77%;^MHa zln3!*eJ#vVSXWmkt;&ZE9m4ZIfrFgO7g}uByhWqYh>HnayLL@P=bp6X`BJ~~C=smv@?6opVFtPUR>}+XoJUKZDX=!QL z_9n}8C}!Edak8(JfX7~NQ$BXm?*>+LZc6k$(}ExzT4cYhRPPGX4GiQjkGTO)$&%e&+@8x)lE1V!?_D)&d?pcu*4X z)~#Do`hm1nvvgw`Jbd8K1owWrfWeB(TI_gm7lOb_FRVS3Lv+kw5|&?W)h)RHyN^1- zz2ZxcSyWWy@FcLVMvDOJnI#NYdORYyUnNzhWsnw~X^NdYcVgS$TI`2w2?u3sqz`jP z6?RgFqTCG?y{z8pux3U^hC{%;d-qC}3|d8H(4scW3F-pDUbxfi5PVpenWuQT8-)38?k({PSnyS@n8IqcN|mpqeVyP(DAH=daK^;M zM2CPUPMnb9go6E7Q+|Q%EOH)8Ar>A{U_GH+ep2WU;yo{~cs@;wDg``u?i|(-V*4w+ zi}x_$Fb^qCOlwkLh#!kGNxPYM0lyYFNysbbK%31*1>DcGc&`Z@RWm3BymaXj74U6d z#WQ#q(P1hemLJRY+f}>@xJ}-%a8pY|>ts|GXyH}BPkFLFWJ*fPJ!uJ9z#1OKi;*KT zTnUIp?-ZCHmfe{5c@%IR>->hN(U_K&7V1d9$D@Ex^5Xjms_K1;)2C0k zj|cA9vBM$Y@#DwoUie)e1$>6^55fLVBzWQEu3fukF|m#%9#*S$E-WkzW1I(g6fjn{ z!sj^Rvu3k-^T5DB!QkLvfyH9kLIlLP<|Bd*9XfRA(4j+z4jnq!;y-|v529HMk23%O N002ovPDHLkV1nn}h0*{3 literal 0 HcmV?d00001 diff --git a/examples/wchess/wchess.wasm/chessboardjs-1.0.0/img/chesspieces/wikipedia/bP.png b/examples/wchess/wchess.wasm/chessboardjs-1.0.0/img/chesspieces/wikipedia/bP.png new file mode 100644 index 0000000000000000000000000000000000000000..afa0c9d4459e4d04648ca413ba90690bfdd49a02 GIT binary patch literal 777 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 l`)^ z-n=PRR8&Y=QBY6-yLRn@ZQHiN`t|E!<;sAcp zw%QWGCeI%Jk3K#=;OpxPetv$>x@|x}z;l$ZV;L-qHWmf^#$AssUBz<`-r2+glP6CG z>_T(r&V{(RIEasrhgGXq!P>QJVbi8fuzmY>$jZus-Me?gzJ2>3CnpCEA3h96j~SgBFX6i=n){9A`ir z#HFPO&Xg|-=o{<6x~^WmD%Fi`z_y^^(b3Vi|D)|m85tRE>cCoCTcNtTS_%f25@~5^ zkdl%DD^{$K)@n0n&V)&mCP8Rus6?D)wJ3j&Wzh!LnCJt2q0d8y4oP)jU0A0YnVFgY z5%}M%^~l)wFE;yP_Y&ER2N4kw9-lmLse^4{OB)f}CJerjZ%cfnx1Ga5AJ=dbCQN`S zQ>IA5PM(V}NuxNxC#9lytNSe|u9X$^>eP$cv_ zapFW6GiHo5(Fn$G-M?kKu8Z3v9K5khZP~H~_UzdM`T6;9>eMMHEiHu$7cRi%%a@_9 zt`2V8xPgmpX*-6yjE4^&O1p_CPo7BQSZ{Byw4)dp7?9Z4*C$=a@8ul~?nv(3xdXRu z-GYXO2FWjO>n>ir2xVnuQdcf4EQGy#_d;rFDkLT*!i*U+ASfsZp8#grO1<3N+@D^% zcC8n;R!^Tk1yR)8$w^x<9N^hj52~uF;M}=$P*hYT1)7(a2S<(^k!D<+Z87ld>}=^@ z{6AhtVmZ7qY%M3<7~EyxB-PZ^B#CM?!QhR$efze!dGqFv?4;ia)~!l0yBZrCMHFP` z&Yh5)oGf)Y?5^ytyLH^dbz^vVIK;-r!kRT}aPoi?Cr-fi>(`}>UA}yIt(^`NLcH-L znk~eW9RXcC58P}Q0r4H>Prd9|A72ewX17`RufpI%>V9E(U(Fa!AN|zEPw1(lt$@F; z@GHhYPy@?IR2;DJ?WY!c2HX1rGpm>CXrVf5sSRb+rajbkz2f&{@Gp_Z({Vq(Iu^~i zKp6b}gH?=4!r<>@;qT1gd(Zf)?{3Bqg~7i%!h_FT;$gb>A(hD{n^f|VL_XuGj%cbg zoZ8?kyyUPnW0000+NjA?5IGAHvP4t z54%KWsa~*up#S25g4PyUJ-=B#vU8SfOkVC3u;J1>CCN>xZx>V_Sh(vMyUFU{q&A(T z%a1H=@4r>w>!lpyyYBwP?~T)?JD%6QuQd^xTrI;6v+VaXtCeeiy8Wc6`Rl~%4ZC%_?Pl1m|0#bk z^7R^V?ErQbHRi&f$MXc79yDCv;r+Zw?g6KO{)aj8(mb3yc@;lQyRI|;dChg!RlNKE z2Rz^KOH@NqfTL01M}LIQvs-Etth*oXd|r8f`Q??<_)3?oyAnC+!1AR*FSp!&`>Jlg ze_+iNk^RBzuU~#z^mB@p_ivL;)4i7N+qG`>cNAB`UCr7Rwbo5)mgnY` z%;g)qY$tQOpL?4le&-}t+^m}H?YFCJM3nTCehM|;g7cR zGA+F`RWxmJzeVFD=0;#pu4Yve;?#ZdEzbUm=b{7K&po<#$IEQ?)x!@1Zt*^=Q-AeL z=bfkBqwE`8`967bj@It5w>>Ys`)K2?z~f8Lnb+t}{M^W$ak}{Ubs@{GvO*EfV88O5 ze8_iV_VWU1rp*0s-6soA?ptd4F#E=>dx^Ko-DaPg7dgu!o*9yi7XDzq)Gu?~A%9jq ONWjz8&t;ucLK6U+JX?nV literal 0 HcmV?d00001 diff --git a/examples/wchess/wchess.wasm/chessboardjs-1.0.0/img/chesspieces/wikipedia/wB.png b/examples/wchess/wchess.wasm/chessboardjs-1.0.0/img/chesspieces/wikipedia/wB.png new file mode 100644 index 0000000000000000000000000000000000000000..70e0e14088f6ea09b84b728df6aaae6d4776ab4e GIT binary patch literal 2374 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 Z{12-kJ&=_`^N#=k002ovPDHLkV1l3JS(E?( literal 0 HcmV?d00001 diff --git a/examples/wchess/wchess.wasm/chessboardjs-1.0.0/img/chesspieces/wikipedia/wN.png b/examples/wchess/wchess.wasm/chessboardjs-1.0.0/img/chesspieces/wikipedia/wN.png new file mode 100644 index 0000000000000000000000000000000000000000..237250c164f652f0ea61f5e8f65c9b4cb83489b7 GIT binary patch literal 2388 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 V@B7wJo3#J{002ovPDHLkV1iHz`5piO literal 0 HcmV?d00001 diff --git a/examples/wchess/wchess.wasm/chessboardjs-1.0.0/img/chesspieces/wikipedia/wQ.png b/examples/wchess/wchess.wasm/chessboardjs-1.0.0/img/chesspieces/wikipedia/wQ.png new file mode 100644 index 0000000000000000000000000000000000000000..c3dfc15e556cbbeb546374aa9ee5e6bf121e929d GIT binary patch literal 3812 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 minimum + function validSemanticVersion (version, minimum) { + version = parseSemVer(version) + minimum = parseSemVer(minimum) + + var versionNum = (version.major * 100000 * 100000) + + (version.minor * 100000) + + version.patch + var minimumNum = (minimum.major * 100000 * 100000) + + (minimum.minor * 100000) + + minimum.patch + + return versionNum >= minimumNum + } + + function interpolateTemplate (str, obj) { + for (var key in obj) { + if (!obj.hasOwnProperty(key)) continue + var keyTemplateStr = '{' + key + '}' + var value = obj[key] + while (str.indexOf(keyTemplateStr) !== -1) { + str = str.replace(keyTemplateStr, value) + } + } + return str + } + + if (RUN_ASSERTS) { + console.assert(interpolateTemplate('abc', {a: 'x'}) === 'abc') + console.assert(interpolateTemplate('{a}bc', {}) === '{a}bc') + console.assert(interpolateTemplate('{a}bc', {p: 'q'}) === '{a}bc') + console.assert(interpolateTemplate('{a}bc', {a: 'x'}) === 'xbc') + console.assert(interpolateTemplate('{a}bc{a}bc', {a: 'x'}) === 'xbcxbc') + console.assert(interpolateTemplate('{a}{a}{b}', {a: 'x', b: 'y'}) === 'xxy') + } + + // --------------------------------------------------------------------------- + // Predicates + // --------------------------------------------------------------------------- + + function isString (s) { + return typeof s === 'string' + } + + function isFunction (f) { + return typeof f === 'function' + } + + function isInteger (n) { + return typeof n === 'number' && + isFinite(n) && + Math.floor(n) === n + } + + function validAnimationSpeed (speed) { + if (speed === 'fast' || speed === 'slow') return true + if (!isInteger(speed)) return false + return speed >= 0 + } + + function validThrottleRate (rate) { + return isInteger(rate) && + rate >= 1 + } + + function validMove (move) { + // move should be a string + if (!isString(move)) return false + + // move should be in the form of "e2-e4", "f6-d5" + var squares = move.split('-') + if (squares.length !== 2) return false + + return validSquare(squares[0]) && validSquare(squares[1]) + } + + function validSquare (square) { + return isString(square) && square.search(/^[a-h][1-8]$/) !== -1 + } + + if (RUN_ASSERTS) { + console.assert(validSquare('a1')) + console.assert(validSquare('e2')) + console.assert(!validSquare('D2')) + console.assert(!validSquare('g9')) + console.assert(!validSquare('a')) + console.assert(!validSquare(true)) + console.assert(!validSquare(null)) + console.assert(!validSquare({})) + } + + function validPieceCode (code) { + return isString(code) && code.search(/^[bw][KQRNBP]$/) !== -1 + } + + if (RUN_ASSERTS) { + console.assert(validPieceCode('bP')) + console.assert(validPieceCode('bK')) + console.assert(validPieceCode('wK')) + console.assert(validPieceCode('wR')) + console.assert(!validPieceCode('WR')) + console.assert(!validPieceCode('Wr')) + console.assert(!validPieceCode('a')) + console.assert(!validPieceCode(true)) + console.assert(!validPieceCode(null)) + console.assert(!validPieceCode({})) + } + + function validFen (fen) { + if (!isString(fen)) return false + + // cut off any move, castling, etc info from the end + // we're only interested in position information + fen = fen.replace(/ .+$/, '') + + // expand the empty square numbers to just 1s + fen = expandFenEmptySquares(fen) + + // FEN should be 8 sections separated by slashes + var chunks = fen.split('/') + if (chunks.length !== 8) return false + + // check each section + for (var i = 0; i < 8; i++) { + if (chunks[i].length !== 8 || + chunks[i].search(/[^kqrnbpKQRNBP1]/) !== -1) { + return false + } + } + + return true + } + + if (RUN_ASSERTS) { + console.assert(validFen(START_FEN)) + console.assert(validFen('8/8/8/8/8/8/8/8')) + console.assert(validFen('r1bqkbnr/pppp1ppp/2n5/1B2p3/4P3/5N2/PPPP1PPP/RNBQK2R')) + console.assert(validFen('3r3r/1p4pp/2nb1k2/pP3p2/8/PB2PN2/p4PPP/R4RK1 b - - 0 1')) + console.assert(!validFen('3r3z/1p4pp/2nb1k2/pP3p2/8/PB2PN2/p4PPP/R4RK1 b - - 0 1')) + console.assert(!validFen('anbqkbnr/8/8/8/8/8/PPPPPPPP/8')) + console.assert(!validFen('rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/')) + console.assert(!validFen('rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBN')) + console.assert(!validFen('888888/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR')) + console.assert(!validFen('888888/pppppppp/74/8/8/8/PPPPPPPP/RNBQKBNR')) + console.assert(!validFen({})) + } + + function validPositionObject (pos) { + if (!$.isPlainObject(pos)) return false + + for (var i in pos) { + if (!pos.hasOwnProperty(i)) continue + + if (!validSquare(i) || !validPieceCode(pos[i])) { + return false + } + } + + return true + } + + if (RUN_ASSERTS) { + console.assert(validPositionObject(START_POSITION)) + console.assert(validPositionObject({})) + console.assert(validPositionObject({e2: 'wP'})) + console.assert(validPositionObject({e2: 'wP', d2: 'wP'})) + console.assert(!validPositionObject({e2: 'BP'})) + console.assert(!validPositionObject({y2: 'wP'})) + console.assert(!validPositionObject(null)) + console.assert(!validPositionObject('start')) + console.assert(!validPositionObject(START_FEN)) + } + + function isTouchDevice () { + return 'ontouchstart' in document.documentElement + } + + function validJQueryVersion () { + return typeof window.$ && + $.fn && + $.fn.jquery && + validSemanticVersion($.fn.jquery, MINIMUM_JQUERY_VERSION) + } + + // --------------------------------------------------------------------------- + // Chess Util Functions + // --------------------------------------------------------------------------- + + // convert FEN piece code to bP, wK, etc + function fenToPieceCode (piece) { + // black piece + if (piece.toLowerCase() === piece) { + return 'b' + piece.toUpperCase() + } + + // white piece + return 'w' + piece.toUpperCase() + } + + // convert bP, wK, etc code to FEN structure + function pieceCodeToFen (piece) { + var pieceCodeLetters = piece.split('') + + // white piece + if (pieceCodeLetters[0] === 'w') { + return pieceCodeLetters[1].toUpperCase() + } + + // black piece + return pieceCodeLetters[1].toLowerCase() + } + + // convert FEN string to position object + // returns false if the FEN string is invalid + function fenToObj (fen) { + if (!validFen(fen)) return false + + // cut off any move, castling, etc info from the end + // we're only interested in position information + fen = fen.replace(/ .+$/, '') + + var rows = fen.split('/') + var position = {} + + var currentRow = 8 + for (var i = 0; i < 8; i++) { + var row = rows[i].split('') + var colIdx = 0 + + // loop through each character in the FEN section + for (var j = 0; j < row.length; j++) { + // number / empty squares + if (row[j].search(/[1-8]/) !== -1) { + var numEmptySquares = parseInt(row[j], 10) + colIdx = colIdx + numEmptySquares + } else { + // piece + var square = COLUMNS[colIdx] + currentRow + position[square] = fenToPieceCode(row[j]) + colIdx = colIdx + 1 + } + } + + currentRow = currentRow - 1 + } + + return position + } + + // position object to FEN string + // returns false if the obj is not a valid position object + function objToFen (obj) { + if (!validPositionObject(obj)) return false + + var fen = '' + + var currentRow = 8 + for (var i = 0; i < 8; i++) { + for (var j = 0; j < 8; j++) { + var square = COLUMNS[j] + currentRow + + // piece exists + if (obj.hasOwnProperty(square)) { + fen = fen + pieceCodeToFen(obj[square]) + } else { + // empty space + fen = fen + '1' + } + } + + if (i !== 7) { + fen = fen + '/' + } + + currentRow = currentRow - 1 + } + + // squeeze the empty numbers together + fen = squeezeFenEmptySquares(fen) + + return fen + } + + if (RUN_ASSERTS) { + console.assert(objToFen(START_POSITION) === START_FEN) + console.assert(objToFen({}) === '8/8/8/8/8/8/8/8') + console.assert(objToFen({a2: 'wP', 'b2': 'bP'}) === '8/8/8/8/8/8/Pp6/8') + } + + function squeezeFenEmptySquares (fen) { + return fen.replace(/11111111/g, '8') + .replace(/1111111/g, '7') + .replace(/111111/g, '6') + .replace(/11111/g, '5') + .replace(/1111/g, '4') + .replace(/111/g, '3') + .replace(/11/g, '2') + } + + function expandFenEmptySquares (fen) { + return fen.replace(/8/g, '11111111') + .replace(/7/g, '1111111') + .replace(/6/g, '111111') + .replace(/5/g, '11111') + .replace(/4/g, '1111') + .replace(/3/g, '111') + .replace(/2/g, '11') + } + + // returns the distance between two squares + function squareDistance (squareA, squareB) { + var squareAArray = squareA.split('') + var squareAx = COLUMNS.indexOf(squareAArray[0]) + 1 + var squareAy = parseInt(squareAArray[1], 10) + + var squareBArray = squareB.split('') + var squareBx = COLUMNS.indexOf(squareBArray[0]) + 1 + var squareBy = parseInt(squareBArray[1], 10) + + var xDelta = Math.abs(squareAx - squareBx) + var yDelta = Math.abs(squareAy - squareBy) + + if (xDelta >= yDelta) return xDelta + return yDelta + } + + // returns the square of the closest instance of piece + // returns false if no instance of piece is found in position + function findClosestPiece (position, piece, square) { + // create array of closest squares from square + var closestSquares = createRadius(square) + + // search through the position in order of distance for the piece + for (var i = 0; i < closestSquares.length; i++) { + var s = closestSquares[i] + + if (position.hasOwnProperty(s) && position[s] === piece) { + return s + } + } + + return false + } + + // returns an array of closest squares from square + function createRadius (square) { + var squares = [] + + // calculate distance of all squares + for (var i = 0; i < 8; i++) { + for (var j = 0; j < 8; j++) { + var s = COLUMNS[i] + (j + 1) + + // skip the square we're starting from + if (square === s) continue + + squares.push({ + square: s, + distance: squareDistance(square, s) + }) + } + } + + // sort by distance + squares.sort(function (a, b) { + return a.distance - b.distance + }) + + // just return the square code + var surroundingSquares = [] + for (i = 0; i < squares.length; i++) { + surroundingSquares.push(squares[i].square) + } + + return surroundingSquares + } + + // given a position and a set of moves, return a new position + // with the moves executed + function calculatePositionFromMoves (position, moves) { + var newPosition = deepCopy(position) + + for (var i in moves) { + if (!moves.hasOwnProperty(i)) continue + + // skip the move if the position doesn't have a piece on the source square + if (!newPosition.hasOwnProperty(i)) continue + + var piece = newPosition[i] + delete newPosition[i] + newPosition[moves[i]] = piece + } + + return newPosition + } + + // TODO: add some asserts here for calculatePositionFromMoves + + // --------------------------------------------------------------------------- + // HTML + // --------------------------------------------------------------------------- + + function buildContainerHTML (hasSparePieces) { + var html = '
' + + if (hasSparePieces) { + html += '
' + } + + html += '
' + + if (hasSparePieces) { + html += '
' + } + + html += '
' + + return interpolateTemplate(html, CSS) + } + + // --------------------------------------------------------------------------- + // Config + // --------------------------------------------------------------------------- + + function expandConfigArgumentShorthand (config) { + if (config === 'start') { + config = {position: deepCopy(START_POSITION)} + } else if (validFen(config)) { + config = {position: fenToObj(config)} + } else if (validPositionObject(config)) { + config = {position: deepCopy(config)} + } + + // config must be an object + if (!$.isPlainObject(config)) config = {} + + return config + } + + // validate config / set default options + function expandConfig (config) { + // default for orientation is white + if (config.orientation !== 'black') config.orientation = 'white' + + // default for showNotation is true + if (config.showNotation !== false) config.showNotation = true + + // default for draggable is false + if (config.draggable !== true) config.draggable = false + + // default for dropOffBoard is 'snapback' + if (config.dropOffBoard !== 'trash') config.dropOffBoard = 'snapback' + + // default for sparePieces is false + if (config.sparePieces !== true) config.sparePieces = false + + // draggable must be true if sparePieces is enabled + if (config.sparePieces) config.draggable = true + + // default piece theme is wikipedia + if (!config.hasOwnProperty('pieceTheme') || + (!isString(config.pieceTheme) && !isFunction(config.pieceTheme))) { + config.pieceTheme = 'img/chesspieces/wikipedia/{piece}.png' + } + + // animation speeds + if (!validAnimationSpeed(config.appearSpeed)) config.appearSpeed = DEFAULT_APPEAR_SPEED + if (!validAnimationSpeed(config.moveSpeed)) config.moveSpeed = DEFAULT_MOVE_SPEED + if (!validAnimationSpeed(config.snapbackSpeed)) config.snapbackSpeed = DEFAULT_SNAPBACK_SPEED + if (!validAnimationSpeed(config.snapSpeed)) config.snapSpeed = DEFAULT_SNAP_SPEED + if (!validAnimationSpeed(config.trashSpeed)) config.trashSpeed = DEFAULT_TRASH_SPEED + + // throttle rate + if (!validThrottleRate(config.dragThrottleRate)) config.dragThrottleRate = DEFAULT_DRAG_THROTTLE_RATE + + return config + } + + // --------------------------------------------------------------------------- + // Dependencies + // --------------------------------------------------------------------------- + + // check for a compatible version of jQuery + function checkJQuery () { + if (!validJQueryVersion()) { + var errorMsg = 'Chessboard Error 1005: Unable to find a valid version of jQuery. ' + + 'Please include jQuery ' + MINIMUM_JQUERY_VERSION + ' or higher on the page' + + '\n\n' + + 'Exiting' + ELLIPSIS + window.alert(errorMsg) + return false + } + + return true + } + + // return either boolean false or the $container element + function checkContainerArg (containerElOrString) { + if (containerElOrString === '') { + var errorMsg1 = 'Chessboard Error 1001: ' + + 'The first argument to Chessboard() cannot be an empty string.' + + '\n\n' + + 'Exiting' + ELLIPSIS + window.alert(errorMsg1) + return false + } + + // convert containerEl to query selector if it is a string + if (isString(containerElOrString) && + containerElOrString.charAt(0) !== '#') { + containerElOrString = '#' + containerElOrString + } + + // containerEl must be something that becomes a jQuery collection of size 1 + var $container = $(containerElOrString) + if ($container.length !== 1) { + var errorMsg2 = 'Chessboard Error 1003: ' + + 'The first argument to Chessboard() must be the ID of a DOM node, ' + + 'an ID query selector, or a single DOM node.' + + '\n\n' + + 'Exiting' + ELLIPSIS + window.alert(errorMsg2) + return false + } + + return $container + } + + // --------------------------------------------------------------------------- + // Constructor + // --------------------------------------------------------------------------- + + function constructor (containerElOrString, config) { + // first things first: check basic dependencies + if (!checkJQuery()) return null + var $container = checkContainerArg(containerElOrString) + if (!$container) return null + + // ensure the config object is what we expect + config = expandConfigArgumentShorthand(config) + config = expandConfig(config) + + // DOM elements + var $board = null + var $draggedPiece = null + var $sparePiecesTop = null + var $sparePiecesBottom = null + + // constructor return object + var widget = {} + + // ------------------------------------------------------------------------- + // Stateful + // ------------------------------------------------------------------------- + + var boardBorderSize = 2 + var currentOrientation = 'white' + var currentPosition = {} + var draggedPiece = null + var draggedPieceLocation = null + var draggedPieceSource = null + var isDragging = false + var sparePiecesElsIds = {} + var squareElsIds = {} + var squareElsOffsets = {} + var squareSize = 16 + + // ------------------------------------------------------------------------- + // Validation / Errors + // ------------------------------------------------------------------------- + + function error (code, msg, obj) { + // do nothing if showErrors is not set + if ( + config.hasOwnProperty('showErrors') !== true || + config.showErrors === false + ) { + return + } + + var errorText = 'Chessboard Error ' + code + ': ' + msg + + // print to console + if ( + config.showErrors === 'console' && + typeof console === 'object' && + typeof console.log === 'function' + ) { + console.log(errorText) + if (arguments.length >= 2) { + console.log(obj) + } + return + } + + // alert errors + if (config.showErrors === 'alert') { + if (obj) { + errorText += '\n\n' + JSON.stringify(obj) + } + window.alert(errorText) + return + } + + // custom function + if (isFunction(config.showErrors)) { + config.showErrors(code, msg, obj) + } + } + + function setInitialState () { + currentOrientation = config.orientation + + // make sure position is valid + if (config.hasOwnProperty('position')) { + if (config.position === 'start') { + currentPosition = deepCopy(START_POSITION) + } else if (validFen(config.position)) { + currentPosition = fenToObj(config.position) + } else if (validPositionObject(config.position)) { + currentPosition = deepCopy(config.position) + } else { + error( + 7263, + 'Invalid value passed to config.position.', + config.position + ) + } + } + } + + // ------------------------------------------------------------------------- + // DOM Misc + // ------------------------------------------------------------------------- + + // calculates square size based on the width of the container + // got a little CSS black magic here, so let me explain: + // get the width of the container element (could be anything), reduce by 1 for + // fudge factor, and then keep reducing until we find an exact mod 8 for + // our square size + function calculateSquareSize () { + var containerWidth = parseInt($container.width(), 10) + + // defensive, prevent infinite loop + if (!containerWidth || containerWidth <= 0) { + return 0 + } + + // pad one pixel + var boardWidth = containerWidth - 1 + + while (boardWidth % 8 !== 0 && boardWidth > 0) { + boardWidth = boardWidth - 1 + } + + return boardWidth / 8 + } + + // create random IDs for elements + function createElIds () { + // squares on the board + for (var i = 0; i < COLUMNS.length; i++) { + for (var j = 1; j <= 8; j++) { + var square = COLUMNS[i] + j + squareElsIds[square] = square + '-' + uuid() + } + } + + // spare pieces + var pieces = 'KQRNBP'.split('') + for (i = 0; i < pieces.length; i++) { + var whitePiece = 'w' + pieces[i] + var blackPiece = 'b' + pieces[i] + sparePiecesElsIds[whitePiece] = whitePiece + '-' + uuid() + sparePiecesElsIds[blackPiece] = blackPiece + '-' + uuid() + } + } + + // ------------------------------------------------------------------------- + // Markup Building + // ------------------------------------------------------------------------- + + function buildBoardHTML (orientation) { + if (orientation !== 'black') { + orientation = 'white' + } + + var html = '' + + // algebraic notation / orientation + var alpha = deepCopy(COLUMNS) + var row = 8 + if (orientation === 'black') { + alpha.reverse() + row = 1 + } + + var squareColor = 'white' + for (var i = 0; i < 8; i++) { + html += '
' + for (var j = 0; j < 8; j++) { + var square = alpha[j] + row + + html += '
' + + if (config.showNotation) { + // alpha notation + if ((orientation === 'white' && row === 1) || + (orientation === 'black' && row === 8)) { + html += '
' + alpha[j] + '
' + } + + // numeric notation + if (j === 0) { + html += '
' + row + '
' + } + } + + html += '
' // end .square + + squareColor = (squareColor === 'white') ? 'black' : 'white' + } + html += '
' + + squareColor = (squareColor === 'white') ? 'black' : 'white' + + if (orientation === 'white') { + row = row - 1 + } else { + row = row + 1 + } + } + + return interpolateTemplate(html, CSS) + } + + function buildPieceImgSrc (piece) { + if (isFunction(config.pieceTheme)) { + return config.pieceTheme(piece) + } + + if (isString(config.pieceTheme)) { + return interpolateTemplate(config.pieceTheme, {piece: piece}) + } + + // NOTE: this should never happen + error(8272, 'Unable to build image source for config.pieceTheme.') + return '' + } + + function buildPieceHTML (piece, hidden, id) { + var html = '' + + return interpolateTemplate(html, CSS) + } + + function buildSparePiecesHTML (color) { + var pieces = ['wK', 'wQ', 'wR', 'wB', 'wN', 'wP'] + if (color === 'black') { + pieces = ['bK', 'bQ', 'bR', 'bB', 'bN', 'bP'] + } + + var html = '' + for (var i = 0; i < pieces.length; i++) { + html += buildPieceHTML(pieces[i], false, sparePiecesElsIds[pieces[i]]) + } + + return html + } + + // ------------------------------------------------------------------------- + // Animations + // ------------------------------------------------------------------------- + + function animateSquareToSquare (src, dest, piece, completeFn) { + // get information about the source and destination squares + var $srcSquare = $('#' + squareElsIds[src]) + var srcSquarePosition = $srcSquare.offset() + var $destSquare = $('#' + squareElsIds[dest]) + var destSquarePosition = $destSquare.offset() + + // create the animated piece and absolutely position it + // over the source square + var animatedPieceId = uuid() + $('body').append(buildPieceHTML(piece, true, animatedPieceId)) + var $animatedPiece = $('#' + animatedPieceId) + $animatedPiece.css({ + display: '', + position: 'absolute', + top: srcSquarePosition.top, + left: srcSquarePosition.left + }) + + // remove original piece from source square + $srcSquare.find('.' + CSS.piece).remove() + + function onFinishAnimation1 () { + // add the "real" piece to the destination square + $destSquare.append(buildPieceHTML(piece)) + + // remove the animated piece + $animatedPiece.remove() + + // run complete function + if (isFunction(completeFn)) { + completeFn() + } + } + + // animate the piece to the destination square + var opts = { + duration: config.moveSpeed, + complete: onFinishAnimation1 + } + $animatedPiece.animate(destSquarePosition, opts) + } + + function animateSparePieceToSquare (piece, dest, completeFn) { + var srcOffset = $('#' + sparePiecesElsIds[piece]).offset() + var $destSquare = $('#' + squareElsIds[dest]) + var destOffset = $destSquare.offset() + + // create the animate piece + var pieceId = uuid() + $('body').append(buildPieceHTML(piece, true, pieceId)) + var $animatedPiece = $('#' + pieceId) + $animatedPiece.css({ + display: '', + position: 'absolute', + left: srcOffset.left, + top: srcOffset.top + }) + + // on complete + function onFinishAnimation2 () { + // add the "real" piece to the destination square + $destSquare.find('.' + CSS.piece).remove() + $destSquare.append(buildPieceHTML(piece)) + + // remove the animated piece + $animatedPiece.remove() + + // run complete function + if (isFunction(completeFn)) { + completeFn() + } + } + + // animate the piece to the destination square + var opts = { + duration: config.moveSpeed, + complete: onFinishAnimation2 + } + $animatedPiece.animate(destOffset, opts) + } + + // execute an array of animations + function doAnimations (animations, oldPos, newPos) { + if (animations.length === 0) return + + var numFinished = 0 + function onFinishAnimation3 () { + // exit if all the animations aren't finished + numFinished = numFinished + 1 + if (numFinished !== animations.length) return + + drawPositionInstant() + + // run their onMoveEnd function + if (isFunction(config.onMoveEnd)) { + config.onMoveEnd(deepCopy(oldPos), deepCopy(newPos)) + } + } + + for (var i = 0; i < animations.length; i++) { + var animation = animations[i] + + // clear a piece + if (animation.type === 'clear') { + $('#' + squareElsIds[animation.square] + ' .' + CSS.piece) + .fadeOut(config.trashSpeed, onFinishAnimation3) + + // add a piece with no spare pieces - fade the piece onto the square + } else if (animation.type === 'add' && !config.sparePieces) { + $('#' + squareElsIds[animation.square]) + .append(buildPieceHTML(animation.piece, true)) + .find('.' + CSS.piece) + .fadeIn(config.appearSpeed, onFinishAnimation3) + + // add a piece with spare pieces - animate from the spares + } else if (animation.type === 'add' && config.sparePieces) { + animateSparePieceToSquare(animation.piece, animation.square, onFinishAnimation3) + + // move a piece from squareA to squareB + } else if (animation.type === 'move') { + animateSquareToSquare(animation.source, animation.destination, animation.piece, onFinishAnimation3) + } + } + } + + // calculate an array of animations that need to happen in order to get + // from pos1 to pos2 + function calculateAnimations (pos1, pos2) { + // make copies of both + pos1 = deepCopy(pos1) + pos2 = deepCopy(pos2) + + var animations = [] + var squaresMovedTo = {} + + // remove pieces that are the same in both positions + for (var i in pos2) { + if (!pos2.hasOwnProperty(i)) continue + + if (pos1.hasOwnProperty(i) && pos1[i] === pos2[i]) { + delete pos1[i] + delete pos2[i] + } + } + + // find all the "move" animations + for (i in pos2) { + if (!pos2.hasOwnProperty(i)) continue + + var closestPiece = findClosestPiece(pos1, pos2[i], i) + if (closestPiece) { + animations.push({ + type: 'move', + source: closestPiece, + destination: i, + piece: pos2[i] + }) + + delete pos1[closestPiece] + delete pos2[i] + squaresMovedTo[i] = true + } + } + + // "add" animations + for (i in pos2) { + if (!pos2.hasOwnProperty(i)) continue + + animations.push({ + type: 'add', + square: i, + piece: pos2[i] + }) + + delete pos2[i] + } + + // "clear" animations + for (i in pos1) { + if (!pos1.hasOwnProperty(i)) continue + + // do not clear a piece if it is on a square that is the result + // of a "move", ie: a piece capture + if (squaresMovedTo.hasOwnProperty(i)) continue + + animations.push({ + type: 'clear', + square: i, + piece: pos1[i] + }) + + delete pos1[i] + } + + return animations + } + + // ------------------------------------------------------------------------- + // Control Flow + // ------------------------------------------------------------------------- + + function drawPositionInstant () { + // clear the board + $board.find('.' + CSS.piece).remove() + + // add the pieces + for (var i in currentPosition) { + if (!currentPosition.hasOwnProperty(i)) continue + + $('#' + squareElsIds[i]).append(buildPieceHTML(currentPosition[i])) + } + } + + function drawBoard () { + $board.html(buildBoardHTML(currentOrientation, squareSize, config.showNotation)) + drawPositionInstant() + + if (config.sparePieces) { + if (currentOrientation === 'white') { + $sparePiecesTop.html(buildSparePiecesHTML('black')) + $sparePiecesBottom.html(buildSparePiecesHTML('white')) + } else { + $sparePiecesTop.html(buildSparePiecesHTML('white')) + $sparePiecesBottom.html(buildSparePiecesHTML('black')) + } + } + } + + function setCurrentPosition (position) { + var oldPos = deepCopy(currentPosition) + var newPos = deepCopy(position) + var oldFen = objToFen(oldPos) + var newFen = objToFen(newPos) + + // do nothing if no change in position + if (oldFen === newFen) return + + // run their onChange function + if (isFunction(config.onChange)) { + config.onChange(oldPos, newPos) + } + + // update state + currentPosition = position + } + + function isXYOnSquare (x, y) { + for (var i in squareElsOffsets) { + if (!squareElsOffsets.hasOwnProperty(i)) continue + + var s = squareElsOffsets[i] + if (x >= s.left && + x < s.left + squareSize && + y >= s.top && + y < s.top + squareSize) { + return i + } + } + + return 'offboard' + } + + // records the XY coords of every square into memory + function captureSquareOffsets () { + squareElsOffsets = {} + + for (var i in squareElsIds) { + if (!squareElsIds.hasOwnProperty(i)) continue + + squareElsOffsets[i] = $('#' + squareElsIds[i]).offset() + } + } + + function removeSquareHighlights () { + $board + .find('.' + CSS.square) + .removeClass(CSS.highlight1 + ' ' + CSS.highlight2) + } + + function snapbackDraggedPiece () { + // there is no "snapback" for spare pieces + if (draggedPieceSource === 'spare') { + trashDraggedPiece() + return + } + + removeSquareHighlights() + + // animation complete + function complete () { + drawPositionInstant() + $draggedPiece.css('display', 'none') + + // run their onSnapbackEnd function + if (isFunction(config.onSnapbackEnd)) { + config.onSnapbackEnd( + draggedPiece, + draggedPieceSource, + deepCopy(currentPosition), + currentOrientation + ) + } + } + + // get source square position + var sourceSquarePosition = $('#' + squareElsIds[draggedPieceSource]).offset() + + // animate the piece to the target square + var opts = { + duration: config.snapbackSpeed, + complete: complete + } + $draggedPiece.animate(sourceSquarePosition, opts) + + // set state + isDragging = false + } + + function trashDraggedPiece () { + removeSquareHighlights() + + // remove the source piece + var newPosition = deepCopy(currentPosition) + delete newPosition[draggedPieceSource] + setCurrentPosition(newPosition) + + // redraw the position + drawPositionInstant() + + // hide the dragged piece + $draggedPiece.fadeOut(config.trashSpeed) + + // set state + isDragging = false + } + + function dropDraggedPieceOnSquare (square) { + removeSquareHighlights() + + // update position + var newPosition = deepCopy(currentPosition) + delete newPosition[draggedPieceSource] + newPosition[square] = draggedPiece + setCurrentPosition(newPosition) + + // get target square information + var targetSquarePosition = $('#' + squareElsIds[square]).offset() + + // animation complete + function onAnimationComplete () { + drawPositionInstant() + $draggedPiece.css('display', 'none') + + // execute their onSnapEnd function + if (isFunction(config.onSnapEnd)) { + config.onSnapEnd(draggedPieceSource, square, draggedPiece) + } + } + + // snap the piece to the target square + var opts = { + duration: config.snapSpeed, + complete: onAnimationComplete + } + $draggedPiece.animate(targetSquarePosition, opts) + + // set state + isDragging = false + } + + function beginDraggingPiece (source, piece, x, y) { + // run their custom onDragStart function + // their custom onDragStart function can cancel drag start + if (isFunction(config.onDragStart) && + config.onDragStart(source, piece, deepCopy(currentPosition), currentOrientation) === false) { + return + } + + // set state + isDragging = true + draggedPiece = piece + draggedPieceSource = source + + // if the piece came from spare pieces, location is offboard + if (source === 'spare') { + draggedPieceLocation = 'offboard' + } else { + draggedPieceLocation = source + } + + // capture the x, y coords of all squares in memory + captureSquareOffsets() + + // create the dragged piece + $draggedPiece.attr('src', buildPieceImgSrc(piece)).css({ + display: '', + position: 'absolute', + left: x - squareSize / 2, + top: y - squareSize / 2 + }) + + if (source !== 'spare') { + // highlight the source square and hide the piece + $('#' + squareElsIds[source]) + .addClass(CSS.highlight1) + .find('.' + CSS.piece) + .css('display', 'none') + } + } + + function updateDraggedPiece (x, y) { + // put the dragged piece over the mouse cursor + $draggedPiece.css({ + left: x - squareSize / 2, + top: y - squareSize / 2 + }) + + // get location + var location = isXYOnSquare(x, y) + + // do nothing if the location has not changed + if (location === draggedPieceLocation) return + + // remove highlight from previous square + if (validSquare(draggedPieceLocation)) { + $('#' + squareElsIds[draggedPieceLocation]).removeClass(CSS.highlight2) + } + + // add highlight to new square + if (validSquare(location)) { + $('#' + squareElsIds[location]).addClass(CSS.highlight2) + } + + // run onDragMove + if (isFunction(config.onDragMove)) { + config.onDragMove( + location, + draggedPieceLocation, + draggedPieceSource, + draggedPiece, + deepCopy(currentPosition), + currentOrientation + ) + } + + // update state + draggedPieceLocation = location + } + + function stopDraggedPiece (location) { + // determine what the action should be + var action = 'drop' + if (location === 'offboard' && config.dropOffBoard === 'snapback') { + action = 'snapback' + } + if (location === 'offboard' && config.dropOffBoard === 'trash') { + action = 'trash' + } + + // run their onDrop function, which can potentially change the drop action + if (isFunction(config.onDrop)) { + var newPosition = deepCopy(currentPosition) + + // source piece is a spare piece and position is off the board + // if (draggedPieceSource === 'spare' && location === 'offboard') {...} + // position has not changed; do nothing + + // source piece is a spare piece and position is on the board + if (draggedPieceSource === 'spare' && validSquare(location)) { + // add the piece to the board + newPosition[location] = draggedPiece + } + + // source piece was on the board and position is off the board + if (validSquare(draggedPieceSource) && location === 'offboard') { + // remove the piece from the board + delete newPosition[draggedPieceSource] + } + + // source piece was on the board and position is on the board + if (validSquare(draggedPieceSource) && validSquare(location)) { + // move the piece + delete newPosition[draggedPieceSource] + newPosition[location] = draggedPiece + } + + var oldPosition = deepCopy(currentPosition) + + var result = config.onDrop( + draggedPieceSource, + location, + draggedPiece, + newPosition, + oldPosition, + currentOrientation + ) + if (result === 'snapback' || result === 'trash') { + action = result + } + } + + // do it! + if (action === 'snapback') { + snapbackDraggedPiece() + } else if (action === 'trash') { + trashDraggedPiece() + } else if (action === 'drop') { + dropDraggedPieceOnSquare(location) + } + } + + // ------------------------------------------------------------------------- + // Public Methods + // ------------------------------------------------------------------------- + + // clear the board + widget.clear = function (useAnimation) { + widget.position({}, useAnimation) + } + + // remove the widget from the page + widget.destroy = function () { + // remove markup + $container.html('') + $draggedPiece.remove() + + // remove event handlers + $container.unbind() + } + + // shorthand method to get the current FEN + widget.fen = function () { + return widget.position('fen') + } + + // flip orientation + widget.flip = function () { + return widget.orientation('flip') + } + + // move pieces + // TODO: this method should be variadic as well as accept an array of moves + widget.move = function () { + // no need to throw an error here; just do nothing + // TODO: this should return the current position + if (arguments.length === 0) return + + var useAnimation = true + + // collect the moves into an object + var moves = {} + for (var i = 0; i < arguments.length; i++) { + // any "false" to this function means no animations + if (arguments[i] === false) { + useAnimation = false + continue + } + + // skip invalid arguments + if (!validMove(arguments[i])) { + error(2826, 'Invalid move passed to the move method.', arguments[i]) + continue + } + + var tmp = arguments[i].split('-') + moves[tmp[0]] = tmp[1] + } + + // calculate position from moves + var newPos = calculatePositionFromMoves(currentPosition, moves) + + // update the board + widget.position(newPos, useAnimation) + + // return the new position object + return newPos + } + + widget.orientation = function (arg) { + // no arguments, return the current orientation + if (arguments.length === 0) { + return currentOrientation + } + + // set to white or black + if (arg === 'white' || arg === 'black') { + currentOrientation = arg + drawBoard() + return currentOrientation + } + + // flip orientation + if (arg === 'flip') { + currentOrientation = currentOrientation === 'white' ? 'black' : 'white' + drawBoard() + return currentOrientation + } + + error(5482, 'Invalid value passed to the orientation method.', arg) + } + + widget.position = function (position, useAnimation) { + // no arguments, return the current position + if (arguments.length === 0) { + return deepCopy(currentPosition) + } + + // get position as FEN + if (isString(position) && position.toLowerCase() === 'fen') { + return objToFen(currentPosition) + } + + // start position + if (isString(position) && position.toLowerCase() === 'start') { + position = deepCopy(START_POSITION) + } + + // convert FEN to position object + if (validFen(position)) { + position = fenToObj(position) + } + + // validate position object + if (!validPositionObject(position)) { + error(6482, 'Invalid value passed to the position method.', position) + return + } + + // default for useAnimations is true + if (useAnimation !== false) useAnimation = true + + if (useAnimation) { + // start the animations + var animations = calculateAnimations(currentPosition, position) + doAnimations(animations, currentPosition, position) + + // set the new position + setCurrentPosition(position) + } else { + // instant update + setCurrentPosition(position) + drawPositionInstant() + } + } + + widget.resize = function () { + // calulate the new square size + squareSize = calculateSquareSize() + + // set board width + $board.css('width', squareSize * 8 + 'px') + + // set drag piece size + $draggedPiece.css({ + height: squareSize, + width: squareSize + }) + + // spare pieces + if (config.sparePieces) { + $container + .find('.' + CSS.sparePieces) + .css('paddingLeft', squareSize + boardBorderSize + 'px') + } + + // redraw the board + drawBoard() + } + + // set the starting position + widget.start = function (useAnimation) { + widget.position('start', useAnimation) + } + + // ------------------------------------------------------------------------- + // Browser Events + // ------------------------------------------------------------------------- + + function stopDefault (evt) { + evt.preventDefault() + } + + function mousedownSquare (evt) { + // do nothing if we're not draggable + if (!config.draggable) return + + // do nothing if there is no piece on this square + var square = $(this).attr('data-square') + if (!validSquare(square)) return + if (!currentPosition.hasOwnProperty(square)) return + + beginDraggingPiece(square, currentPosition[square], evt.pageX, evt.pageY) + } + + function touchstartSquare (e) { + // do nothing if we're not draggable + if (!config.draggable) return + + // do nothing if there is no piece on this square + var square = $(this).attr('data-square') + if (!validSquare(square)) return + if (!currentPosition.hasOwnProperty(square)) return + + e = e.originalEvent + beginDraggingPiece( + square, + currentPosition[square], + e.changedTouches[0].pageX, + e.changedTouches[0].pageY + ) + } + + function mousedownSparePiece (evt) { + // do nothing if sparePieces is not enabled + if (!config.sparePieces) return + + var piece = $(this).attr('data-piece') + + beginDraggingPiece('spare', piece, evt.pageX, evt.pageY) + } + + function touchstartSparePiece (e) { + // do nothing if sparePieces is not enabled + if (!config.sparePieces) return + + var piece = $(this).attr('data-piece') + + e = e.originalEvent + beginDraggingPiece( + 'spare', + piece, + e.changedTouches[0].pageX, + e.changedTouches[0].pageY + ) + } + + function mousemoveWindow (evt) { + if (isDragging) { + updateDraggedPiece(evt.pageX, evt.pageY) + } + } + + var throttledMousemoveWindow = throttle(mousemoveWindow, config.dragThrottleRate) + + function touchmoveWindow (evt) { + // do nothing if we are not dragging a piece + if (!isDragging) return + + // prevent screen from scrolling + evt.preventDefault() + + updateDraggedPiece(evt.originalEvent.changedTouches[0].pageX, + evt.originalEvent.changedTouches[0].pageY) + } + + var throttledTouchmoveWindow = throttle(touchmoveWindow, config.dragThrottleRate) + + function mouseupWindow (evt) { + // do nothing if we are not dragging a piece + if (!isDragging) return + + // get the location + var location = isXYOnSquare(evt.pageX, evt.pageY) + + stopDraggedPiece(location) + } + + function touchendWindow (evt) { + // do nothing if we are not dragging a piece + if (!isDragging) return + + // get the location + var location = isXYOnSquare(evt.originalEvent.changedTouches[0].pageX, + evt.originalEvent.changedTouches[0].pageY) + + stopDraggedPiece(location) + } + + function mouseenterSquare (evt) { + // do not fire this event if we are dragging a piece + // NOTE: this should never happen, but it's a safeguard + if (isDragging) return + + // exit if they did not provide a onMouseoverSquare function + if (!isFunction(config.onMouseoverSquare)) return + + // get the square + var square = $(evt.currentTarget).attr('data-square') + + // NOTE: this should never happen; defensive + if (!validSquare(square)) return + + // get the piece on this square + var piece = false + if (currentPosition.hasOwnProperty(square)) { + piece = currentPosition[square] + } + + // execute their function + config.onMouseoverSquare(square, piece, deepCopy(currentPosition), currentOrientation) + } + + function mouseleaveSquare (evt) { + // do not fire this event if we are dragging a piece + // NOTE: this should never happen, but it's a safeguard + if (isDragging) return + + // exit if they did not provide an onMouseoutSquare function + if (!isFunction(config.onMouseoutSquare)) return + + // get the square + var square = $(evt.currentTarget).attr('data-square') + + // NOTE: this should never happen; defensive + if (!validSquare(square)) return + + // get the piece on this square + var piece = false + if (currentPosition.hasOwnProperty(square)) { + piece = currentPosition[square] + } + + // execute their function + config.onMouseoutSquare(square, piece, deepCopy(currentPosition), currentOrientation) + } + + // ------------------------------------------------------------------------- + // Initialization + // ------------------------------------------------------------------------- + + function addEvents () { + // prevent "image drag" + $('body').on('mousedown mousemove', '.' + CSS.piece, stopDefault) + + // mouse drag pieces + $board.on('mousedown', '.' + CSS.square, mousedownSquare) + $container.on('mousedown', '.' + CSS.sparePieces + ' .' + CSS.piece, mousedownSparePiece) + + // mouse enter / leave square + $board + .on('mouseenter', '.' + CSS.square, mouseenterSquare) + .on('mouseleave', '.' + CSS.square, mouseleaveSquare) + + // piece drag + var $window = $(window) + $window + .on('mousemove', throttledMousemoveWindow) + .on('mouseup', mouseupWindow) + + // touch drag pieces + if (isTouchDevice()) { + $board.on('touchstart', '.' + CSS.square, touchstartSquare) + $container.on('touchstart', '.' + CSS.sparePieces + ' .' + CSS.piece, touchstartSparePiece) + $window + .on('touchmove', throttledTouchmoveWindow) + .on('touchend', touchendWindow) + } + } + + function initDOM () { + // create unique IDs for all the elements we will create + createElIds() + + // build board and save it in memory + $container.html(buildContainerHTML(config.sparePieces)) + $board = $container.find('.' + CSS.board) + + if (config.sparePieces) { + $sparePiecesTop = $container.find('.' + CSS.sparePiecesTop) + $sparePiecesBottom = $container.find('.' + CSS.sparePiecesBottom) + } + + // create the drag piece + var draggedPieceId = uuid() + $('body').append(buildPieceHTML('wP', true, draggedPieceId)) + $draggedPiece = $('#' + draggedPieceId) + + // TODO: need to remove this dragged piece element if the board is no + // longer in the DOM + + // get the border size + boardBorderSize = parseInt($board.css('borderLeftWidth'), 10) + + // set the size and draw the board + widget.resize() + } + + // ------------------------------------------------------------------------- + // Initialization + // ------------------------------------------------------------------------- + + setInitialState() + initDOM() + addEvents() + + // return the widget object + return widget + } // end constructor + + // TODO: do module exports here + window['Chessboard'] = constructor + + // support legacy ChessBoard name + window['ChessBoard'] = window['Chessboard'] + + // expose util functions + window['Chessboard']['fenToObj'] = fenToObj + window['Chessboard']['objToFen'] = objToFen +})() // end anonymous wrapper diff --git a/examples/wchess/wchess.wasm/chessboardjs-1.0.0/js/chessboard-1.0.0.min.js b/examples/wchess/wchess.wasm/chessboardjs-1.0.0/js/chessboard-1.0.0.min.js new file mode 100644 index 00000000000..73ea2870fdf --- /dev/null +++ b/examples/wchess/wchess.wasm/chessboardjs-1.0.0/js/chessboard-1.0.0.min.js @@ -0,0 +1,2 @@ +/*! chessboard.js v1.0.0 | (c) 2019 Chris Oakman | MIT License chessboardjs.com/license */ +!function(){"use strict";var z=window.jQuery,F="abcdefgh".split(""),r=20,A="…",W="1.8.3",e="rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR",G=pe(e),n=200,t=200,o=60,a=30,i=100,H={};function V(e,r,n){function t(){o=0,a&&(a=!1,s())}var o=0,a=!1,i=[],s=function(){o=window.setTimeout(t,r),e.apply(n,i)};return function(e){i=arguments,o?a=!0:s()}}function Z(){return"xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx".replace(/x/g,function(e){return(16*Math.random()|0).toString(16)})}function _(e){return JSON.parse(JSON.stringify(e))}function s(e){var r=e.split(".");return{major:parseInt(r[0],10),minor:parseInt(r[1],10),patch:parseInt(r[2],10)}}function ee(e,r){for(var n in r)if(r.hasOwnProperty(n))for(var t="{"+n+"}",o=r[n];-1!==e.indexOf(t);)e=e.replace(t,o);return e}function re(e){return"string"==typeof e}function ne(e){return"function"==typeof e}function p(e){return"number"==typeof e&&isFinite(e)&&Math.floor(e)===e}function c(e){return"fast"===e||"slow"===e||!!p(e)&&0<=e}function te(e){if(!re(e))return!1;var r=e.split("-");return 2===r.length&&(oe(r[0])&&oe(r[1]))}function oe(e){return re(e)&&-1!==e.search(/^[a-h][1-8]$/)}function u(e){return re(e)&&-1!==e.search(/^[bw][KQRNBP]$/)}function ae(e){if(!re(e))return!1;var r=(e=function(e){return e.replace(/8/g,"11111111").replace(/7/g,"1111111").replace(/6/g,"111111").replace(/5/g,"11111").replace(/4/g,"1111").replace(/3/g,"111").replace(/2/g,"11")}(e=e.replace(/ .+$/,""))).split("/");if(8!==r.length)return!1;for(var n=0;n<8;n++)if(8!==r[n].length||-1!==r[n].search(/[^kqrnbpKQRNBP1]/))return!1;return!0}function ie(e){if(!z.isPlainObject(e))return!1;for(var r in e)if(e.hasOwnProperty(r)&&(!oe(r)||!u(e[r])))return!1;return!0}function se(){return typeof window.$&&z.fn&&z.fn.jquery&&function(e,r){e=s(e),r=s(r);var n=1e5*e.major*1e5+1e5*e.minor+e.patch;return 1e5*r.major*1e5+1e5*r.minor+r.patch<=n}(z.fn.jquery,W)}function pe(e){if(!ae(e))return!1;for(var r,n=(e=e.replace(/ .+$/,"")).split("/"),t={},o=8,a=0;a<8;a++){for(var i=n[a].split(""),s=0,p=0;p';for(var i=0;i<8;i++){var s=n[i]+t;r+='
',f.showNotation&&(("white"===e&&1===t||"black"===e&&8===t)&&(r+='
'+n[i]+"
"),0===i&&(r+='
'+t+"
")),r+="
",o="white"===o?"black":"white"}r+='
',o="white"===o?"black":"white","white"===e?t-=1:t+=1}return ee(r,H)}(p,f.showNotation)),T(),f.sparePieces&&("white"===p?(t.html(x("black")),o.html(x("white"))):(t.html(x("white")),o.html(x("black"))))}function k(e){var r=_(c),n=_(e);ce(r)!==ce(n)&&(ne(f.onChange)&&f.onChange(r,n),c=e)}function E(e,r){for(var n in w)if(w.hasOwnProperty(n)){var t=w[n];if(e>=t.left&&e=t.top&&r (http://chrisoakman.com/)", + "name": "@chrisoakman/chessboardjs", + "description": "JavaScript chessboard widget", + "homepage": "https://chessboardjs.com", + "license": "MIT", + "version": "1.0.0", + "repository": { + "type": "git", + "url": "git://github.com/oakmac/chessboardjs.git" + }, + "files": ["dist/"], + "dependencies": { + "jquery": ">=3.4.1" + }, + "devDependencies": { + "csso": "3.5.1", + "fs-plus": "3.1.1", + "kidif": "1.1.0", + "mustache": "2.3.0", + "standard": "10.0.2", + "uglify-js": "3.6.0" + }, + "scripts": { + "build": "standard lib/chessboard.js && node scripts/build.js", + "standard": "standard --fix lib/*.js website/js/*.js", + "website": "node scripts/website.js" + } +} diff --git a/examples/wchess/wchess.wasm/index-tmpl.html b/examples/wchess/wchess.wasm/index-tmpl.html new file mode 100644 index 00000000000..736db67d385 --- /dev/null +++ b/examples/wchess/wchess.wasm/index-tmpl.html @@ -0,0 +1,371 @@ + + + + wchess : Voice assistant example using Whisper + WebAssembly + + + + + +
+ wchess : Voice assistant example using Whisper + WebAssembly + +

+ + You can find more about this project on GitHub. + +

+ + More examples: + main | + bench | + stream | + command | + talk | + +

+ +
+ +
+ Whisper model: + +

+ + +
+ +
+
+ + + + +
+ +
+ +
+ +
+ +
+ Status: not started + +
[The moves will be displayed here]
+
+ +
+ + Debug output: + + +
+ + Troubleshooting + +

+ + The page does some heavy computations, so make sure: + +
    +
  • To use a modern web browser (e.g. Chrome, Firefox)
  • +
  • To use a fast desktop or laptop computer (i.e. not a mobile phone)
  • +
  • Your browser supports WASM Fixed-width SIMD
  • +
+ +
+ + | + Build time: @GIT_DATE@ | + Commit hash: @GIT_SHA1@ | + Commit subject: @GIT_COMMIT_SUBJECT@ | + Source Code | + +
+
+ + + + + + diff --git a/examples/wchess/wchess.wasm/jquery-3.7.1.min.js b/examples/wchess/wchess.wasm/jquery-3.7.1.min.js new file mode 100644 index 00000000000..7f37b5d9912 --- /dev/null +++ b/examples/wchess/wchess.wasm/jquery-3.7.1.min.js @@ -0,0 +1,2 @@ +/*! jQuery v3.7.1 | (c) OpenJS Foundation and other contributors | jquery.org/license */ +!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(ie,e){"use strict";var oe=[],r=Object.getPrototypeOf,ae=oe.slice,g=oe.flat?function(e){return oe.flat.call(e)}:function(e){return oe.concat.apply([],e)},s=oe.push,se=oe.indexOf,n={},i=n.toString,ue=n.hasOwnProperty,o=ue.toString,a=o.call(Object),le={},v=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},y=function(e){return null!=e&&e===e.window},C=ie.document,u={type:!0,src:!0,nonce:!0,noModule:!0};function m(e,t,n){var r,i,o=(n=n||C).createElement("script");if(o.text=e,t)for(r in u)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function x(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[i.call(e)]||"object":typeof e}var t="3.7.1",l=/HTML$/i,ce=function(e,t){return new ce.fn.init(e,t)};function c(e){var t=!!e&&"length"in e&&e.length,n=x(e);return!v(e)&&!y(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+ge+")"+ge+"*"),x=new RegExp(ge+"|>"),j=new RegExp(g),A=new RegExp("^"+t+"$"),D={ID:new RegExp("^#("+t+")"),CLASS:new RegExp("^\\.("+t+")"),TAG:new RegExp("^("+t+"|[*])"),ATTR:new RegExp("^"+p),PSEUDO:new RegExp("^"+g),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+ge+"*(even|odd|(([+-]|)(\\d*)n|)"+ge+"*(?:([+-]|)"+ge+"*(\\d+)|))"+ge+"*\\)|)","i"),bool:new RegExp("^(?:"+f+")$","i"),needsContext:new RegExp("^"+ge+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+ge+"*((?:-\\d)?\\d*)"+ge+"*\\)|)(?=[^-]|$)","i")},N=/^(?:input|select|textarea|button)$/i,q=/^h\d$/i,L=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,H=/[+~]/,O=new RegExp("\\\\[\\da-fA-F]{1,6}"+ge+"?|\\\\([^\\r\\n\\f])","g"),P=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},M=function(){V()},R=J(function(e){return!0===e.disabled&&fe(e,"fieldset")},{dir:"parentNode",next:"legend"});try{k.apply(oe=ae.call(ye.childNodes),ye.childNodes),oe[ye.childNodes.length].nodeType}catch(e){k={apply:function(e,t){me.apply(e,ae.call(t))},call:function(e){me.apply(e,ae.call(arguments,1))}}}function I(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(V(e),e=e||T,C)){if(11!==p&&(u=L.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return k.call(n,a),n}else if(f&&(a=f.getElementById(i))&&I.contains(e,a)&&a.id===i)return k.call(n,a),n}else{if(u[2])return k.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&e.getElementsByClassName)return k.apply(n,e.getElementsByClassName(i)),n}if(!(h[t+" "]||d&&d.test(t))){if(c=t,f=e,1===p&&(x.test(t)||m.test(t))){(f=H.test(t)&&U(e.parentNode)||e)==e&&le.scope||((s=e.getAttribute("id"))?s=ce.escapeSelector(s):e.setAttribute("id",s=S)),o=(l=Y(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+Q(l[o]);c=l.join(",")}try{return k.apply(n,f.querySelectorAll(c)),n}catch(e){h(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return re(t.replace(ve,"$1"),e,n,r)}function W(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function F(e){return e[S]=!0,e}function $(e){var t=T.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function B(t){return function(e){return fe(e,"input")&&e.type===t}}function _(t){return function(e){return(fe(e,"input")||fe(e,"button"))&&e.type===t}}function z(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&R(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function X(a){return F(function(o){return o=+o,F(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function U(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}function V(e){var t,n=e?e.ownerDocument||e:ye;return n!=T&&9===n.nodeType&&n.documentElement&&(r=(T=n).documentElement,C=!ce.isXMLDoc(T),i=r.matches||r.webkitMatchesSelector||r.msMatchesSelector,r.msMatchesSelector&&ye!=T&&(t=T.defaultView)&&t.top!==t&&t.addEventListener("unload",M),le.getById=$(function(e){return r.appendChild(e).id=ce.expando,!T.getElementsByName||!T.getElementsByName(ce.expando).length}),le.disconnectedMatch=$(function(e){return i.call(e,"*")}),le.scope=$(function(){return T.querySelectorAll(":scope")}),le.cssHas=$(function(){try{return T.querySelector(":has(*,:jqfake)"),!1}catch(e){return!0}}),le.getById?(b.filter.ID=function(e){var t=e.replace(O,P);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&C){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(O,P);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&C){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):t.querySelectorAll(e)},b.find.CLASS=function(e,t){if("undefined"!=typeof t.getElementsByClassName&&C)return t.getElementsByClassName(e)},d=[],$(function(e){var t;r.appendChild(e).innerHTML="",e.querySelectorAll("[selected]").length||d.push("\\["+ge+"*(?:value|"+f+")"),e.querySelectorAll("[id~="+S+"-]").length||d.push("~="),e.querySelectorAll("a#"+S+"+*").length||d.push(".#.+[+~]"),e.querySelectorAll(":checked").length||d.push(":checked"),(t=T.createElement("input")).setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),r.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&d.push(":enabled",":disabled"),(t=T.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||d.push("\\["+ge+"*name"+ge+"*="+ge+"*(?:''|\"\")")}),le.cssHas||d.push(":has"),d=d.length&&new RegExp(d.join("|")),l=function(e,t){if(e===t)return a=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!le.sortDetached&&t.compareDocumentPosition(e)===n?e===T||e.ownerDocument==ye&&I.contains(ye,e)?-1:t===T||t.ownerDocument==ye&&I.contains(ye,t)?1:o?se.call(o,e)-se.call(o,t):0:4&n?-1:1)}),T}for(e in I.matches=function(e,t){return I(e,null,null,t)},I.matchesSelector=function(e,t){if(V(e),C&&!h[t+" "]&&(!d||!d.test(t)))try{var n=i.call(e,t);if(n||le.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){h(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(O,P),e[3]=(e[3]||e[4]||e[5]||"").replace(O,P),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||I.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&I.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return D.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&j.test(n)&&(t=Y(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(O,P).toLowerCase();return"*"===e?function(){return!0}:function(e){return fe(e,t)}},CLASS:function(e){var t=s[e+" "];return t||(t=new RegExp("(^|"+ge+")"+e+"("+ge+"|$)"))&&s(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=I.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function T(e,n,r){return v(n)?ce.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?ce.grep(e,function(e){return e===n!==r}):"string"!=typeof n?ce.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(ce.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||k,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:S.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof ce?t[0]:t,ce.merge(this,ce.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:C,!0)),w.test(r[1])&&ce.isPlainObject(t))for(r in t)v(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=C.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):v(e)?void 0!==n.ready?n.ready(e):e(ce):ce.makeArray(e,this)}).prototype=ce.fn,k=ce(C);var E=/^(?:parents|prev(?:Until|All))/,j={children:!0,contents:!0,next:!0,prev:!0};function A(e,t){while((e=e[t])&&1!==e.nodeType);return e}ce.fn.extend({has:function(e){var t=ce(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,Ce=/^$|^module$|\/(?:java|ecma)script/i;xe=C.createDocumentFragment().appendChild(C.createElement("div")),(be=C.createElement("input")).setAttribute("type","radio"),be.setAttribute("checked","checked"),be.setAttribute("name","t"),xe.appendChild(be),le.checkClone=xe.cloneNode(!0).cloneNode(!0).lastChild.checked,xe.innerHTML="",le.noCloneChecked=!!xe.cloneNode(!0).lastChild.defaultValue,xe.innerHTML="",le.option=!!xe.lastChild;var ke={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function Se(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&fe(e,t)?ce.merge([e],n):n}function Ee(e,t){for(var n=0,r=e.length;n",""]);var je=/<|&#?\w+;/;function Ae(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function Re(e,t){return fe(e,"table")&&fe(11!==t.nodeType?t:t.firstChild,"tr")&&ce(e).children("tbody")[0]||e}function Ie(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function We(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Fe(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(_.hasData(e)&&(s=_.get(e).events))for(i in _.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),C.head.appendChild(r[0])},abort:function(){i&&i()}}});var Jt,Kt=[],Zt=/(=)\?(?=&|$)|\?\?/;ce.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Kt.pop()||ce.expando+"_"+jt.guid++;return this[e]=!0,e}}),ce.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Zt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Zt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=v(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Zt,"$1"+r):!1!==e.jsonp&&(e.url+=(At.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||ce.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=ie[r],ie[r]=function(){o=arguments},n.always(function(){void 0===i?ce(ie).removeProp(r):ie[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Kt.push(r)),o&&v(i)&&i(o[0]),o=i=void 0}),"script"}),le.createHTMLDocument=((Jt=C.implementation.createHTMLDocument("").body).innerHTML="
",2===Jt.childNodes.length),ce.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(le.createHTMLDocument?((r=(t=C.implementation.createHTMLDocument("")).createElement("base")).href=C.location.href,t.head.appendChild(r)):t=C),o=!n&&[],(i=w.exec(e))?[t.createElement(i[1])]:(i=Ae([e],t,o),o&&o.length&&ce(o).remove(),ce.merge([],i.childNodes)));var r,i,o},ce.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(ce.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},ce.expr.pseudos.animated=function(t){return ce.grep(ce.timers,function(e){return t===e.elem}).length},ce.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=ce.css(e,"position"),c=ce(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=ce.css(e,"top"),u=ce.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),v(t)&&(t=t.call(e,n,ce.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},ce.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){ce.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===ce.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===ce.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=ce(e).offset()).top+=ce.css(e,"borderTopWidth",!0),i.left+=ce.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-ce.css(r,"marginTop",!0),left:t.left-i.left-ce.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===ce.css(e,"position"))e=e.offsetParent;return e||J})}}),ce.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;ce.fn[t]=function(e){return M(this,function(e,t,n){var r;if(y(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),ce.each(["top","left"],function(e,n){ce.cssHooks[n]=Ye(le.pixelPosition,function(e,t){if(t)return t=Ge(e,n),_e.test(t)?ce(e).position()[n]+"px":t})}),ce.each({Height:"height",Width:"width"},function(a,s){ce.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){ce.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return M(this,function(e,t,n){var r;return y(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?ce.css(e,t,i):ce.style(e,t,n,i)},s,n?e:void 0,n)}})}),ce.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){ce.fn[t]=function(e){return this.on(t,e)}}),ce.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.on("mouseenter",e).on("mouseleave",t||e)}}),ce.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){ce.fn[n]=function(e,t){return 0 +#include +#include + +#include + +constexpr int N_THREAD = 8; + +std::vector g_contexts(4, nullptr); + +std::mutex g_mutex; +std::thread g_worker; + +std::condition_variable g_cv; +bool g_running(false); +std::vector g_pcmf32; + +void set_move(const std::string & move, float prob) { + MAIN_THREAD_EM_ASM({ + setMove(UTF8ToString($0), $1) + }, move.c_str(), prob); +} + +bool get_audio(std::vector & audio) { + std::unique_lock lock(g_mutex); + g_cv.wait(lock, [] { return !g_running || !g_pcmf32.empty(); }); + if (!g_running) return false; + audio = std::move(g_pcmf32); + return true; +} + +void wchess_main(size_t i) { + struct whisper_full_params wparams = whisper_full_default_params(whisper_sampling_strategy::WHISPER_SAMPLING_GREEDY); + + wparams.n_threads = std::min(N_THREAD, (int) std::thread::hardware_concurrency()); + wparams.offset_ms = 0; + wparams.translate = false; + wparams.no_context = true; + wparams.single_segment = true; + wparams.print_realtime = false; + wparams.print_progress = false; + wparams.print_timestamps = true; + wparams.print_special = false; + wparams.no_timestamps = true; + + wparams.max_tokens = 32; + wparams.audio_ctx = 768; // partial encoder context for better performance + + wparams.temperature = 0.0f; + wparams.temperature_inc = 2.0f; + wparams.greedy.best_of = 1; + + wparams.beam_search.beam_size = 1; + + wparams.language = "en"; + + wparams.grammar_penalty = 100.0; + wparams.initial_prompt = "bishop to c3, rook to d4, knight to e5, d4 d5, knight to c3, c3, queen to d4, king b1, pawn to a1, bishop to b2, knight to c3,"; + + printf("command: using %d threads\n", wparams.n_threads); + + WChess::callbacks cb; + cb.get_audio = get_audio; + cb.set_move = set_move; + + WChess(g_contexts[i], wparams, cb, {}).run(); + if (i < g_contexts.size()) { + whisper_free(g_contexts[i]); + g_contexts[i] = nullptr; + } +} + +EMSCRIPTEN_BINDINGS(command) { + emscripten::function("init", emscripten::optional_override([](const std::string & path_model) { + for (size_t i = 0; i < g_contexts.size(); ++i) { + if (g_contexts[i] == nullptr) { + g_contexts[i] = whisper_init_from_file_with_params(path_model.c_str(), whisper_context_default_params()); + if (g_contexts[i] != nullptr) { + g_running = true; + if (g_worker.joinable()) { + g_worker.join(); + } + g_worker = std::thread([i]() { + wchess_main(i); + }); + + return i + 1; + } else { + return (size_t) 0; + } + } + } + + return (size_t) 0; + })); + + emscripten::function("free", emscripten::optional_override([](size_t /* index */) { + { + std::unique_lock lock(g_mutex); + g_running = false; + } + g_cv.notify_one(); + })); + + emscripten::function("set_audio", emscripten::optional_override([](size_t index, const emscripten::val & audio) { + --index; + + if (index >= g_contexts.size()) { + return -1; + } + + if (g_contexts[index] == nullptr) { + return -2; + } + + { + std::lock_guard lock(g_mutex); + const int n = audio["length"].as(); + + emscripten::val heap = emscripten::val::module_property("HEAPU8"); + emscripten::val memory = heap["buffer"]; + + g_pcmf32.resize(n); + + emscripten::val memoryView = audio["constructor"].new_(memory, reinterpret_cast(g_pcmf32.data()), n); + memoryView.call("set", audio); + } + g_cv.notify_one(); + + return 0; + })); +} From 9d5784957b927810ac4533265731fb7cd50f3c42 Mon Sep 17 00:00:00 2001 From: Fraxy V Date: Thu, 7 Dec 2023 05:36:45 +0200 Subject: [PATCH 02/15] wchess: fix allowed moves in check --- examples/wchess/libwchess/Chessboard.cpp | 1 + examples/wchess/libwchess/test-chessboard.cpp | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/examples/wchess/libwchess/Chessboard.cpp b/examples/wchess/libwchess/Chessboard.cpp index e321faeff83..f2e638953dc 100644 --- a/examples/wchess/libwchess/Chessboard.cpp +++ b/examples/wchess/libwchess/Chessboard.cpp @@ -765,6 +765,7 @@ void Chessboard::detectChecks() { std::set tmp; auto pos = traverse(p.pos(), d, Add(m_state->board, tmp, king.color())); if (pos == king.pos()) { + tmp.insert(p.pos()); if (!m_inCheck) { m_allowedInCheck = std::move(tmp); } diff --git a/examples/wchess/libwchess/test-chessboard.cpp b/examples/wchess/libwchess/test-chessboard.cpp index dc2ab3b152d..e46d0a166d0 100644 --- a/examples/wchess/libwchess/test-chessboard.cpp +++ b/examples/wchess/libwchess/test-chessboard.cpp @@ -87,6 +87,17 @@ int main() { ASSERT(chess.grammar().empty()); } + { + Chessboard chess; + ASSERT(chess.process("f4") == "f2-f4"); + ASSERT(chess.process("e5") == "e7-e5"); + ASSERT(chess.process("g4") == "g2-g4"); + ASSERT(chess.process("d5") == "d7-d5"); + ASSERT(chess.process("g1 f3") == "g1-f3"); + ASSERT(chess.process("queen to h4") == "d8-h4"); + ASSERT(!chess.grammar().empty()); + } + { Chessboard chess; ASSERT(chess.process("knight c3") == "b1-c3"); From be5cd736eb34ea2e1e7f806e3853b787afd6d971 Mon Sep 17 00:00:00 2001 From: Fraxy V Date: Fri, 8 Dec 2023 10:03:41 +0200 Subject: [PATCH 03/15] wchess: touchstart, touchend events --- examples/wchess/wchess.wasm/index-tmpl.html | 30 +++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/examples/wchess/wchess.wasm/index-tmpl.html b/examples/wchess/wchess.wasm/index-tmpl.html index 736db67d385..f9112d7ccc7 100644 --- a/examples/wchess/wchess.wasm/index-tmpl.html +++ b/examples/wchess/wchess.wasm/index-tmpl.html @@ -23,6 +23,22 @@ overflow-wrap: normal; overflow-x: scroll; } + .button { + background-color: #000000; + color: #FFFFFF; + padding: 20px; + border-radius: 10px; + -moz-border-radius: 10px; + -webkit-border-radius: 10px; + margin:10px; + width: 100px; + } + .center { + display: flex; + justify-content: center; + align-items: center; + width: 400px; + } @@ -68,8 +84,8 @@
-
- +
+

@@ -313,6 +329,16 @@ // } // }, true); + document.getElementById('toggler').addEventListener("touchstart", function(event){ + this.innerText = "Release"; + onStart(); + }, true); + + document.getElementById('toggler').addEventListener("touchend", function(event){ + this.innerText = "Hold"; + onStop(); + }, true) + document.getElementById('toggler').addEventListener('mousedown', function(event) { this.innerText = "Release"; onStart(); From cefd0a77ac0b7c020632afc5c3d870502b2230cc Mon Sep 17 00:00:00 2001 From: Fraxy V Date: Fri, 8 Dec 2023 17:57:26 +0200 Subject: [PATCH 04/15] wchess: css, disabled button --- examples/wchess/libwchess/Chessboard.cpp | 2 +- examples/wchess/wchess.wasm/index-tmpl.html | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/examples/wchess/libwchess/Chessboard.cpp b/examples/wchess/libwchess/Chessboard.cpp index f2e638953dc..ad2c58dcfd2 100644 --- a/examples/wchess/libwchess/Chessboard.cpp +++ b/examples/wchess/libwchess/Chessboard.cpp @@ -697,7 +697,7 @@ void Chessboard::flagUpdates(char pos_from, char pos_to) { } void Chessboard::updatePins(Piece& piece) { - if (piece.type() == Piece::Pawn || piece.type() == Piece::Knight) return; + if (piece.type() == Piece::Pawn || piece.type() == Piece::Knight || piece.type() == Piece::King) return; auto& enemyPieces = piece.color() ? m_state->whites : m_state->blacks; auto& enemyPins = piece.color() ? m_state->whitePins : m_state->blackPins; auto& king = enemyPieces.k; diff --git a/examples/wchess/wchess.wasm/index-tmpl.html b/examples/wchess/wchess.wasm/index-tmpl.html index f9112d7ccc7..1c109a19fd9 100644 --- a/examples/wchess/wchess.wasm/index-tmpl.html +++ b/examples/wchess/wchess.wasm/index-tmpl.html @@ -33,6 +33,16 @@ margin:10px; width: 100px; } + button[disabled]{ + background-color: #cccccc; + color: #666666; + padding: 20px; + border-radius: 10px; + -moz-border-radius: 10px; + -webkit-border-radius: 10px; + margin:10px; + width: 100px; + } .center { display: flex; justify-content: center; From 369226b14f89668d77b46a48fd4fd8505215ecae Mon Sep 17 00:00:00 2001 From: Georgi Gerganov Date: Sun, 10 Dec 2023 23:50:45 +0200 Subject: [PATCH 05/15] wchess : html touches --- .../{ => js/chessboard-1.0.0}/CHANGELOG.md | 0 .../{ => js/chessboard-1.0.0}/LICENSE.md | 0 .../{ => js/chessboard-1.0.0}/README.md | 0 .../{ => js/chessboard-1.0.0}/package.json | 0 examples/wchess/wchess.wasm/index-tmpl.html | 66 +++++++++++++++---- 5 files changed, 55 insertions(+), 11 deletions(-) rename examples/wchess/wchess.wasm/chessboardjs-1.0.0/{ => js/chessboard-1.0.0}/CHANGELOG.md (100%) rename examples/wchess/wchess.wasm/chessboardjs-1.0.0/{ => js/chessboard-1.0.0}/LICENSE.md (100%) rename examples/wchess/wchess.wasm/chessboardjs-1.0.0/{ => js/chessboard-1.0.0}/README.md (100%) rename examples/wchess/wchess.wasm/chessboardjs-1.0.0/{ => js/chessboard-1.0.0}/package.json (100%) diff --git a/examples/wchess/wchess.wasm/chessboardjs-1.0.0/CHANGELOG.md b/examples/wchess/wchess.wasm/chessboardjs-1.0.0/js/chessboard-1.0.0/CHANGELOG.md similarity index 100% rename from examples/wchess/wchess.wasm/chessboardjs-1.0.0/CHANGELOG.md rename to examples/wchess/wchess.wasm/chessboardjs-1.0.0/js/chessboard-1.0.0/CHANGELOG.md diff --git a/examples/wchess/wchess.wasm/chessboardjs-1.0.0/LICENSE.md b/examples/wchess/wchess.wasm/chessboardjs-1.0.0/js/chessboard-1.0.0/LICENSE.md similarity index 100% rename from examples/wchess/wchess.wasm/chessboardjs-1.0.0/LICENSE.md rename to examples/wchess/wchess.wasm/chessboardjs-1.0.0/js/chessboard-1.0.0/LICENSE.md diff --git a/examples/wchess/wchess.wasm/chessboardjs-1.0.0/README.md b/examples/wchess/wchess.wasm/chessboardjs-1.0.0/js/chessboard-1.0.0/README.md similarity index 100% rename from examples/wchess/wchess.wasm/chessboardjs-1.0.0/README.md rename to examples/wchess/wchess.wasm/chessboardjs-1.0.0/js/chessboard-1.0.0/README.md diff --git a/examples/wchess/wchess.wasm/chessboardjs-1.0.0/package.json b/examples/wchess/wchess.wasm/chessboardjs-1.0.0/js/chessboard-1.0.0/package.json similarity index 100% rename from examples/wchess/wchess.wasm/chessboardjs-1.0.0/package.json rename to examples/wchess/wchess.wasm/chessboardjs-1.0.0/js/chessboard-1.0.0/package.json diff --git a/examples/wchess/wchess.wasm/index-tmpl.html b/examples/wchess/wchess.wasm/index-tmpl.html index 1c109a19fd9..51d435d4922 100644 --- a/examples/wchess/wchess.wasm/index-tmpl.html +++ b/examples/wchess/wchess.wasm/index-tmpl.html @@ -1,7 +1,11 @@ - wchess : Voice assistant example using Whisper + WebAssembly + wchess : voice-controlled chess using Whisper + WebAssembly + + + +
- wchess : Voice assistant example using Whisper + WebAssembly + wchess : voice-controlled chess using Whisper + WebAssembly + +

+ + This is a demonstration of using Whisper to recognize voice commands in the browser. + +

+ + Usage:
+ +
    +
  • Hold the button and say a chess move (e.g. "e2-e4")
  • +
  • Release the button and wait for the move to be recognized
  • +
  • Repeat
  • +
+ + Examples:
+ +
    +
  • "d4"
  • +
  • "e2 e4"
  • +
  • "Knight to f3"
  • +
  • "Bishop to b5"
  • +
+ + Note that not all chess moves are supported. For example, castling and pawn promotion
+ currently do not work, but can be easily implemented. The main purpose of this example
+ is to demonstrate the capabilities of Whisper and it's application in the browser.

- You can find more about this project on GitHub. + Features:
+ +
    +
  • Model quantization for reduced memory footprint (~42MB)
  • +
  • Grammar-based sampling for improved recognition accuracy
  • +
+ + You can find more about this project on GitHub.

@@ -84,7 +128,7 @@

-
+
- +
- wchess : voice-controlled chess using Whisper + WebAssembly +
+ wchess : voice-controlled chess using Whisper + WebAssembly -

+

- This is a demonstration of using Whisper to recognize voice commands in the browser. + This is a demonstration of using Whisper to recognize voice commands in the browser. -

+

- Usage:
+ Usage:
-
    -
  • Hold the button and say a chess move (e.g. "e2-e4")
  • -
  • Release the button and wait for the move to be recognized
  • -
  • Repeat
  • -
+
    +
  • Hold the button and say a chess move (e.g. "Knight to c3")
  • +
  • Release the button and wait for the move to be recognized
  • +
  • Repeat
  • +
- Examples:
+ Examples:
-
    -
  • "d4"
  • -
  • "e2 e4"
  • -
  • "Knight to f3"
  • -
  • "Bishop to b5"
  • -
+
    +
  • "d4"
  • +
  • "e2 e4"
  • +
  • "Knight to f3"
  • +
  • "Bishop to b5"
  • +
- Note that not all chess moves are supported. For example, castling and pawn promotion
- currently do not work, but can be easily implemented. There could also be some bugs in
- the move handling logic in general.

- The main purpose of this example is to demonstrate the capabilities of whisper.cpp and
- its application in the browser. + + Note that not all chess moves are supported. For example, castling and pawn promotion + currently do not work, but can be easily implemented. There could also be some bugs in + the move handling logic in general. The main reason for that is to keep the implementation + simple. The assumption is that a real application would already have a proper move + validation logic in place.

-

+ The main purpose of this example is to demonstrate the capabilities of whisper.cpp and + its application in the browser. - Features:
+

-
    -
  • Model quantization for reduced memory footprint (~42MB)
  • -
  • Grammar-based sampling for improved recognition accuracy
  • -
+ Features:
- You can find more about this project on GitHub. +
    +
  • Model quantization for reduced memory footprint (~42MB)
  • +
  • Grammar-based sampling for improved recognition accuracy
  • +
-

+ You can find more about this project on GitHub. + +

- More examples: - main | - bench | - stream | - command | - talk | + More examples: + main | + bench | + stream | + command | + talk | -

+

+ +

@@ -130,7 +140,7 @@

-
+
diff --git a/examples/wchess/wchess.wasm/wchess.wasm.cpp b/examples/wchess/wchess.wasm/wchess.wasm.cpp index 4a3c628368b..f3fb084c333 100644 --- a/examples/wchess/wchess.wasm/wchess.wasm.cpp +++ b/examples/wchess/wchess.wasm/wchess.wasm.cpp @@ -12,6 +12,7 @@ std::mutex g_mutex; std::thread g_worker; std::condition_variable g_cv; + bool g_running(false); std::vector g_pcmf32; @@ -21,6 +22,12 @@ void set_move(const std::string & move, float prob) { }, move.c_str(), prob); } +void set_grammar(const std::string & grammar) { + MAIN_THREAD_EM_ASM({ + setGrammar(UTF8ToString($0)) + }, grammar.c_str()); +} + bool get_audio(std::vector & audio) { std::unique_lock lock(g_mutex); g_cv.wait(lock, [] { return !g_running || !g_pcmf32.empty(); }); @@ -62,8 +69,10 @@ void wchess_main(size_t i) { WChess::callbacks cb; cb.get_audio = get_audio; cb.set_move = set_move; + cb.set_grammar = set_grammar; WChess(g_contexts[i], wparams, cb, {}).run(); + if (i < g_contexts.size()) { whisper_free(g_contexts[i]); g_contexts[i] = nullptr; From 64020d673fe49174610ab3623eba944978ec59d7 Mon Sep 17 00:00:00 2001 From: Georgi Gerganov Date: Thu, 14 Dec 2023 15:49:02 +0200 Subject: [PATCH 13/15] wchess : update UX --- examples/wchess/wchess.wasm/index-tmpl.html | 92 ++++++++++++++------- 1 file changed, 62 insertions(+), 30 deletions(-) diff --git a/examples/wchess/wchess.wasm/index-tmpl.html b/examples/wchess/wchess.wasm/index-tmpl.html index 1d9d57b7e8f..ba3ab053804 100644 --- a/examples/wchess/wchess.wasm/index-tmpl.html +++ b/examples/wchess/wchess.wasm/index-tmpl.html @@ -65,7 +65,7 @@ - +
wchess : voice-controlled chess using Whisper + WebAssembly @@ -79,6 +79,8 @@ Usage:
    +
  • Select a Whisper model
  • +
  • Accept the microphone permission request if prompted
  • Hold the button and say a chess move (e.g. "Knight to c3")
  • Release the button and wait for the move to be recognized
  • Repeat
  • @@ -89,29 +91,30 @@
    • "d4"
    • "e2 e4"
    • -
    • "Knight to f3"
    • +
    • "Knight f3"
    • "Bishop to b5"
    + Features:
    + +
      +
    • Model quantization for reduced memory footprint (~42MB)
    • +
    • Grammar-based sampling for improved recognition accuracy
    • +
    + Note that not all chess moves are supported. For example, castling and pawn promotion currently do not work, but can be easily implemented. There could also be some bugs in the move handling logic in general. The main reason for that is to keep the implementation simple. The assumption is that a real application would already have a proper move - validation logic in place.

    + validation logic in place.

    The main purpose of this example is to demonstrate the capabilities of whisper.cpp and - its application in the browser. + its application in the browser for voice recognition. +

    - Features:
    - -
      -
    • Model quantization for reduced memory footprint (~42MB)
    • -
    • Grammar-based sampling for improved recognition accuracy
    • -
    - You can find more about this project on GitHub.

    @@ -131,35 +134,38 @@
    Whisper model: +

    - +
    -
    -
    - - - +
    +
    +
    + + + -
    +
    -
    - Status: not started +
    + Status: select model -
    - -
    +
    + +
    -
    [The moves will be displayed here]
    +
    [The grammar will be displayed here]
    -
    [The grammar will be displayed here]
    +
    [The moves will be displayed here]
    +

    @@ -229,7 +235,6 @@ instance = Module.init(model_file); if (instance) { - document.getElementById('toggler').disabled = false; setStatus('Ready'); printTextarea("js: whisper initialized, instance: " + instance); } @@ -279,6 +284,7 @@ model_whisper = 'tiny.en-q8_0'; document.getElementById('model-whisper-status').innerHTML = 'loading "' + model_whisper + '" ... '; + document.getElementById('fetch-whisper-tiny-en').style.display = 'none'; cbProgress = function(p) { let el = document.getElementById('fetch-whisper-progress'); @@ -291,6 +297,30 @@ }; loadRemote(url, dst, size_mb, cbProgress, storeFS, cbCancel, printTextarea); + + // init audio capture so that the user receives a permission request + { + let context = new AudioContext({ + sampleRate: 16000, + channelCount: 1, + echoCancellation: false, + autoGainControl: true, + noiseSuppression: true, + }); + navigator.mediaDevices.getUserMedia({audio: true, video: false}) + .then(function(s) { + stream = s; + stream.getTracks().forEach(function(track) { + track.stop(); + }); + }) + .catch(function(err) { + printTextarea('js: error getting audio stream: ' + err); + }); + context.close(); + } + + document.getElementById('toggler').style.display = 'block'; } // @@ -309,7 +339,9 @@ window.OfflineAudioContext = window.OfflineAudioContext || window.webkitOfflineAudioContext; function stopRecording() { - mediaRecorder.stop(); + if (mediaRecorder) { + mediaRecorder.stop(); + } } function startRecording() { From f6efe33b83b017d94952aa50a48b878ecb21051f Mon Sep 17 00:00:00 2001 From: Georgi Gerganov Date: Thu, 14 Dec 2023 15:52:42 +0200 Subject: [PATCH 14/15] wchess : add comment --- examples/wchess/wchess.wasm/index-tmpl.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/wchess/wchess.wasm/index-tmpl.html b/examples/wchess/wchess.wasm/index-tmpl.html index ba3ab053804..47452b312cc 100644 --- a/examples/wchess/wchess.wasm/index-tmpl.html +++ b/examples/wchess/wchess.wasm/index-tmpl.html @@ -110,7 +110,7 @@ validation logic in place.

    The main purpose of this example is to demonstrate the capabilities of whisper.cpp and - its application in the browser for voice recognition. + its application in the browser for voice recognition locally on your device.

    From 9217905e08d2d6dfed4ccf51b9a57d95eb97cc8f Mon Sep 17 00:00:00 2001 From: Georgi Gerganov Date: Thu, 14 Dec 2023 15:55:41 +0200 Subject: [PATCH 15/15] wchess : add README --- README.md | 1 + examples/wchess/README.md | 5 +++++ 2 files changed, 6 insertions(+) create mode 100644 examples/wchess/README.md diff --git a/README.md b/README.md index a01d53a056b..9b30a4cf64a 100644 --- a/README.md +++ b/README.md @@ -770,6 +770,7 @@ Some of the examples are even ported to run in the browser using WebAssembly. Ch | [bench](examples/bench) | [bench.wasm](examples/bench.wasm) | Benchmark the performance of Whisper on your machine | | [stream](examples/stream) | [stream.wasm](examples/stream.wasm) | Real-time transcription of raw microphone capture | | [command](examples/command) | [command.wasm](examples/command.wasm) | Basic voice assistant example for receiving voice commands from the mic | +| [wchess](examples/wchess) | [wchess.wasm](examples/wchess) | Voice-controlled chess | | [talk](examples/talk) | [talk.wasm](examples/talk.wasm) | Talk with a GPT-2 bot | | [talk-llama](examples/talk-llama) | | Talk with a LLaMA bot | | [whisper.objc](examples/whisper.objc) | | iOS mobile application using whisper.cpp | diff --git a/examples/wchess/README.md b/examples/wchess/README.md new file mode 100644 index 00000000000..b3c6526569a --- /dev/null +++ b/examples/wchess/README.md @@ -0,0 +1,5 @@ +# wchess.wasm + +Voice-controlled chess using Whisper + WebAssembly + +Online demo: https://whisper.ggerganov.com/wchess/