Persistent game #5

Merged
Simponic merged 6 commits from persistent-game into main 2023-01-17 16:00:18 -05:00
6 changed files with 210 additions and 49 deletions
Showing only changes of commit 118e62e760 - Show all commits

View File

@ -4,11 +4,6 @@ defmodule Chessh.Game do
import Ecto.Changeset import Ecto.Changeset
schema "games" do 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(:fen, :string)
field(:moves, :integer, default: 0) field(:moves, :integer, default: 0)
@ -30,11 +25,6 @@ defmodule Chessh.Game do
:turn, :turn,
:winner, :winner,
:status, :status,
:last_move,
:increment_sec,
:light_clock_ms,
:dark_clock_ms,
:last_move,
:light_player_id, :light_player_id,
:dark_player_id :dark_player_id
]) ])

View File

@ -61,6 +61,11 @@ defmodule Chessh.SSH.Client do
}} }}
end 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 @impl true
def handle_info(:quit, %State{} = state) do def handle_info(:quit, %State{} = state) do
{:stop, :normal, state} {:stop, :normal, state}
@ -92,8 +97,7 @@ defmodule Chessh.SSH.Client do
%State{ %State{
width: width, width: width,
height: height, height: height,
screen_pid: screen_pid, screen_pid: screen_pid
screen_state_initials: [_ | rest_initial]
} = state } = state
) do ) do
case keymap(data) do case keymap(data) do
@ -101,10 +105,7 @@ defmodule Chessh.SSH.Client do
{:stop, :normal, state} {:stop, :normal, state}
:previous_screen -> :previous_screen ->
[{prev_module, prev_state_initial} | _] = rest_initial {:noreply, go_back_one_screen(state)}
send(self(), {:set_screen_process, prev_module, prev_state_initial})
{:noreply, %State{state | screen_state_initials: rest_initial}}
action -> action ->
send(screen_pid, {:input, width, height, action}) send(screen_pid, {:input, width, height, action})
@ -186,4 +187,25 @@ defmodule Chessh.SSH.Client do
send(screen_pid, {:render, width, height}) send(screen_pid, {:render, width, height})
end end
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 end

View File

@ -100,7 +100,6 @@ defmodule Chessh.SSH.Client.Game do
game: new_game game: new_game
} }
Logger.debug("asdfaldfjalsdkfjal #{inspect(new_state)}")
{:ok, new_state} {:ok, new_state}
end end
@ -118,10 +117,7 @@ defmodule Chessh.SSH.Client.Game do
else: %{dark_player_id: player_session.player_id} else: %{dark_player_id: player_session.player_id}
), ),
%{ %{
fen: @default_fen, fen: @default_fen
increment_sec: 3,
light_clock_ms: 5 * 60 * 1000,
dark_clock_ms: 5 * 60 * 1000
} }
) )
) )
@ -172,7 +168,8 @@ defmodule Chessh.SSH.Client.Game do
move_from: move_from, move_from: move_from,
cursor: %{x: cursor_x, y: cursor_y} = cursor, cursor: %{x: cursor_x, y: cursor_y} = cursor,
client_pid: client_pid, client_pid: client_pid,
flipped: flipped flipped: flipped,
binbo_pid: binbo_pid
} = state } = state
) do ) do
new_cursor = new_cursor =
@ -196,10 +193,6 @@ defmodule Chessh.SSH.Client.Game do
{move_from, nil} {move_from, nil}
end end
if move_from && move_to do
attempt_move(move_from, move_to, state)
end
new_state = %State{ new_state = %State{
state state
| cursor: new_cursor, | cursor: new_cursor,
@ -213,6 +206,52 @@ defmodule Chessh.SSH.Client.Game do
flipped: if(action == "f", do: !flipped, else: flipped) 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)}) send(client_pid, {:send_to_ssh, render_state(new_state)})
new_state new_state
end end
@ -223,6 +262,13 @@ defmodule Chessh.SSH.Client.Game do
new_state new_state
end end
defp attempt_move(
from,
to,
%State{} = state
),
do: attempt_move(from, to, state, nil)
defp attempt_move( defp attempt_move(
from, from,
to, to,
@ -231,35 +277,69 @@ defmodule Chessh.SSH.Client.Game do
binbo_pid: binbo_pid, binbo_pid: binbo_pid,
flipped: flipped, flipped: flipped,
color: turn color: turn
} },
promotion
) do ) do
attempted_move = attempted_move =
if flipped, if(flipped,
do: "#{Renderer.to_chess_coord(flip(from))}#{Renderer.to_chess_coord(flip(to))}", do: "#{Renderer.to_chess_coord(flip(from))}#{Renderer.to_chess_coord(flip(to))}",
else: "#{Renderer.to_chess_coord(from)}#{Renderer.to_chess_coord(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 game = Repo.get(Game, game_id)
{:ok, :continue} ->
{:ok, fen} = :binbo.get_fen(binbo_pid)
game = Repo.get(Game, game_id)
{:ok, _new_game} = case :binbo.move(
Game.changeset(game, %{ binbo_pid,
fen: fen, attempted_move
moves: game.moves + 1, ) do
turn: if(game.turn == :dark, do: :light, else: :dark), {:ok, status} ->
last_move: DateTime.utc_now() case status do
}) :continue ->
|> Repo.update() {: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}) :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

View File

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

View File

@ -66,7 +66,9 @@ defmodule Chessh.SSH.Client.Game.Renderer do
id: game_id, id: game_id,
dark_player: %Player{username: dark_player}, dark_player: %Player{username: dark_player},
light_player: %Player{username: light_player}, light_player: %Player{username: light_player},
turn: turn turn: turn,
status: status,
winner: winner
} }
}) do }) do
rendered = [ rendered = [
@ -78,7 +80,16 @@ defmodule Chessh.SSH.Client.Game.Renderer do
"#{ANSI.default_color()} --vs-- ", "#{ANSI.default_color()} --vs-- ",
ANSI.format_fragment([@dark_piece_color, dark_player]), ANSI.format_fragment([@dark_piece_color, dark_player]),
ANSI.default_color(), 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
], ],
"" ""
) )

View File

@ -3,11 +3,6 @@ defmodule Chessh.Repo.Migrations.CreateGames do
def change do def change do
create table(:games) 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(:fen, :string)
add(:moves, :integer, default: 0) add(:moves, :integer, default: 0)