From 118e62e760c2bbd62a18b249e71c920d8e6d50e9 Mon Sep 17 00:00:00 2001 From: Logan Hunt Date: Tue, 17 Jan 2023 13:14:39 -0700 Subject: [PATCH] Add promotion --- lib/chessh/schema/game.ex | 10 -- lib/chessh/ssh/client/client.ex | 34 ++++- lib/chessh/ssh/client/game/game.ex | 132 ++++++++++++++---- lib/chessh/ssh/client/game/promotion.ex | 63 +++++++++ lib/chessh/ssh/client/game/renderer.ex | 15 +- .../20230115201050_create_games.exs | 5 - 6 files changed, 210 insertions(+), 49 deletions(-) create mode 100644 lib/chessh/ssh/client/game/promotion.ex diff --git a/lib/chessh/schema/game.ex b/lib/chessh/schema/game.ex index 499c1b3..b6ff327 100644 --- a/lib/chessh/schema/game.ex +++ b/lib/chessh/schema/game.ex @@ -4,11 +4,6 @@ defmodule Chessh.Game do import Ecto.Changeset schema "games" do - field(:increment_sec, :integer) - field(:light_clock_ms, :integer) - field(:dark_clock_ms, :integer) - field(:last_move, :utc_datetime_usec) - field(:fen, :string) field(:moves, :integer, default: 0) @@ -30,11 +25,6 @@ defmodule Chessh.Game do :turn, :winner, :status, - :last_move, - :increment_sec, - :light_clock_ms, - :dark_clock_ms, - :last_move, :light_player_id, :dark_player_id ]) diff --git a/lib/chessh/ssh/client/client.ex b/lib/chessh/ssh/client/client.ex index 21583af..72bbb66 100644 --- a/lib/chessh/ssh/client/client.ex +++ b/lib/chessh/ssh/client/client.ex @@ -61,6 +61,11 @@ defmodule Chessh.SSH.Client do }} end + @impl true + def handle_info({:go_back_one_screen, previous_state}, %State{} = state) do + {:noreply, go_back_one_screen(state, previous_state)} + end + @impl true def handle_info(:quit, %State{} = state) do {:stop, :normal, state} @@ -92,8 +97,7 @@ defmodule Chessh.SSH.Client do %State{ width: width, height: height, - screen_pid: screen_pid, - screen_state_initials: [_ | rest_initial] + screen_pid: screen_pid } = state ) do case keymap(data) do @@ -101,10 +105,7 @@ defmodule Chessh.SSH.Client do {:stop, :normal, state} :previous_screen -> - [{prev_module, prev_state_initial} | _] = rest_initial - send(self(), {:set_screen_process, prev_module, prev_state_initial}) - - {:noreply, %State{state | screen_state_initials: rest_initial}} + {:noreply, go_back_one_screen(state)} action -> send(screen_pid, {:input, width, height, action}) @@ -186,4 +187,25 @@ defmodule Chessh.SSH.Client do send(screen_pid, {:render, width, height}) end end + + defp go_back_one_screen( + %State{ + screen_state_initials: [_ | rest_initial] + } = state, + previous_state + ) do + [{prev_module, prev_state_initial} | _] = rest_initial + + send( + self(), + {:set_screen_process, prev_module, + if(is_nil(previous_state), do: prev_state_initial, else: previous_state)} + ) + + %State{state | screen_state_initials: rest_initial} + end + + defp go_back_one_screen(%State{} = state) do + go_back_one_screen(state, nil) + end end diff --git a/lib/chessh/ssh/client/game/game.ex b/lib/chessh/ssh/client/game/game.ex index fb38316..386fcb3 100644 --- a/lib/chessh/ssh/client/game/game.ex +++ b/lib/chessh/ssh/client/game/game.ex @@ -100,7 +100,6 @@ defmodule Chessh.SSH.Client.Game do game: new_game } - Logger.debug("asdfaldfjalsdkfjal #{inspect(new_state)}") {:ok, new_state} end @@ -118,10 +117,7 @@ defmodule Chessh.SSH.Client.Game do else: %{dark_player_id: player_session.player_id} ), %{ - fen: @default_fen, - increment_sec: 3, - light_clock_ms: 5 * 60 * 1000, - dark_clock_ms: 5 * 60 * 1000 + fen: @default_fen } ) ) @@ -172,7 +168,8 @@ defmodule Chessh.SSH.Client.Game do move_from: move_from, cursor: %{x: cursor_x, y: cursor_y} = cursor, client_pid: client_pid, - flipped: flipped + flipped: flipped, + binbo_pid: binbo_pid } = state ) do new_cursor = @@ -196,10 +193,6 @@ defmodule Chessh.SSH.Client.Game do {move_from, nil} end - if move_from && move_to do - attempt_move(move_from, move_to, state) - end - new_state = %State{ state | cursor: new_cursor, @@ -213,6 +206,52 @@ defmodule Chessh.SSH.Client.Game do flipped: if(action == "f", do: !flipped, else: flipped) } + if move_from && move_to do + maybe_flipped_from = if flipped, do: flip(move_from), else: move_from + maybe_flipped_to = if flipped, do: flip(move_to), else: move_to + + piece_type = + :binbo_position.get_piece( + :binbo_board.notation_to_index(Renderer.to_chess_coord(maybe_flipped_from)), + :binbo.game_state(binbo_pid) + ) + + promotion_possible = + case piece_type do + 1 -> + # Light pawn + {y, _} = maybe_flipped_to + y == 0 + + 17 -> + # Dark pawn + {y, _} = maybe_flipped_to + y == Renderer.chess_board_height() - 1 + + _ -> + false + end + + if promotion_possible do + send( + client_pid, + {:set_screen_process, Chessh.SSH.Client.Game.PromotionScreen, + %Chessh.SSH.Client.Game.PromotionScreen.State{ + client_pid: client_pid, + game_pid: self(), + game_state: new_state + }} + ) + + receive do + {:promotion, promotion} -> + attempt_move(move_from, move_to, state, promotion) + end + else + attempt_move(move_from, move_to, state) + end + end + send(client_pid, {:send_to_ssh, render_state(new_state)}) new_state end @@ -223,6 +262,13 @@ defmodule Chessh.SSH.Client.Game do new_state end + defp attempt_move( + from, + to, + %State{} = state + ), + do: attempt_move(from, to, state, nil) + defp attempt_move( from, to, @@ -231,35 +277,69 @@ defmodule Chessh.SSH.Client.Game do binbo_pid: binbo_pid, flipped: flipped, color: turn - } + }, + promotion ) do attempted_move = - if flipped, + 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)}" + ) <> + if(promotion, do: promotion, else: "") - case :binbo.move(binbo_pid, attempted_move) do - {:ok, :continue} -> - {:ok, fen} = :binbo.get_fen(binbo_pid) - game = Repo.get(Game, game_id) + game = Repo.get(Game, game_id) - {:ok, _new_game} = - Game.changeset(game, %{ - fen: fen, - moves: game.moves + 1, - turn: if(game.turn == :dark, do: :light, else: :dark), - last_move: DateTime.utc_now() - }) - |> Repo.update() + case :binbo.move( + binbo_pid, + attempted_move + ) do + {:ok, status} -> + case status do + :continue -> + {:ok, fen} = :binbo.get_fen(binbo_pid) + + {:ok, _new_game} = + Game.changeset( + game, + %{ + fen: fen, + moves: game.moves + 1, + turn: if(game.turn == :dark, do: :light, else: :dark) + } + ) + |> Repo.update() + + {:draw, _} -> + Game.changeset( + game, + %{status: :draw} + ) + |> Repo.update() + + {:checkmate, :white_wins} -> + Game.changeset( + game, + %{status: :winner, winner: :light} + ) + |> Repo.update() + + {:checkmate, :black_wins} -> + Game.changeset( + game, + %{status: :winner, winner: :dark} + ) + |> Repo.update() + end :syn.publish(:games, {:game, game_id}, {:new_move, attempted_move}) - _ -> + x -> + Logger.debug(inspect(x)) nil end end - defp attempt_move(_, _, _) do + defp attempt_move(_, _, _, _) do Logger.debug("No matching clause for move attempt - must be illegal?") nil end diff --git a/lib/chessh/ssh/client/game/promotion.ex b/lib/chessh/ssh/client/game/promotion.ex new file mode 100644 index 0000000..c4cece6 --- /dev/null +++ b/lib/chessh/ssh/client/game/promotion.ex @@ -0,0 +1,63 @@ +defmodule Chessh.SSH.Client.Game.PromotionScreen do + alias Chessh.Utils + alias Chessh.SSH.Client.Game + alias IO.ANSI + + defmodule State do + defstruct game_pid: nil, + client_pid: nil, + game_state: nil + end + + use Chessh.SSH.Client.Screen + + @promotion_screen Utils.clear_codes() ++ + [ + "Press the key associated to the piece you'd like to promote", + " 'q' - queen", + " 'r' - rook", + " 'n' - knight", + " 'b' - bishop" + ] + + def init([%State{} = state | _]) do + {:ok, state} + end + + def render(_, _, %State{client_pid: client_pid} = state) do + rendered = + Enum.flat_map( + Enum.zip(0..(length(@promotion_screen) - 1), @promotion_screen), + fn {i, promotion} -> + [ + ANSI.cursor(i, 0), + promotion + ] + end + ) ++ [ANSI.home()] + + send( + client_pid, + {:send_to_ssh, rendered} + ) + + state + end + + def input( + _, + _, + action, + %State{client_pid: client_pid, game_pid: game_pid, game_state: %Game.State{} = game_state} = + state + ) do + promotion = if Enum.member?(["q", "b", "n", "r"], action), do: action, else: nil + + if promotion do + send(client_pid, {:go_back_one_screen, game_state}) + send(game_pid, {:promotion, promotion}) + end + + state + end +end diff --git a/lib/chessh/ssh/client/game/renderer.ex b/lib/chessh/ssh/client/game/renderer.ex index ab2b2b8..f8cf689 100644 --- a/lib/chessh/ssh/client/game/renderer.ex +++ b/lib/chessh/ssh/client/game/renderer.ex @@ -66,7 +66,9 @@ defmodule Chessh.SSH.Client.Game.Renderer do id: game_id, dark_player: %Player{username: dark_player}, light_player: %Player{username: light_player}, - turn: turn + turn: turn, + status: status, + winner: winner } }) do rendered = [ @@ -78,7 +80,16 @@ defmodule Chessh.SSH.Client.Game.Renderer do "#{ANSI.default_color()} --vs-- ", ANSI.format_fragment([@dark_piece_color, dark_player]), ANSI.default_color(), - ", #{ANSI.format_fragment([if(turn == :light, do: @light_piece_color, else: @dark_piece_color), if(turn == :dark, do: dark_player, else: light_player)])} to move" + case status do + :continue -> + ", #{ANSI.format_fragment([if(turn == :light, do: @light_piece_color, else: @dark_piece_color), if(turn == :dark, do: dark_player, else: light_player)])} to move" + + :draw -> + "ended in a draw" + + :winner -> + ", #{ANSI.format_fragment([if(winner == :light, do: @light_piece_color, else: @dark_piece_color), if(winner == :dark, do: dark_player, else: light_player)])} won!" + end ], "" ) diff --git a/priv/repo/migrations/20230115201050_create_games.exs b/priv/repo/migrations/20230115201050_create_games.exs index 2ed13cb..418d1de 100644 --- a/priv/repo/migrations/20230115201050_create_games.exs +++ b/priv/repo/migrations/20230115201050_create_games.exs @@ -3,11 +3,6 @@ defmodule Chessh.Repo.Migrations.CreateGames do def change do create table(:games) do - add(:increment_sec, :integer) - add(:light_clock_ms, :integer) - add(:dark_clock_ms, :integer) - add(:last_move, :utc_datetime_usec, null: true) - add(:fen, :string) add(:moves, :integer, default: 0)