diff --git a/lib/chessh/ssh/client/game/game.ex b/lib/chessh/ssh/client/game/game.ex index 65b9d10..9dbde7f 100644 --- a/lib/chessh/ssh/client/game/game.ex +++ b/lib/chessh/ssh/client/game/game.ex @@ -291,6 +291,8 @@ defmodule Chessh.SSH.Client.Game do }, promotion ) do + game = Repo.get(Game, game_id) + attempted_move = if(flipped, 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: "") - game = Repo.get(Game, game_id) - case :binbo.move( binbo_pid, attempted_move @@ -307,42 +307,19 @@ defmodule Chessh.SSH.Client.Game do {:ok, status} -> {:ok, fen} = :binbo.get_fen(binbo_pid) - default_changeset = %{ - fen: fen, - moves: game.moves + 1, - turn: if(game.turn == :dark, do: :light, else: :dark) - } - - case status do - :continue -> - {:ok, _new_game} = - Game.changeset( - game, - default_changeset - ) - |> Repo.update() - - {:draw, _} -> - Game.changeset( - game, - Map.merge(default_changeset, %{status: :draw}) + {:ok, _new_game} = + game + |> Game.changeset( + Map.merge( + %{ + fen: fen, + moves: game.moves + 1, + turn: if(game.turn == :dark, do: :light, else: :dark) + }, + changeset_from_status(status) ) - |> 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 + ) + |> Repo.update() :syn.publish(:games, {:game, game_id}, {:new_move, attempted_move}) @@ -367,4 +344,20 @@ defmodule Chessh.SSH.Client.Game do ) do Renderer.render_board_state(fen, state) 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 diff --git a/lib/chessh/ssh/client/game/renderer.ex b/lib/chessh/ssh/client/game/renderer.ex index f8cf689..d7c5b26 100644 --- a/lib/chessh/ssh/client/game/renderer.ex +++ b/lib/chessh/ssh/client/game/renderer.ex @@ -10,15 +10,17 @@ defmodule Chessh.SSH.Client.Game.Renderer do @tile_width 7 @tile_height 4 - @from_select_background ANSI.light_blue_background() - @to_select_background ANSI.blue_background() + @previous_move_background ANSI.light_yellow_background() + @from_select_background ANSI.light_green_background() + @to_select_background ANSI.green_background() @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_width(), do: @chess_board_width def to_select_background(), do: @to_select_background def from_select_background(), do: @from_select_background + def previous_move_background(), do: @previous_move_background def to_chess_coord({y, x}) 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, flipped: flipped, game: %Chessh.Game{ - id: game_id, dark_player: %Player{username: dark_player}, light_player: %Player{username: light_player}, turn: turn, @@ -75,7 +76,6 @@ defmodule Chessh.SSH.Client.Game.Renderer do Enum.join( [ ANSI.clear_line(), - "Game #{game_id}: ", ANSI.format_fragment([@light_piece_color, light_player]), "#{ANSI.default_color()} --vs-- ", ANSI.format_fragment([@dark_piece_color, dark_player]), @@ -89,7 +89,8 @@ defmodule Chessh.SSH.Client.Game.Renderer do :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 + end, + ANSI.default_color() ], "" ) @@ -110,142 +111,82 @@ defmodule Chessh.SSH.Client.Game.Renderer do ) 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( fen, {tile_width, tile_height} = tile_dims, highlights, flipped ) do - coordinate_to_piece = make_coordinate_to_piece_art_map(fen) - board = make_board(tile_dims) + board_coord_to_piece_art = make_board_coordinate_to_piece_art_map(fen) + 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) %{row_chars: row_chars} = Enum.reduce( - Enum.zip(String.graphemes(row_str), 0..(String.length(row_str) - 1)), - %{current_color: ANSI.black(), row_chars: []}, - fn {char, col}, %{current_color: current_color, row_chars: row_chars} = row_state -> + Enum.zip(String.graphemes(row_str), 0..(tile_width * @chess_board_width - 1)), + %{tile_chunk: [], current_color: ANSI.default_color(), row_chars: []}, + 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) + col_relative_to_tile = col - curr_x * tile_width - key = - "#{if !flipped, do: curr_y, else: @chess_board_height - curr_y - 1}, #{if !flipped, do: curr_x, else: @chess_board_width - curr_x - 1}" + 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)} - relative_to_tile_col = col - curr_x * tile_width - - 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 + {color, char} = + case Map.fetch(board_coord_to_piece_art, board_coord) do {:ok, {shade, type}} -> piece = Utils.ascii_chars()["pieces"][shade][type] 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 && - relative_to_tile_col < tile_width - pad_left_right do - piece_char = String.at(piece_line, relative_to_tile_col - pad_left_right) - new_char = if piece_char == " ", do: char, else: piece_char + piece_char = + if col_relative_to_tile >= spaces_pad_piece_line && + col_relative_to_tile < tile_width - spaces_pad_piece_line, + do: String.at(piece_line, col_relative_to_tile - spaces_pad_piece_line) - color = - if piece_char == " ", - do: ANSI.default_color(), - else: - if(shade == "dark", do: @dark_piece_color, else: @light_piece_color) + new_char = if !piece_char || piece_char == " ", do: tile_char, else: piece_char - if color != current_color do - {color, row_chars ++ [prefix, color, new_char]} - else - {current_color, row_chars ++ [prefix, new_char]} - end - else - {ANSI.default_color(), row_chars ++ [prefix, ANSI.default_color(), char]} - end + tile_char_color = + if !piece_char || piece_char == " ", + do: ANSI.default_color(), + else: if(shade == "dark", do: @dark_piece_color, else: @light_piece_color) + + {tile_char_color, new_char} _ -> - if ANSI.default_color() != current_color do - {ANSI.default_color(), row_chars ++ [prefix, ANSI.default_color(), char]} - else - {current_color, row_chars ++ [prefix, char]} - end + {current_color, tile_char} end - %{ - row_state - | current_color: color, - row_chars: row_chars + tile_chunk = + if col_relative_to_tile == 0 do + case Map.fetch(highlights, {curr_y, curr_x}) do + {: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 ) @@ -299,7 +240,7 @@ defmodule Chessh.SSH.Client.Game.Renderer do end) end - defp make_board({tile_width, tile_height}) do + defp make_board_tiles({tile_width, tile_height}) do rows = Enum.map(0..(@chess_board_height - 1), fn row -> 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) 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