diff --git a/lib/chessh/schema/game.ex b/lib/chessh/schema/game.ex index b6ff327..b7893f1 100644 --- a/lib/chessh/schema/game.ex +++ b/lib/chessh/schema/game.ex @@ -6,6 +6,7 @@ defmodule Chessh.Game do schema "games" do field(:fen, :string) field(:moves, :integer, default: 0) + field(:last_move, :string) field(:turn, Ecto.Enum, values: [:light, :dark], default: :light) field(:winner, Ecto.Enum, values: [:light, :dark, :none], default: :none) @@ -25,6 +26,7 @@ defmodule Chessh.Game do :turn, :winner, :status, + :last_move, :light_player_id, :dark_player_id ]) diff --git a/lib/chessh/ssh/client/game/game.ex b/lib/chessh/ssh/client/game/game.ex index e07a8b3..11b00b5 100644 --- a/lib/chessh/ssh/client/game/game.ex +++ b/lib/chessh/ssh/client/game/game.ex @@ -101,15 +101,24 @@ defmodule Chessh.SSH.Client.Game do player_color = if(new_game.light_player_id == player_session.player_id, do: :light, else: :dark) - new_state = %State{ - state - | binbo_pid: binbo_pid, - color: player_color, - game: new_game, - flipped: player_color == :dark - } + new_state = + (fn new_state -> + %State{ + new_state + | highlighted: make_highlight_map(new_state) + } + end).(%State{ + state + | binbo_pid: binbo_pid, + color: player_color, + game: new_game, + flipped: player_color == :dark + }) - send(client_pid, {:send_to_ssh, [Utils.clear_codes() | render_state(new_state)]}) + send( + client_pid, + {:send_to_ssh, [Utils.clear_codes() | Renderer.render_board_state(new_state)]} + ) {:ok, new_state} end @@ -147,16 +156,26 @@ defmodule Chessh.SSH.Client.Game do def handle_info( {:new_move, move}, - %State{game: %Game{id: game_id}, client_pid: client_pid, binbo_pid: binbo_pid} = state + %State{ + game: %Game{id: game_id}, + client_pid: client_pid, + binbo_pid: binbo_pid + } = state ) do :binbo.move(binbo_pid, move) - new_state = %State{ - state - | game: Repo.get(Game, game_id) |> Repo.preload([:light_player, :dark_player]) - } + new_state = + (fn new_state -> + %State{ + new_state + | highlighted: make_highlight_map(new_state) + } + end).(%State{ + state + | game: Repo.get(Game, game_id) |> Repo.preload([:light_player, :dark_player]) + }) - send(client_pid, {:send_to_ssh, render_state(new_state)}) + send(client_pid, {:send_to_ssh, Renderer.render_board_state(new_state)}) {:noreply, new_state} end @@ -167,7 +186,7 @@ defmodule Chessh.SSH.Client.Game do ) do game = Repo.get(Game, game_id) |> Repo.preload([:light_player, :dark_player]) new_state = %State{state | game: game} - send(client_pid, {:send_to_ssh, render_state(new_state)}) + send(client_pid, {:send_to_ssh, Renderer.render_board_state(new_state)}) {:noreply, new_state} end @@ -199,7 +218,9 @@ defmodule Chessh.SSH.Client.Game do end maybe_flipped_cursor_tup = - if flipped, do: flip({new_cursor.y, new_cursor.x}), else: {new_cursor.y, new_cursor.x} + if flipped, + do: Renderer.flip({new_cursor.y, new_cursor.x}), + else: {new_cursor.y, new_cursor.x} piece_type = :binbo_position.get_piece( @@ -226,21 +247,27 @@ defmodule Chessh.SSH.Client.Game do {move_from, nil} end - new_state = %State{ - state - | cursor: new_cursor, - move_from: new_move_from, - highlighted: %{ - {new_cursor.y, new_cursor.x} => Renderer.to_select_background(), - new_move_from => Renderer.from_select_background() - }, - width: width, - height: height, - flipped: if(action == "f", do: !flipped, else: flipped) - } + new_state = + (fn new_state -> + %State{ + new_state + | highlighted: + make_highlight_map(new_state, %{ + {new_cursor.y, new_cursor.x} => Renderer.to_select_background(), + new_move_from => Renderer.from_select_background() + }) + } + end).(%State{ + state + | cursor: new_cursor, + move_from: new_move_from, + width: width, + height: height, + flipped: if(action == "f", do: !flipped, else: flipped) + }) if move_from && move_to do - maybe_flipped_to = if flipped, do: flip(move_to), else: move_to + maybe_flipped_to = if flipped, do: Renderer.flip(move_to), else: move_to promotion_possible = case piece_type do @@ -278,13 +305,13 @@ defmodule Chessh.SSH.Client.Game do end end - send(client_pid, {:send_to_ssh, render_state(new_state)}) + send(client_pid, {:send_to_ssh, Renderer.render_board_state(new_state)}) new_state end def render(width, height, %State{client_pid: client_pid} = state) do new_state = %State{state | width: width, height: height} - send(client_pid, {:send_to_ssh, render_state(new_state)}) + send(client_pid, {:send_to_ssh, Renderer.render_board_state(new_state)}) new_state end @@ -308,11 +335,14 @@ defmodule Chessh.SSH.Client.Game do ) do game = Repo.get(Game, game_id) + [from, to] = + [from, to] + |> Enum.map(fn coord -> if flipped, do: Renderer.flip(coord), else: coord end) + |> Enum.map(&Renderer.to_chess_coord/1) + attempted_move = - if(flipped, - do: "#{Renderer.to_chess_coord(flip(from))}#{Renderer.to_chess_coord(flip(to))}", - else: "#{Renderer.to_chess_coord(from)}#{Renderer.to_chess_coord(to)}" - ) <> + from <> + to <> if(promotion, do: promotion, else: "") case :binbo.move( @@ -329,7 +359,8 @@ defmodule Chessh.SSH.Client.Game do %{ fen: fen, moves: game.moves + 1, - turn: if(game.turn == :dark, do: :light, else: :dark) + turn: if(game.turn == :dark, do: :light, else: :dark), + last_move: attempted_move }, changeset_from_status(status) ) @@ -338,28 +369,17 @@ defmodule Chessh.SSH.Client.Game do :syn.publish(:games, {:game, game_id}, {:new_move, attempted_move}) - x -> - Logger.debug(inspect(x)) + _ -> nil end end defp attempt_move(_, _, _, _) do Logger.debug("No matching clause for move attempt - must be illegal?") + nil end - defp flip({y, x}), - do: {Renderer.chess_board_height() - 1 - y, Renderer.chess_board_width() - 1 - x} - - defp render_state( - %State{ - game: %Game{fen: fen} - } = state - ) do - Renderer.render_board_state(fen, state) - end - defp changeset_from_status(game_status) do case game_status do :continue -> @@ -375,4 +395,26 @@ defmodule Chessh.SSH.Client.Game do %{status: :winner, winner: :dark} end end + + defp make_highlight_map( + %State{ + game: %Game{last_move: last_move}, + flipped: flipped + }, + extra_highlights \\ %{} + ) do + if last_move do + [prev_move_from, prev_move_to] = + [String.slice(last_move, 0..1), String.slice(last_move, 2..4)] + |> Enum.map(fn coord -> Renderer.from_chess_coord(coord, flipped) end) + + %{ + prev_move_from => Renderer.previous_move_background(), + prev_move_to => Renderer.previous_move_background() + } + else + %{} + end + |> Map.merge(extra_highlights) + end end diff --git a/lib/chessh/ssh/client/game/renderer.ex b/lib/chessh/ssh/client/game/renderer.ex index 161780a..17215ea 100644 --- a/lib/chessh/ssh/client/game/renderer.ex +++ b/lib/chessh/ssh/client/game/renderer.ex @@ -10,8 +10,8 @@ defmodule Chessh.SSH.Client.Game.Renderer do @tile_height 4 @previous_move_background ANSI.light_yellow_background() - @from_select_background ANSI.light_magenta_background() - @to_select_background ANSI.light_magenta_background() + @from_select_background ANSI.light_green_background() + @to_select_background ANSI.light_green_background() @dark_piece_color ANSI.red() @light_piece_color ANSI.light_cyan() @@ -26,8 +26,16 @@ defmodule Chessh.SSH.Client.Game.Renderer do "#{List.to_string([?a + x])}#{@chess_board_height - y}" end + def flip({y, x}), + do: {@chess_board_height - 1 - y, @chess_board_width - 1 - x} + + def from_chess_coord(s, flipped \\ false) do + [x, y | _] = String.downcase(s) |> String.to_charlist() + coords = {?8 - y, x - ?i + @chess_board_width} + if flipped, do: flip(coords), else: coords + end + def render_board_state( - fen, %Game.State{ game: %Chessh.Game{ @@ -36,14 +44,13 @@ defmodule Chessh.SSH.Client.Game.Renderer do } = state ) when is_nil(light_player) do - render_board_state(fen, %Game.State{ + render_board_state(%Game.State{ state | game: %Chessh.Game{game | light_player: %Player{username: "(no opponent)"}} }) end def render_board_state( - fen, %Game.State{ game: %Chessh.Game{ @@ -52,18 +59,21 @@ defmodule Chessh.SSH.Client.Game.Renderer do } = state ) when is_nil(dark_player) do - render_board_state(fen, %Game.State{ + render_board_state(%Game.State{ state | game: %Chessh.Game{game | dark_player: %Player{username: "(no opponent)"}} }) end - def render_board_state(fen, %Game.State{ + def render_board_state(%Game.State{ width: _width, height: _height, highlighted: highlighted, flipped: flipped, - game: %Chessh.Game{} = game + game: + %Chessh.Game{ + fen: fen + } = game }) do rendered = [ ANSI.clear_line(), diff --git a/priv/repo/migrations/20230128032207_add_last_move_to_game.exs b/priv/repo/migrations/20230128032207_add_last_move_to_game.exs new file mode 100644 index 0000000..db6199d --- /dev/null +++ b/priv/repo/migrations/20230128032207_add_last_move_to_game.exs @@ -0,0 +1,9 @@ +defmodule Chessh.Repo.Migrations.AddLastMoveToGame do + use Ecto.Migration + + def change do + alter table(:games) do + add(:last_move, :string, null: true) + end + end +end