Persistent game #5
@ -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
|
||||||
])
|
])
|
||||||
|
@ -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
|
||||||
|
@ -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
|
|
||||||
{:ok, :continue} ->
|
|
||||||
{:ok, fen} = :binbo.get_fen(binbo_pid)
|
|
||||||
game = Repo.get(Game, game_id)
|
game = Repo.get(Game, game_id)
|
||||||
|
|
||||||
|
case :binbo.move(
|
||||||
|
binbo_pid,
|
||||||
|
attempted_move
|
||||||
|
) do
|
||||||
|
{:ok, status} ->
|
||||||
|
case status do
|
||||||
|
:continue ->
|
||||||
|
{:ok, fen} = :binbo.get_fen(binbo_pid)
|
||||||
|
|
||||||
{:ok, _new_game} =
|
{:ok, _new_game} =
|
||||||
Game.changeset(game, %{
|
Game.changeset(
|
||||||
|
game,
|
||||||
|
%{
|
||||||
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: DateTime.utc_now()
|
}
|
||||||
})
|
)
|
||||||
|> Repo.update()
|
|> 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
|
||||||
|
63
lib/chessh/ssh/client/game/promotion.ex
Normal file
63
lib/chessh/ssh/client/game/promotion.ex
Normal 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
|
@ -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(),
|
||||||
|
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"
|
", #{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
|
||||||
],
|
],
|
||||||
""
|
""
|
||||||
)
|
)
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user