From 22c4e0d5fd83720acaae99aa26a85f891151db5e Mon Sep 17 00:00:00 2001 From: Owen Rees Date: Sat, 20 Jun 2026 19:38:01 +0200 Subject: [PATCH 1/3] return SAN notation of last move --- assets/js/hooks/chessBoard.js | 22 +++++++------------- lib/chesstrainer_web/live/chess_test_live.ex | 11 +++++++--- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/assets/js/hooks/chessBoard.js b/assets/js/hooks/chessBoard.js index bbecd04..d288ba1 100644 --- a/assets/js/hooks/chessBoard.js +++ b/assets/js/hooks/chessBoard.js @@ -3,19 +3,16 @@ import { Chess } from 'chess.js'; export const ChessBoard = { mounted() { - // Initialize chess.js for local rule validation this.chess = new Chess(); - // Initialize Chessground this.ground = Chessground(this.el, { fen: this.chess.fen(), movable: { color: 'white', - free: false, // Don't allow completely arbitrary drops - dests: this.getValidDests() // Only allow legal chess moves + free: false, + dests: this.getValidDests() }, events: { - // Triggered when a piece is successfully dropped move: (orig, dest, _metadata) => { this.handleMove(orig, dest); } @@ -34,30 +31,27 @@ export const ChessBoard = { }, handleMove(orig, dest) { - // 1. Try to execute the move in chess.js state const move = this.chess.move({ from: orig, to: dest, promotion: 'q' }); if (move) { - // 2. Telemetry back up to Phoenix LiveView + const san = move.san this.pushEvent("move_played", { from: orig, to: dest, - fen: this.chess.fen() + san: san, + fen: this.chess.fen() }); - // 3. Determine whose turn it is now ('w' or 'b') const nextTurnColor = this.chess.turn() === 'w' ? 'white' : 'black'; - // 4. Force Chessground to update its internal state for the next player this.ground.set({ - turnColor: nextTurnColor, // Switches turn tracking + turnColor: nextTurnColor, movable: { - color: nextTurnColor, // 👈 THIS IS CRITICAL: Changes who is allowed to drag pieces! - dests: this.getValidDests() // Re-calculates valid legal squares for Black + color: nextTurnColor, + dests: this.getValidDests() } }); } else { - // If chess.js says it's an illegal move, snap the piece back instantly this.ground.set({ fen: this.chess.fen() }); } } diff --git a/lib/chesstrainer_web/live/chess_test_live.ex b/lib/chesstrainer_web/live/chess_test_live.ex index b944b05..017f383 100644 --- a/lib/chesstrainer_web/live/chess_test_live.ex +++ b/lib/chesstrainer_web/live/chess_test_live.ex @@ -3,7 +3,7 @@ defmodule ChesstrainerWeb.ChessTestLive do @impl true def mount(_params, _session, socket) do - {:ok, assign(socket, last_move: "None yet", total_moves: 0)} + {:ok, assign(socket, last_move: "None yet", total_moves: 0, san: "None yet")} end @impl true @@ -16,6 +16,7 @@ defmodule ChesstrainerWeb.ChessTestLive do

Last Played Move:

{@last_move}

+

{@san}

Total Moves:

@@ -37,11 +38,15 @@ defmodule ChesstrainerWeb.ChessTestLive do end @impl true - def handle_event("move_played", %{"from" => from, "to" => to, "fen" => _fen}, socket) do - # Receive the payload sent from the client JS Hook via this.pushEvent + def handle_event( + "move_played", + %{"from" => from, "to" => to, "fen" => _fen, "san" => san}, + socket + ) do {:noreply, socket |> assign(:last_move, "#{from} âž” #{to}") + |> assign(:san, san) |> assign(:total_moves, socket.assigns.total_moves + 1)} end end -- 2.52.0 From 014ca0d18ca1848f4b9ab1cde570c221866f715c Mon Sep 17 00:00:00 2001 From: Owen Rees Date: Sat, 20 Jun 2026 20:20:42 +0200 Subject: [PATCH 2/3] generate rudimentary moves list from SAN notation --- assets/js/hooks/chessBoard.js | 5 ++--- lib/chesstrainer_web/live/chess_test_live.ex | 17 +++++++++++------ 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/assets/js/hooks/chessBoard.js b/assets/js/hooks/chessBoard.js index d288ba1..4a6ae28 100644 --- a/assets/js/hooks/chessBoard.js +++ b/assets/js/hooks/chessBoard.js @@ -34,12 +34,11 @@ export const ChessBoard = { const move = this.chess.move({ from: orig, to: dest, promotion: 'q' }); if (move) { - const san = move.san this.pushEvent("move_played", { from: orig, to: dest, - san: san, - fen: this.chess.fen() + fen: this.chess.fen(), + san: move.san, }); const nextTurnColor = this.chess.turn() === 'w' ? 'white' : 'black'; diff --git a/lib/chesstrainer_web/live/chess_test_live.ex b/lib/chesstrainer_web/live/chess_test_live.ex index 017f383..08fb0fd 100644 --- a/lib/chesstrainer_web/live/chess_test_live.ex +++ b/lib/chesstrainer_web/live/chess_test_live.ex @@ -3,7 +3,7 @@ defmodule ChesstrainerWeb.ChessTestLive do @impl true def mount(_params, _session, socket) do - {:ok, assign(socket, last_move: "None yet", total_moves: 0, san: "None yet")} + {:ok, assign(socket, last_move: "None yet", san: "None yet", move_list: [])} end @impl true @@ -17,10 +17,13 @@ defmodule ChesstrainerWeb.ChessTestLive do

Last Played Move:

{@last_move}

{@san}

-
-
-

Total Moves:

-

{@total_moves}

+

+ {if length(@move_list) == 0 do + "No Moves" + else + Enum.reverse(@move_list) |> Enum.join(", ") + end} +

@@ -43,10 +46,12 @@ defmodule ChesstrainerWeb.ChessTestLive do %{"from" => from, "to" => to, "fen" => _fen, "san" => san}, socket ) do + current_moves = socket.assigns.move_list + {:noreply, socket |> assign(:last_move, "#{from} âž” #{to}") |> assign(:san, san) - |> assign(:total_moves, socket.assigns.total_moves + 1)} + |> assign(:move_list, [san | current_moves])} end end -- 2.52.0 From 42f8a88c618c09305b53b6a0378f66f7a5160f0e Mon Sep 17 00:00:00 2001 From: Owen Rees Date: Sat, 20 Jun 2026 20:33:18 +0200 Subject: [PATCH 3/3] load FEN string, parse through chess.js and load correct orientaton etc --- assets/js/hooks/chessBoard.js | 48 ++++++++++++++------ lib/chesstrainer_web/live/chess_test_live.ex | 30 ++++++++++++ 2 files changed, 64 insertions(+), 14 deletions(-) diff --git a/assets/js/hooks/chessBoard.js b/assets/js/hooks/chessBoard.js index 4a6ae28..a09f6c8 100644 --- a/assets/js/hooks/chessBoard.js +++ b/assets/js/hooks/chessBoard.js @@ -3,21 +3,41 @@ import { Chess } from 'chess.js'; export const ChessBoard = { mounted() { - this.chess = new Chess(); + this.chess = new Chess(); - this.ground = Chessground(this.el, { - fen: this.chess.fen(), - movable: { - color: 'white', - free: false, - dests: this.getValidDests() - }, - events: { - move: (orig, dest, _metadata) => { - this.handleMove(orig, dest); - } - } - }); + this.ground = Chessground(this.el, { + fen: this.chess.fen(), + movable: { + color: 'white', + free: false, + dests: this.getValidDests() + }, + events: { + move: (orig, dest, _metadata) => { + this.handleMove(orig, dest); + } + } + }); + + this.handleEvent("set_fen", ({ fen }) => { + try { + this.chess.load(fen); + + const currentTurnColor = this.chess.turn() === 'w' ? 'white' : 'black'; + + this.ground.set({ + fen: fen, + turnColor: currentTurnColor, + movable: { + color: currentTurnColor, + dests: this.getValidDests() + } + }); + } catch (error) { + console.error("Invalid FEN string submitted:", error); + alert("Invalid FEN configuration!"); + } + }); }, getValidDests() { diff --git a/lib/chesstrainer_web/live/chess_test_live.ex b/lib/chesstrainer_web/live/chess_test_live.ex index 08fb0fd..9815924 100644 --- a/lib/chesstrainer_web/live/chess_test_live.ex +++ b/lib/chesstrainer_web/live/chess_test_live.ex @@ -12,6 +12,24 @@ defmodule ChesstrainerWeb.ChessTestLive do

Chessground LiveView Test

+
+
+ +
+ +
+

Last Played Move:

@@ -40,6 +58,18 @@ defmodule ChesstrainerWeb.ChessTestLive do """ end + @impl true + def handle_event("load_fen", %{"fen" => fen}, socket) do + clean_fen = String.trim(fen) + + {:noreply, + socket + |> assign(:last_move, "Position Loaded") + |> assign(:san, "None yet") + |> assign(:move_list, []) + |> push_event("set_fen", %{fen: clean_fen})} + end + @impl true def handle_event( "move_played", -- 2.52.0