Refactor client game for a bit

This commit is contained in:
Simponic 2023-01-20 08:43:18 -07:00
parent 4666d7871a
commit e0058fedfb
Signed by untrusted user who does not match committer: simponic
GPG Key ID: 52B3774857EB24B1
2 changed files with 148 additions and 155 deletions

View File

@ -291,6 +291,8 @@ defmodule Chessh.SSH.Client.Game do
}, },
promotion promotion
) do ) do
game = Repo.get(Game, game_id)
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))}",
@ -298,8 +300,6 @@ defmodule Chessh.SSH.Client.Game do
) <> ) <>
if(promotion, do: promotion, else: "") if(promotion, do: promotion, else: "")
game = Repo.get(Game, game_id)
case :binbo.move( case :binbo.move(
binbo_pid, binbo_pid,
attempted_move attempted_move
@ -307,42 +307,19 @@ defmodule Chessh.SSH.Client.Game do
{:ok, status} -> {:ok, status} ->
{:ok, fen} = :binbo.get_fen(binbo_pid) {:ok, fen} = :binbo.get_fen(binbo_pid)
default_changeset = %{ {:ok, _new_game} =
fen: fen, game
moves: game.moves + 1, |> Game.changeset(
turn: if(game.turn == :dark, do: :light, else: :dark) Map.merge(
} %{
fen: fen,
case status do moves: game.moves + 1,
:continue -> turn: if(game.turn == :dark, do: :light, else: :dark)
{:ok, _new_game} = },
Game.changeset( changeset_from_status(status)
game,
default_changeset
)
|> Repo.update()
{:draw, _} ->
Game.changeset(
game,
Map.merge(default_changeset, %{status: :draw})
) )
|> Repo.update() )
|> Repo.update()
{:checkmate, :white_wins} ->
Game.changeset(
game,
Map.merge(default_changeset, %{status: :winner, winner: :light})
)
|> Repo.update()
{:checkmate, :black_wins} ->
Game.changeset(
game,
Map.merge(default_changeset, %{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})
@ -367,4 +344,20 @@ defmodule Chessh.SSH.Client.Game do
) do ) do
Renderer.render_board_state(fen, state) Renderer.render_board_state(fen, state)
end end
defp changeset_from_status(game_status) do
case game_status do
:continue ->
%{}
{:draw, _} ->
%{status: :draw}
{:checkmate, :white_wins} ->
%{status: :winner, winner: :light}
{:checkmate, :black_wins} ->
%{status: :winner, winner: :dark}
end
end
end end

View File

@ -10,15 +10,17 @@ defmodule Chessh.SSH.Client.Game.Renderer do
@tile_width 7 @tile_width 7
@tile_height 4 @tile_height 4
@from_select_background ANSI.light_blue_background() @previous_move_background ANSI.light_yellow_background()
@to_select_background ANSI.blue_background() @from_select_background ANSI.light_green_background()
@to_select_background ANSI.green_background()
@dark_piece_color ANSI.light_red() @dark_piece_color ANSI.light_red()
@light_piece_color ANSI.light_magenta() @light_piece_color ANSI.light_blue()
def chess_board_height(), do: @chess_board_height def chess_board_height(), do: @chess_board_height
def chess_board_width(), do: @chess_board_width def chess_board_width(), do: @chess_board_width
def to_select_background(), do: @to_select_background def to_select_background(), do: @to_select_background
def from_select_background(), do: @from_select_background def from_select_background(), do: @from_select_background
def previous_move_background(), do: @previous_move_background
def to_chess_coord({y, x}) def to_chess_coord({y, x})
when x >= 0 and x < @chess_board_width and y >= 0 and y < @chess_board_height do when x >= 0 and x < @chess_board_width and y >= 0 and y < @chess_board_height do
@ -63,7 +65,6 @@ defmodule Chessh.SSH.Client.Game.Renderer do
highlighted: highlighted, highlighted: highlighted,
flipped: flipped, flipped: flipped,
game: %Chessh.Game{ game: %Chessh.Game{
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,
@ -75,7 +76,6 @@ defmodule Chessh.SSH.Client.Game.Renderer do
Enum.join( Enum.join(
[ [
ANSI.clear_line(), ANSI.clear_line(),
"Game #{game_id}: ",
ANSI.format_fragment([@light_piece_color, light_player]), ANSI.format_fragment([@light_piece_color, light_player]),
"#{ANSI.default_color()} --vs-- ", "#{ANSI.default_color()} --vs-- ",
ANSI.format_fragment([@dark_piece_color, dark_player]), ANSI.format_fragment([@dark_piece_color, dark_player]),
@ -89,7 +89,8 @@ defmodule Chessh.SSH.Client.Game.Renderer do
:winner -> :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!" ", #{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 end,
ANSI.default_color()
], ],
"" ""
) )
@ -110,142 +111,82 @@ defmodule Chessh.SSH.Client.Game.Renderer do
) )
end end
defp tileIsLight(row, col) do
rem(row, 2) == rem(col, 2)
end
defp piece_type(char) do
case String.capitalize(char) do
"P" -> "pawn"
"N" -> "knight"
"R" -> "rook"
"B" -> "bishop"
"K" -> "king"
"Q" -> "queen"
_ -> nil
end
end
defp skip_cols_or_place_piece_reduce(char, {curr_column, data}, rowI) do
case Integer.parse(char) do
{skip, ""} ->
{curr_column + skip, data}
_ ->
case piece_type(char) do
nil ->
{curr_column, data}
type ->
shade = if(char == String.capitalize(char), do: "light", else: "dark")
{curr_column + 1,
Map.put(
data,
"#{rowI}, #{curr_column}",
{shade, type}
)}
end
end
end
defp make_coordinate_to_piece_art_map(fen) do
rows =
String.split(fen, " ")
|> List.first()
|> String.split("/")
Enum.zip(rows, 0..(length(rows) - 1))
|> Enum.map(fn {row, rowI} ->
{@chess_board_height, pieces_per_row} =
Enum.reduce(
String.split(row, ""),
{0, %{}},
&skip_cols_or_place_piece_reduce(&1, &2, rowI)
)
pieces_per_row
end)
|> Enum.reduce(%{}, fn pieces_map_for_this_row, acc ->
Map.merge(acc, pieces_map_for_this_row)
end)
end
defp draw_board( defp draw_board(
fen, fen,
{tile_width, tile_height} = tile_dims, {tile_width, tile_height} = tile_dims,
highlights, highlights,
flipped flipped
) do ) do
coordinate_to_piece = make_coordinate_to_piece_art_map(fen) board_coord_to_piece_art = make_board_coordinate_to_piece_art_map(fen)
board = make_board(tile_dims) tile_rows = make_board_tiles(tile_dims)
(Enum.zip_with([board, 0..(length(board) - 1)], fn [row_str, row] -> (Enum.zip_with([tile_rows, 0..(tile_height * @chess_board_height - 1)], fn [row_str, row] ->
curr_y = div(row, tile_height) curr_y = div(row, tile_height)
%{row_chars: row_chars} = %{row_chars: row_chars} =
Enum.reduce( Enum.reduce(
Enum.zip(String.graphemes(row_str), 0..(String.length(row_str) - 1)), Enum.zip(String.graphemes(row_str), 0..(tile_width * @chess_board_width - 1)),
%{current_color: ANSI.black(), row_chars: []}, %{tile_chunk: [], current_color: ANSI.default_color(), row_chars: []},
fn {char, col}, %{current_color: current_color, row_chars: row_chars} = row_state -> fn {tile_char, col},
%{tile_chunk: tile_chunk, current_color: current_color, row_chars: row_chars} =
row_acc_state ->
curr_x = div(col, tile_width) curr_x = div(col, tile_width)
col_relative_to_tile = col - curr_x * tile_width
key = board_coord =
"#{if !flipped, do: curr_y, else: @chess_board_height - curr_y - 1}, #{if !flipped, do: curr_x, else: @chess_board_width - curr_x - 1}" {if(!flipped, do: curr_y, else: @chess_board_height - curr_y - 1),
if(!flipped, do: curr_x, else: @chess_board_width - curr_x - 1)}
relative_to_tile_col = col - curr_x * tile_width {color, char} =
case Map.fetch(board_coord_to_piece_art, board_coord) do
prefix =
if relative_to_tile_col == 0 do
case Map.fetch(highlights, {curr_y, curr_x}) do
{:ok, color} ->
color
_ ->
ANSI.default_background()
end
end
{color, row_chars} =
case Map.fetch(coordinate_to_piece, key) do
{:ok, {shade, type}} -> {:ok, {shade, type}} ->
piece = Utils.ascii_chars()["pieces"][shade][type] piece = Utils.ascii_chars()["pieces"][shade][type]
piece_line = Enum.at(piece, row - curr_y * tile_height) piece_line = Enum.at(piece, row - curr_y * tile_height)
pad_left_right = div(tile_width - String.length(piece_line), 2) spaces_pad_piece_line = div(tile_width - String.length(piece_line), 2)
if relative_to_tile_col >= pad_left_right && piece_char =
relative_to_tile_col < tile_width - pad_left_right do if col_relative_to_tile >= spaces_pad_piece_line &&
piece_char = String.at(piece_line, relative_to_tile_col - pad_left_right) col_relative_to_tile < tile_width - spaces_pad_piece_line,
new_char = if piece_char == " ", do: char, else: piece_char do: String.at(piece_line, col_relative_to_tile - spaces_pad_piece_line)
color = new_char = if !piece_char || piece_char == " ", do: tile_char, else: piece_char
if piece_char == " ",
do: ANSI.default_color(),
else:
if(shade == "dark", do: @dark_piece_color, else: @light_piece_color)
if color != current_color do tile_char_color =
{color, row_chars ++ [prefix, color, new_char]} if !piece_char || piece_char == " ",
else do: ANSI.default_color(),
{current_color, row_chars ++ [prefix, new_char]} else: if(shade == "dark", do: @dark_piece_color, else: @light_piece_color)
end
else {tile_char_color, new_char}
{ANSI.default_color(), row_chars ++ [prefix, ANSI.default_color(), char]}
end
_ -> _ ->
if ANSI.default_color() != current_color do {current_color, tile_char}
{ANSI.default_color(), row_chars ++ [prefix, ANSI.default_color(), char]}
else
{current_color, row_chars ++ [prefix, char]}
end
end end
%{ tile_chunk =
row_state if col_relative_to_tile == 0 do
| current_color: color, case Map.fetch(highlights, {curr_y, curr_x}) do
row_chars: row_chars {:ok, highlighted_background_color} ->
[highlighted_background_color]
_ ->
[ANSI.default_background(), ANSI.default_color()]
end
else
tile_chunk
end ++ if(color == current_color, do: [char], else: [color, char])
new_accumulated_state = %{
row_acc_state
| current_color:
if(col_relative_to_tile == 0, do: ANSI.default_color(), else: color),
tile_chunk: tile_chunk
} }
if col_relative_to_tile == @tile_width - 1 do
%{new_accumulated_state | row_chars: row_chars ++ tile_chunk}
else
new_accumulated_state
end
end end
) )
@ -299,7 +240,7 @@ defmodule Chessh.SSH.Client.Game.Renderer do
end) end)
end end
defp make_board({tile_width, tile_height}) do defp make_board_tiles({tile_width, tile_height}) do
rows = rows =
Enum.map(0..(@chess_board_height - 1), fn row -> Enum.map(0..(@chess_board_height - 1), fn row ->
Enum.map(0..(@chess_board_width - 1), fn col -> Enum.map(0..(@chess_board_width - 1), fn col ->
@ -311,4 +252,63 @@ defmodule Chessh.SSH.Client.Game.Renderer do
Enum.flat_map(rows, fn row -> Enum.map(1..tile_height, fn _ -> row end) end) Enum.flat_map(rows, fn row -> Enum.map(1..tile_height, fn _ -> row end) end)
end end
defp tileIsLight(row, col), do: rem(row, 2) == rem(col, 2)
defp piece_type(char) do
case String.capitalize(char) do
"P" -> "pawn"
"N" -> "knight"
"R" -> "rook"
"B" -> "bishop"
"K" -> "king"
"Q" -> "queen"
_ -> nil
end
end
defp skip_cols_or_place_piece_reduce(char, {curr_column, data}, row_i) do
case Integer.parse(char) do
{skip, ""} ->
{curr_column + skip, data}
_ ->
case piece_type(char) do
nil ->
{curr_column, data}
type ->
shade = if(char == String.capitalize(char), do: "light", else: "dark")
{curr_column + 1,
Map.put(
data,
{row_i, curr_column},
{shade, type}
)}
end
end
end
defp make_board_coordinate_to_piece_art_map(fen) do
rows =
String.split(fen, " ")
|> List.first()
|> String.split("/")
Enum.zip(rows, 0..(length(rows) - 1))
|> Enum.map(fn {row, rowI} ->
{@chess_board_height, pieces_per_row} =
Enum.reduce(
String.split(row, ""),
{0, %{}},
&skip_cols_or_place_piece_reduce(&1, &2, rowI)
)
pieces_per_row
end)
|> Enum.reduce(%{}, fn pieces_map_for_this_row, acc ->
Map.merge(acc, pieces_map_for_this_row)
end)
end
end end