Move history #18
@ -12,6 +12,8 @@ defmodule Chessh.Game do
|
|||||||
field(:winner, Ecto.Enum, values: [:light, :dark, :none], default: :none)
|
field(:winner, Ecto.Enum, values: [:light, :dark, :none], default: :none)
|
||||||
field(:status, Ecto.Enum, values: [:continue, :draw, :winner], default: :continue)
|
field(:status, Ecto.Enum, values: [:continue, :draw, :winner], default: :continue)
|
||||||
|
|
||||||
|
field(:game_moves, :string)
|
||||||
|
|
||||||
belongs_to(:light_player, Player, foreign_key: :light_player_id)
|
belongs_to(:light_player, Player, foreign_key: :light_player_id)
|
||||||
belongs_to(:dark_player, Player, foreign_key: :dark_player_id)
|
belongs_to(:dark_player, Player, foreign_key: :dark_player_id)
|
||||||
|
|
||||||
@ -31,7 +33,8 @@ defmodule Chessh.Game do
|
|||||||
:last_move,
|
:last_move,
|
||||||
:light_player_id,
|
:light_player_id,
|
||||||
:dark_player_id,
|
:dark_player_id,
|
||||||
:discord_thread_id
|
:discord_thread_id,
|
||||||
|
:game_moves
|
||||||
])
|
])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -12,8 +12,6 @@ defmodule Chessh.SSH.Client.Game do
|
|||||||
game: nil,
|
game: nil,
|
||||||
client_pid: nil,
|
client_pid: nil,
|
||||||
binbo_pid: nil,
|
binbo_pid: nil,
|
||||||
width: 0,
|
|
||||||
height: 0,
|
|
||||||
flipped: false,
|
flipped: false,
|
||||||
color: nil,
|
color: nil,
|
||||||
player_session: nil
|
player_session: nil
|
||||||
@ -25,7 +23,6 @@ defmodule Chessh.SSH.Client.Game do
|
|||||||
:syn.add_node_to_scopes([:games])
|
:syn.add_node_to_scopes([:games])
|
||||||
:ok = :syn.join(:games, {:game, game_id}, self())
|
:ok = :syn.join(:games, {:game, game_id}, self())
|
||||||
|
|
||||||
:binbo.start()
|
|
||||||
{:ok, binbo_pid} = :binbo.new_server()
|
{:ok, binbo_pid} = :binbo.new_server()
|
||||||
:binbo.new_game(binbo_pid, fen)
|
:binbo.new_game(binbo_pid, fen)
|
||||||
|
|
||||||
@ -177,11 +174,9 @@ defmodule Chessh.SSH.Client.Game do
|
|||||||
flipped: player_color == :dark
|
flipped: player_color == :dark
|
||||||
})
|
})
|
||||||
|
|
||||||
send(
|
# Clear screen and do initial render
|
||||||
client_pid,
|
send(client_pid, {:send_to_ssh, Utils.clear_codes()})
|
||||||
{:send_to_ssh, [Utils.clear_codes() | Renderer.render_board_state(new_state)]}
|
render(new_state)
|
||||||
)
|
|
||||||
|
|
||||||
{:ok, new_state}
|
{:ok, new_state}
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -227,8 +222,8 @@ defmodule Chessh.SSH.Client.Game do
|
|||||||
end
|
end
|
||||||
|
|
||||||
def input(
|
def input(
|
||||||
width,
|
_width,
|
||||||
height,
|
_height,
|
||||||
action,
|
action,
|
||||||
%State{
|
%State{
|
||||||
move_from: move_from,
|
move_from: move_from,
|
||||||
@ -300,8 +295,6 @@ defmodule Chessh.SSH.Client.Game do
|
|||||||
state
|
state
|
||||||
| cursor: new_cursor,
|
| cursor: new_cursor,
|
||||||
move_from: new_move_from,
|
move_from: new_move_from,
|
||||||
width: width,
|
|
||||||
height: height,
|
|
||||||
flipped: if(action == "f", do: !flipped, else: flipped)
|
flipped: if(action == "f", do: !flipped, else: flipped)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -349,13 +342,7 @@ defmodule Chessh.SSH.Client.Game do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
send(client_pid, {:send_to_ssh, Renderer.render_board_state(new_state)})
|
render(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, Renderer.render_board_state(new_state)})
|
|
||||||
new_state
|
new_state
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -370,7 +357,7 @@ defmodule Chessh.SSH.Client.Game do
|
|||||||
from,
|
from,
|
||||||
to,
|
to,
|
||||||
%State{
|
%State{
|
||||||
game: %Game{id: game_id, turn: turn},
|
game: %Game{game_moves: game_moves, id: game_id, turn: turn},
|
||||||
binbo_pid: binbo_pid,
|
binbo_pid: binbo_pid,
|
||||||
flipped: flipped,
|
flipped: flipped,
|
||||||
color: turn
|
color: turn
|
||||||
@ -404,7 +391,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
|
last_move: attempted_move,
|
||||||
|
game_moves: if(game_moves, do: game_moves <> " ", else: "") <> attempted_move
|
||||||
},
|
},
|
||||||
changeset_from_status(status)
|
changeset_from_status(status)
|
||||||
)
|
)
|
||||||
@ -498,4 +486,11 @@ defmodule Chessh.SSH.Client.Game do
|
|||||||
end
|
end
|
||||||
|> Map.merge(extra_highlights)
|
|> Map.merge(extra_highlights)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def render(_width, _height, %State{} = state), do: render(state)
|
||||||
|
|
||||||
|
def render(%State{client_pid: client_pid} = state) do
|
||||||
|
send(client_pid, {:send_to_ssh, Renderer.render_board_state(state)})
|
||||||
|
state
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
121
lib/chessh/ssh/client/game/previous_game.ex
Normal file
121
lib/chessh/ssh/client/game/previous_game.ex
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
defmodule Chessh.SSH.Client.PreviousGame do
|
||||||
|
@start_fen "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
|
||||||
|
|
||||||
|
alias Chessh.{Game, Utils}
|
||||||
|
alias Chessh.SSH.Client.Game.Renderer
|
||||||
|
alias IO.ANSI
|
||||||
|
|
||||||
|
require Logger
|
||||||
|
|
||||||
|
defmodule State do
|
||||||
|
defstruct move_fens: %{},
|
||||||
|
move_idx: 0,
|
||||||
|
binbo_pid: nil,
|
||||||
|
game: %Game{},
|
||||||
|
client_pid: nil,
|
||||||
|
flipped: false
|
||||||
|
end
|
||||||
|
|
||||||
|
use Chessh.SSH.Client.Screen
|
||||||
|
|
||||||
|
def init([
|
||||||
|
%State{
|
||||||
|
client_pid: client_pid,
|
||||||
|
game: %Game{
|
||||||
|
game_moves: game_moves
|
||||||
|
}
|
||||||
|
} = state
|
||||||
|
]) do
|
||||||
|
{:ok, binbo_pid} = :binbo.new_server()
|
||||||
|
:binbo.new_game(binbo_pid, @start_fen)
|
||||||
|
|
||||||
|
{move_fens, _moves} =
|
||||||
|
game_moves
|
||||||
|
|> String.trim()
|
||||||
|
|> String.split(" ")
|
||||||
|
|> Enum.reduce({%{"0" => @start_fen}, 1}, fn move, {move_idx_fen_map, curr_turn} ->
|
||||||
|
{:ok, _status} = :binbo.move(binbo_pid, move)
|
||||||
|
{:ok, fen} = :binbo.get_fen(binbo_pid)
|
||||||
|
|
||||||
|
{Map.put(move_idx_fen_map, "#{curr_turn}", fen), curr_turn + 1}
|
||||||
|
end)
|
||||||
|
|
||||||
|
new_state = %State{
|
||||||
|
state
|
||||||
|
| binbo_pid: binbo_pid,
|
||||||
|
move_fens: move_fens
|
||||||
|
}
|
||||||
|
|
||||||
|
send(client_pid, {:send_to_ssh, Utils.clear_codes()})
|
||||||
|
render(new_state)
|
||||||
|
|
||||||
|
{:ok, new_state}
|
||||||
|
end
|
||||||
|
|
||||||
|
def input(
|
||||||
|
_width,
|
||||||
|
_height,
|
||||||
|
action,
|
||||||
|
%State{
|
||||||
|
move_idx: move_idx,
|
||||||
|
flipped: flipped,
|
||||||
|
game: %Game{
|
||||||
|
moves: num_moves
|
||||||
|
}
|
||||||
|
} = state
|
||||||
|
) do
|
||||||
|
new_move_idx =
|
||||||
|
case action do
|
||||||
|
:left ->
|
||||||
|
Utils.wrap_around(move_idx, -1, num_moves)
|
||||||
|
|
||||||
|
:right ->
|
||||||
|
Utils.wrap_around(move_idx, 1, num_moves)
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
move_idx
|
||||||
|
end
|
||||||
|
|
||||||
|
new_state = %State{
|
||||||
|
state
|
||||||
|
| move_idx: new_move_idx,
|
||||||
|
flipped: if(action == "f", do: !flipped, else: flipped)
|
||||||
|
}
|
||||||
|
|
||||||
|
render(new_state)
|
||||||
|
new_state
|
||||||
|
end
|
||||||
|
|
||||||
|
def render(
|
||||||
|
%State{
|
||||||
|
flipped: flipped,
|
||||||
|
client_pid: client_pid,
|
||||||
|
move_fens: move_fens,
|
||||||
|
move_idx: move_idx,
|
||||||
|
game: %Game{id: game_id, moves: total_moves}
|
||||||
|
} = state
|
||||||
|
) do
|
||||||
|
{:ok, fen} = Map.fetch(move_fens, "#{move_idx}")
|
||||||
|
|
||||||
|
lines =
|
||||||
|
["Game #{game_id} | Move #{move_idx} / #{total_moves}"] ++
|
||||||
|
Renderer.draw_board(fen, flipped) ++
|
||||||
|
["<- previous | next ->"]
|
||||||
|
|
||||||
|
send(
|
||||||
|
client_pid,
|
||||||
|
{:send_to_ssh,
|
||||||
|
[ANSI.home()] ++
|
||||||
|
Enum.map(
|
||||||
|
Enum.zip(1..length(lines), lines),
|
||||||
|
fn {i, line} ->
|
||||||
|
[ANSI.cursor(i, 0), ANSI.clear_line(), line]
|
||||||
|
end
|
||||||
|
)}
|
||||||
|
)
|
||||||
|
|
||||||
|
state
|
||||||
|
end
|
||||||
|
|
||||||
|
def render(_width, _height, %State{} = state), do: render(state)
|
||||||
|
end
|
@ -69,8 +69,6 @@ defmodule Chessh.SSH.Client.Game.Renderer do
|
|||||||
end
|
end
|
||||||
|
|
||||||
def render_board_state(%Game.State{
|
def render_board_state(%Game.State{
|
||||||
width: _width,
|
|
||||||
height: _height,
|
|
||||||
highlighted: highlighted,
|
highlighted: highlighted,
|
||||||
flipped: flipped,
|
flipped: flipped,
|
||||||
game:
|
game:
|
||||||
@ -169,12 +167,18 @@ defmodule Chessh.SSH.Client.Game.Renderer do
|
|||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp draw_board(
|
def draw_board(
|
||||||
fen,
|
fen,
|
||||||
{tile_width, tile_height} = tile_dims,
|
flipped
|
||||||
highlights,
|
),
|
||||||
flipped
|
do: draw_board(fen, {@tile_width, @tile_height}, %{}, flipped)
|
||||||
) do
|
|
||||||
|
def draw_board(
|
||||||
|
fen,
|
||||||
|
{tile_width, tile_height} = tile_dims,
|
||||||
|
highlights,
|
||||||
|
flipped
|
||||||
|
) do
|
||||||
board_coord_to_piece_art = make_board_coordinate_to_piece_art_map(fen)
|
board_coord_to_piece_art = make_board_coordinate_to_piece_art_map(fen)
|
||||||
tile_rows = make_board_tiles(tile_dims)
|
tile_rows = make_board_tiles(tile_dims)
|
||||||
|
|
||||||
|
@ -4,13 +4,13 @@ defmodule Chessh.SSH.Client.MainMenu do
|
|||||||
|
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
@logo " Simponic's
|
@logo " Simponic's
|
||||||
dP MP\"\"\"\"\"\"`MM MP\"\"\"\"\"\"`MM M\"\"MMMMM\"\"MM
|
dP MP\"\"\"\"\"\"`MM MP\"\"\"\"\"\"`MM M\"\"MMMMM\"\"MM
|
||||||
88 M mmmmm..M M mmmmm..M M MMMMM MM
|
88 M mmmmm..M M mmmmm..M M MMMMM MM
|
||||||
.d8888b. 88d888b. .d8888b. M. `YM M. `YM M `M
|
.d8888b. 88d888b. .d8888b. M. `YM M. `YM M `M
|
||||||
88' `\"\" 88' `88 88ooood8 MMMMMMM. M MMMMMMM. M M MMMMM MM
|
88' `\"\" 88' `88 88ooood8 MMMMMMM. M MMMMMMM. M M MMMMM MM
|
||||||
88. ... 88 88 88. ... M. .MMM' M M. .MMM' M M MMMMM MM
|
88. ... 88 88 88. ... M. .MMM' M M. .MMM' M M MMMMM MM
|
||||||
`88888P' dP dP `88888P' Mb. .dM Mb. .dM M MMMMM MM
|
`88888P' dP dP `88888P' Mb. .dM Mb. .dM M MMMMM MM
|
||||||
MMMMMMMMMMM MMMMMMMMMMM MMMMMMMMMMMM" |> String.split("\n")
|
MMMMMMMMMMM MMMMMMMMMMM MMMMMMMMMMMM" |> String.split("\n")
|
||||||
@logo_cols @logo |> Enum.map(&String.length/1) |> Enum.max()
|
@logo_cols @logo |> Enum.map(&String.length/1) |> Enum.max()
|
||||||
|
|
||||||
@ -18,24 +18,27 @@ defmodule Chessh.SSH.Client.MainMenu do
|
|||||||
|
|
||||||
def dynamic_options(), do: false
|
def dynamic_options(), do: false
|
||||||
def tick_delay_ms(), do: 1000
|
def tick_delay_ms(), do: 1000
|
||||||
def max_displayed_options(), do: 4
|
def max_displayed_options(), do: 5
|
||||||
def max_box_cols(), do: @logo_cols
|
def max_box_cols(), do: @logo_cols
|
||||||
def title(), do: @logo ++ ["- Connected on: #{System.get_env("NODE_ID")}"]
|
def title(), do: @logo ++ ["- Connected on: #{System.get_env("NODE_ID")}"]
|
||||||
|
|
||||||
def initial_options(%State{player_session: %PlayerSession{} = player_session}) do
|
def initial_options(%State{player_session: %PlayerSession{} = player_session}) do
|
||||||
[
|
[
|
||||||
|
{"My Current Games",
|
||||||
|
{Chessh.SSH.Client.SelectCurrentGame,
|
||||||
|
%Chessh.SSH.Client.SelectPaginatePoller.State{player_session: player_session}}},
|
||||||
|
{"Joinable Games (lobby)",
|
||||||
|
{Chessh.SSH.Client.SelectJoinableGame,
|
||||||
|
%Chessh.SSH.Client.SelectPaginatePoller.State{player_session: player_session}}},
|
||||||
|
{"Previous Games",
|
||||||
|
{Chessh.SSH.Client.SelectPreviousGame,
|
||||||
|
%Chessh.SSH.Client.SelectPaginatePoller.State{player_session: player_session}}},
|
||||||
{"Start A Game (Light)",
|
{"Start A Game (Light)",
|
||||||
{Chessh.SSH.Client.Game,
|
{Chessh.SSH.Client.Game,
|
||||||
%Chessh.SSH.Client.Game.State{player_session: player_session, color: :light}}},
|
%Chessh.SSH.Client.Game.State{player_session: player_session, color: :light}}},
|
||||||
{"Start A Game (Dark)",
|
{"Start A Game (Dark)",
|
||||||
{Chessh.SSH.Client.Game,
|
{Chessh.SSH.Client.Game,
|
||||||
%Chessh.SSH.Client.Game.State{player_session: player_session, color: :dark}}},
|
%Chessh.SSH.Client.Game.State{player_session: player_session, color: :dark}}}
|
||||||
{"Current Games",
|
|
||||||
{Chessh.SSH.Client.SelectCurrentGame,
|
|
||||||
%Chessh.SSH.Client.SelectPaginatePoller.State{player_session: player_session}}},
|
|
||||||
{"Joinable Games (lobby)",
|
|
||||||
{Chessh.SSH.Client.SelectJoinableGame,
|
|
||||||
%Chessh.SSH.Client.SelectPaginatePoller.State{player_session: player_session}}}
|
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
102
lib/chessh/ssh/client/menus/select_previous_game.ex
Normal file
102
lib/chessh/ssh/client/menus/select_previous_game.ex
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
defmodule Chessh.SSH.Client.SelectPreviousGame do
|
||||||
|
alias Chessh.{Utils, Repo, Game, PlayerSession}
|
||||||
|
alias Chessh.SSH.Client.GameSelector
|
||||||
|
import Ecto.Query
|
||||||
|
require Logger
|
||||||
|
|
||||||
|
use Chessh.SSH.Client.SelectPaginatePoller
|
||||||
|
|
||||||
|
def refresh_options_ms(), do: 4000
|
||||||
|
def max_displayed_options(), do: 5
|
||||||
|
def title(), do: ["-- Previous Games --"]
|
||||||
|
def dynamic_options(), do: true
|
||||||
|
|
||||||
|
def get_player_sorted_current_games_with_id(player_id, current_id \\ nil, direction \\ :desc) do
|
||||||
|
GameSelector.paginate_ish_query(
|
||||||
|
Game
|
||||||
|
|> where([g], g.status != :continue)
|
||||||
|
|> where([g], g.light_player_id == ^player_id or g.dark_player_id == ^player_id)
|
||||||
|
|> limit(^max_displayed_options()),
|
||||||
|
current_id,
|
||||||
|
direction
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def format_game_selection_tuple(%Game{id: game_id} = game) do
|
||||||
|
{Chessh.SSH.Client.Game.Renderer.make_status_line(game, false), game_id}
|
||||||
|
end
|
||||||
|
|
||||||
|
def next_page_options(%State{
|
||||||
|
options: options,
|
||||||
|
player_session: %PlayerSession{player_id: player_id}
|
||||||
|
}) do
|
||||||
|
{_label, previous_last_game_id} = List.last(options)
|
||||||
|
next_games = get_player_sorted_current_games_with_id(player_id, previous_last_game_id, :desc)
|
||||||
|
|
||||||
|
if length(next_games) > 0,
|
||||||
|
do:
|
||||||
|
next_games
|
||||||
|
|> Enum.map(&format_game_selection_tuple/1),
|
||||||
|
else: options
|
||||||
|
end
|
||||||
|
|
||||||
|
def previous_page_options(%State{
|
||||||
|
options: options,
|
||||||
|
player_session: %PlayerSession{player_id: player_id}
|
||||||
|
}) do
|
||||||
|
{_label, previous_first_game_id} = List.first(options)
|
||||||
|
|
||||||
|
previous_games =
|
||||||
|
get_player_sorted_current_games_with_id(player_id, previous_first_game_id, :asc)
|
||||||
|
|
||||||
|
if length(previous_games) > 0,
|
||||||
|
do:
|
||||||
|
previous_games
|
||||||
|
|> Enum.map(&format_game_selection_tuple/1),
|
||||||
|
else: options
|
||||||
|
end
|
||||||
|
|
||||||
|
def initial_options(%State{player_session: %PlayerSession{player_id: player_id}}) do
|
||||||
|
get_player_sorted_current_games_with_id(player_id)
|
||||||
|
|> Enum.map(&format_game_selection_tuple/1)
|
||||||
|
end
|
||||||
|
|
||||||
|
def refresh_options(%State{options: options}) do
|
||||||
|
from(g in Game,
|
||||||
|
where: g.id in ^Enum.map(options, fn {_, id} -> id end),
|
||||||
|
order_by: [desc: g.id]
|
||||||
|
)
|
||||||
|
|> Repo.all()
|
||||||
|
|> Repo.preload([:light_player, :dark_player])
|
||||||
|
|> Enum.map(&format_game_selection_tuple/1)
|
||||||
|
end
|
||||||
|
|
||||||
|
def refresh_options(%State{
|
||||||
|
options: options,
|
||||||
|
player_session: %PlayerSession{player_id: player_id}
|
||||||
|
}) do
|
||||||
|
previous_last_game_id =
|
||||||
|
case List.last(options) do
|
||||||
|
{_label, id} -> id
|
||||||
|
_ -> 0
|
||||||
|
end
|
||||||
|
|
||||||
|
current_screen_games =
|
||||||
|
get_player_sorted_current_games_with_id(player_id, previous_last_game_id - 1, :asc)
|
||||||
|
|
||||||
|
if !is_nil(current_screen_games) && length(current_screen_games),
|
||||||
|
do:
|
||||||
|
current_screen_games
|
||||||
|
|> Enum.map(&format_game_selection_tuple/1),
|
||||||
|
else: options
|
||||||
|
end
|
||||||
|
|
||||||
|
def make_process_tuple(selected_id, _state) do
|
||||||
|
game = Repo.get(Game, selected_id)
|
||||||
|
|
||||||
|
{Chessh.SSH.Client.PreviousGame,
|
||||||
|
%Chessh.SSH.Client.PreviousGame.State{
|
||||||
|
game: game
|
||||||
|
}}
|
||||||
|
end
|
||||||
|
end
|
2
mix.exs
2
mix.exs
@ -17,7 +17,7 @@ defmodule Chessh.MixProject do
|
|||||||
def application do
|
def application do
|
||||||
[
|
[
|
||||||
mod: {Chessh.Application, []},
|
mod: {Chessh.Application, []},
|
||||||
extra_applications: [:logger, :crypto, :syn, :ssh, :plug_cowboy, :inets, :ssl]
|
extra_applications: [:logger, :crypto, :syn, :ssh, :plug_cowboy, :inets, :ssl, :binbo]
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
9
priv/repo/migrations/20230304031125_add_move_history.exs
Normal file
9
priv/repo/migrations/20230304031125_add_move_history.exs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
defmodule Chessh.Repo.Migrations.AddMoveHistory do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def change do
|
||||||
|
alter table(:games) do
|
||||||
|
add(:game_moves, :string, size: 1024)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in New Issue
Block a user