Add last move schema, add highlighting in renderer

This commit is contained in:
Elizabeth Hunt 2023-01-27 22:28:25 -07:00
parent 841ea41dae
commit af702514e8
Signed by: simponic
GPG Key ID: 52B3774857EB24B1
4 changed files with 120 additions and 57 deletions

View File

@ -6,6 +6,7 @@ defmodule Chessh.Game do
schema "games" do schema "games" do
field(:fen, :string) field(:fen, :string)
field(:moves, :integer, default: 0) field(:moves, :integer, default: 0)
field(:last_move, :string)
field(:turn, Ecto.Enum, values: [:light, :dark], default: :light) field(:turn, Ecto.Enum, values: [:light, :dark], default: :light)
field(:winner, Ecto.Enum, values: [:light, :dark, :none], default: :none) field(:winner, Ecto.Enum, values: [:light, :dark, :none], default: :none)
@ -25,6 +26,7 @@ defmodule Chessh.Game do
:turn, :turn,
:winner, :winner,
:status, :status,
:last_move,
:light_player_id, :light_player_id,
:dark_player_id :dark_player_id
]) ])

View File

@ -101,15 +101,24 @@ defmodule Chessh.SSH.Client.Game do
player_color = player_color =
if(new_game.light_player_id == player_session.player_id, do: :light, else: :dark) if(new_game.light_player_id == player_session.player_id, do: :light, else: :dark)
new_state = %State{ new_state =
(fn new_state ->
%State{
new_state
| highlighted: make_highlight_map(new_state)
}
end).(%State{
state state
| binbo_pid: binbo_pid, | binbo_pid: binbo_pid,
color: player_color, color: player_color,
game: new_game, game: new_game,
flipped: player_color == :dark 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} {:ok, new_state}
end end
@ -147,16 +156,26 @@ defmodule Chessh.SSH.Client.Game do
def handle_info( def handle_info(
{:new_move, move}, {: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 ) do
:binbo.move(binbo_pid, move) :binbo.move(binbo_pid, move)
new_state = %State{ new_state =
(fn new_state ->
%State{
new_state
| highlighted: make_highlight_map(new_state)
}
end).(%State{
state state
| game: Repo.get(Game, game_id) |> Repo.preload([:light_player, :dark_player]) | 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} {:noreply, new_state}
end end
@ -167,7 +186,7 @@ defmodule Chessh.SSH.Client.Game do
) do ) do
game = Repo.get(Game, game_id) |> Repo.preload([:light_player, :dark_player]) game = Repo.get(Game, game_id) |> Repo.preload([:light_player, :dark_player])
new_state = %State{state | game: game} 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} {:noreply, new_state}
end end
@ -199,7 +218,9 @@ defmodule Chessh.SSH.Client.Game do
end end
maybe_flipped_cursor_tup = 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 = piece_type =
:binbo_position.get_piece( :binbo_position.get_piece(
@ -226,21 +247,27 @@ defmodule Chessh.SSH.Client.Game do
{move_from, nil} {move_from, nil}
end end
new_state = %State{ 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 state
| cursor: new_cursor, | cursor: new_cursor,
move_from: new_move_from, 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, width: width,
height: height, height: height,
flipped: if(action == "f", do: !flipped, else: flipped) flipped: if(action == "f", do: !flipped, else: flipped)
} })
if move_from && move_to do 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 = promotion_possible =
case piece_type do case piece_type do
@ -278,13 +305,13 @@ defmodule Chessh.SSH.Client.Game do
end end
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 new_state
end end
def render(width, height, %State{client_pid: client_pid} = state) do def render(width, height, %State{client_pid: client_pid} = state) do
new_state = %State{state | width: width, height: height} 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 new_state
end end
@ -308,11 +335,14 @@ defmodule Chessh.SSH.Client.Game do
) do ) do
game = Repo.get(Game, game_id) 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 = attempted_move =
if(flipped, from <>
do: "#{Renderer.to_chess_coord(flip(from))}#{Renderer.to_chess_coord(flip(to))}", to <>
else: "#{Renderer.to_chess_coord(from)}#{Renderer.to_chess_coord(to)}"
) <>
if(promotion, do: promotion, else: "") if(promotion, do: promotion, else: "")
case :binbo.move( case :binbo.move(
@ -329,7 +359,8 @@ defmodule Chessh.SSH.Client.Game do
%{ %{
fen: fen, fen: fen,
moves: game.moves + 1, 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) changeset_from_status(status)
) )
@ -338,28 +369,17 @@ defmodule Chessh.SSH.Client.Game do
:syn.publish(:games, {:game, game_id}, {:new_move, attempted_move}) :syn.publish(:games, {:game, game_id}, {:new_move, attempted_move})
x -> _ ->
Logger.debug(inspect(x))
nil nil
end end
end end
defp attempt_move(_, _, _, _) do defp attempt_move(_, _, _, _) do
Logger.debug("No matching clause for move attempt - must be illegal?") Logger.debug("No matching clause for move attempt - must be illegal?")
nil nil
end 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 defp changeset_from_status(game_status) do
case game_status do case game_status do
:continue -> :continue ->
@ -375,4 +395,26 @@ defmodule Chessh.SSH.Client.Game do
%{status: :winner, winner: :dark} %{status: :winner, winner: :dark}
end end
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 end

View File

@ -10,8 +10,8 @@ defmodule Chessh.SSH.Client.Game.Renderer do
@tile_height 4 @tile_height 4
@previous_move_background ANSI.light_yellow_background() @previous_move_background ANSI.light_yellow_background()
@from_select_background ANSI.light_magenta_background() @from_select_background ANSI.light_green_background()
@to_select_background ANSI.light_magenta_background() @to_select_background ANSI.light_green_background()
@dark_piece_color ANSI.red() @dark_piece_color ANSI.red()
@light_piece_color ANSI.light_cyan() @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}" "#{List.to_string([?a + x])}#{@chess_board_height - y}"
end 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( def render_board_state(
fen,
%Game.State{ %Game.State{
game: game:
%Chessh.Game{ %Chessh.Game{
@ -36,14 +44,13 @@ defmodule Chessh.SSH.Client.Game.Renderer do
} = state } = state
) )
when is_nil(light_player) do when is_nil(light_player) do
render_board_state(fen, %Game.State{ render_board_state(%Game.State{
state state
| game: %Chessh.Game{game | light_player: %Player{username: "(no opponent)"}} | game: %Chessh.Game{game | light_player: %Player{username: "(no opponent)"}}
}) })
end end
def render_board_state( def render_board_state(
fen,
%Game.State{ %Game.State{
game: game:
%Chessh.Game{ %Chessh.Game{
@ -52,18 +59,21 @@ defmodule Chessh.SSH.Client.Game.Renderer do
} = state } = state
) )
when is_nil(dark_player) do when is_nil(dark_player) do
render_board_state(fen, %Game.State{ render_board_state(%Game.State{
state state
| game: %Chessh.Game{game | dark_player: %Player{username: "(no opponent)"}} | game: %Chessh.Game{game | dark_player: %Player{username: "(no opponent)"}}
}) })
end end
def render_board_state(fen, %Game.State{ def render_board_state(%Game.State{
width: _width, width: _width,
height: _height, height: _height,
highlighted: highlighted, highlighted: highlighted,
flipped: flipped, flipped: flipped,
game: %Chessh.Game{} = game game:
%Chessh.Game{
fen: fen
} = game
}) do }) do
rendered = [ rendered = [
ANSI.clear_line(), ANSI.clear_line(),

View File

@ -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