From 2ce03d4796ad54f332c70ff88f0a9f3a164bbc4a Mon Sep 17 00:00:00 2001 From: Simponic Date: Wed, 11 Jan 2023 09:54:05 -0700 Subject: [PATCH] Checkpoint\? --- TODO.md | 1 + lib/chessh/ssh/client/board.ex | 276 ++++++++++++++++++++++++++ lib/chessh/ssh/client/board/server.ex | 19 ++ lib/chessh/ssh/{ => client}/client.ex | 58 +++--- lib/chessh/ssh/client/menu.ex | 100 ++++++++++ lib/chessh/ssh/client/screen.ex | 31 +++ lib/chessh/ssh/screens/board.ex | 257 ------------------------ lib/chessh/ssh/screens/menu.ex | 104 ---------- lib/chessh/ssh/screens/screen.ex | 22 -- mix.exs | 2 +- mix.lock | 2 + 11 files changed, 456 insertions(+), 416 deletions(-) create mode 100644 TODO.md create mode 100644 lib/chessh/ssh/client/board.ex create mode 100644 lib/chessh/ssh/client/board/server.ex rename lib/chessh/ssh/{ => client}/client.ex (62%) create mode 100644 lib/chessh/ssh/client/menu.ex create mode 100644 lib/chessh/ssh/client/screen.ex delete mode 100644 lib/chessh/ssh/screens/board.ex delete mode 100644 lib/chessh/ssh/screens/menu.ex delete mode 100644 lib/chessh/ssh/screens/screen.ex diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..5f54fe5 --- /dev/null +++ b/TODO.md @@ -0,0 +1 @@ +Change the menu and board to be genserverse, board subscribes to game, diff --git a/lib/chessh/ssh/client/board.ex b/lib/chessh/ssh/client/board.ex new file mode 100644 index 0000000..b192cfe --- /dev/null +++ b/lib/chessh/ssh/client/board.ex @@ -0,0 +1,276 @@ +# defmodule Chessh.SSH.Client.Board do +# alias Chessh.SSH.Client +# alias IO.ANSI +# +# require Logger +# +# defmodule State do +# defstruct cursor: %{x: 0, y: 0}, +# highlighted: %{}, +# move_from: nil +# end +# +# use GenServer +# use Chessh.SSH.Client.Screen +# +# @chess_board_height 8 +# @chess_board_width 8 +# @tile_width 7 +# @tile_height 4 +# +# @dark_piece_color ANSI.magenta() +# @light_piece_color ANSI.red() +# +# def tileIsLight(row, col) do +# rem(row, 2) == rem(col, 2) +# end +# +# def piece_type(char) do +# case String.capitalize(char) do +# "P" -> "pawn" +# "N" -> "knight" +# "R" -> "rook" +# "B" -> "bishop" +# "K" -> "king" +# "Q" -> "queen" +# _ -> nil +# end +# end +# +# def make_board({tile_width, tile_height}) do +# rows = +# Enum.map(0..(@chess_board_height - 1), fn row -> +# Enum.map(0..(@chess_board_width - 1), fn col -> +# if(tileIsLight(row, col), do: ' ', else: '▊') +# |> List.duplicate(tile_width) +# end) +# |> Enum.join("") +# end) +# +# Enum.flat_map(rows, fn row -> Enum.map(1..tile_height, fn _ -> row end) 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 +# +# def draw_board( +# fen, +# {tile_width, tile_height} = tile_dims, +# highlights +# ) do +# coordinate_to_piece = make_coordinate_to_piece_art_map(fen) +# board = make_board(tile_dims) +# +# Enum.zip_with([board, 0..(length(board) - 1)], fn [rowStr, row] -> +# curr_y = div(row, tile_height) +# +# %{row_chars: row_chars} = +# Enum.reduce( +# Enum.zip(String.graphemes(rowStr), 0..(String.length(rowStr) - 1)), +# %{current_color: ANSI.black(), row_chars: []}, +# fn {char, col}, %{current_color: current_color, row_chars: row_chars} = row_state -> +# curr_x = div(col, tile_width) +# key = "#{curr_y}, #{curr_x}" +# 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 +# +# case Map.fetch(coordinate_to_piece, key) do +# {:ok, {shade, type}} -> +# piece = @ascii_chars["pieces"][shade][type] +# piece_line = Enum.at(piece, row - curr_y * tile_height) +# +# piece_line_len = String.length(piece_line) +# pad_left_right = div(tile_width - piece_line_len, 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 +# +# color = +# if piece_char == " ", +# do: ANSI.default_color(), +# else: if(shade == "dark", do: @dark_piece_color, else: @light_piece_color) +# +# if color != current_color do +# %{ +# row_state +# | current_color: color, +# row_chars: row_chars ++ [prefix, color, new_char] +# } +# else +# %{ +# row_state +# | current_color: current_color, +# row_chars: row_chars ++ [prefix, new_char] +# } +# end +# else +# %{ +# row_state +# | current_color: ANSI.default_color(), +# row_chars: row_chars ++ [prefix, ANSI.default_color(), char] +# } +# end +# +# _ -> +# if ANSI.white() != current_color do +# %{ +# row_state +# | current_color: ANSI.default_color(), +# row_chars: row_chars ++ [prefix, ANSI.default_color(), char] +# } +# else +# %{ +# row_state +# | row_chars: row_chars ++ [prefix, char] +# } +# end +# end +# end +# ) +# +# row_chars +# |> Enum.join("") +# end) +# end +# +# def render(%Client.State{ +# state_stack: [{_this_module, %State{highlighted: highlighted}} | _] +# }) do +# board = +# draw_board( +# "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", +# {@tile_width, @tile_height}, +# highlighted +# ) +# +# [ANSI.home()] ++ +# Enum.map( +# Enum.zip(1..length(board), board), +# fn {i, line} -> +# [ANSI.cursor(i, 0), line] +# end +# ) +# end +# +# def handle_input( +# action, +# %Client.State{ +# binbo_pid: binbo_pid, +# state_stack: [ +# {this_module, +# %State{ +# move_from: move_from, +# cursor: %{x: cursor_x, y: cursor_y} = cursor +# } = screen_state} +# | rest_stack +# ] +# } = state +# ) do +# new_cursor = +# case action do +# :left -> %{y: cursor_y, x: cursor_x - 1} +# :right -> %{y: cursor_y, x: cursor_x + 1} +# :down -> %{y: cursor_y + 1, x: cursor_x} +# :up -> %{y: cursor_y - 1, x: cursor_x} +# _ -> cursor +# end +# +# {new_move_from, move_to} = +# if action == :return do +# coords = {new_cursor.y, new_cursor.x} +# +# case move_from do +# nil -> {coords, nil} +# _ -> {nil, coords} +# end +# else +# {move_from, nil} +# end +# +# if move_from && move_to do +# # Logger.debug(inspect(attempt_move(%Chess.Game{}, move_from, move_to))) +# attempted_move = "#{to_chess_coord(move_from)}#{to_chess_coord(move_to)}" +# +# case :binbo.move(binbo_pid, attempted_move) do +# # state.game_id}, {:new_move, :attempted_move}) +# {:ok, _} -> :syn.publish(:games, {:game, "asdf"}, {:new_move, attempted_move}) +# end +# end +# +# %Client.State{ +# state +# | state_stack: [ +# {this_module, +# %State{ +# screen_state +# | cursor: new_cursor, +# move_from: new_move_from, +# highlighted: %{ +# new_move_from => ANSI.green_background(), +# {new_cursor.y, new_cursor.x} => ANSI.green_background() +# } +# }} +# | rest_stack +# ] +# } +# end +# +# defp to_chess_coord({y, x}) +# when x >= 0 and x < @chess_board_width and y >= 0 and y < @chess_board_height do +# "#{List.to_string([?a + x])}#{y + 1}" +# end +# end diff --git a/lib/chessh/ssh/client/board/server.ex b/lib/chessh/ssh/client/board/server.ex new file mode 100644 index 0000000..2e480e7 --- /dev/null +++ b/lib/chessh/ssh/client/board/server.ex @@ -0,0 +1,19 @@ +# defmodule Chessh.SSH.Client.Board.Server do +# use GenServer +# +# defmodule State do +# defstruct game_id: nil, +# binbo_pid: nil +# end +# +# def init([%State{game_id: game_id} = state]) do +# {:ok, binbo_pid} = GenServer.start_link(:binbo, []) +# +# :syn.join(:games, {:game, game_id}) +# {:ok, state} +# end +# +# def handle_cast({:new_move, attempted_move}, %State{game_id: game_id} = state) do +# {:no_reply, state} +# end +# end diff --git a/lib/chessh/ssh/client.ex b/lib/chessh/ssh/client/client.ex similarity index 62% rename from lib/chessh/ssh/client.ex rename to lib/chessh/ssh/client/client.ex index 290c9a7..a5f7bec 100644 --- a/lib/chessh/ssh/client.ex +++ b/lib/chessh/ssh/client/client.ex @@ -11,8 +11,6 @@ defmodule Chessh.SSH.Client do ANSI.home() ] - @min_terminal_width 64 - @min_terminal_height 32 @max_terminal_width 255 @max_terminal_height 127 @@ -25,14 +23,17 @@ defmodule Chessh.SSH.Client do width: 0, height: 0, player_session: nil, - buffer: [], - state_stack: [{Menu, %Menu.State{}}] + screen_processes: [] end @impl true def init([%State{tui_pid: tui_pid} = state]) do - send(tui_pid, {:send_data, render(state)}) - {:ok, state} + {:ok, screen_pid} = + GenServer.start_link(Chessh.SSH.Client.Menu, [ + %Chessh.SSH.Client.Menu.State{tui_pid: tui_pid} + ]) + + {:ok, %{state | screen_processes: [screen_pid]}} end @impl true @@ -63,25 +64,36 @@ defmodule Chessh.SSH.Client do def handle( {:data, data}, - %State{tui_pid: tui_pid, state_stack: [{module, _screen_state} | _tail]} = state + %State{width: width, height: height, screen_processes: [screen_pid | _]} = state ) do action = keymap(data) if action == :quit do {:stop, :normal, state} else - new_state = module.handle_input(action, state) - - send(tui_pid, {:send_data, render(new_state)}) - {:noreply, new_state} + send(screen_pid, {:input, width, height, action}) + {:noreply, state} end end - def handle({:resize, {width, height}}, %State{tui_pid: tui_pid} = state) do + # def handle( + # {:refresh, }, + # %State{screen_processes: [screen_pid | _] = screen_processes, width: width, height: height} = state + # ) do + # send(screen_pid, {:render, tui_pid, width, height}) + # {:noreply, state} + # end + + def handle( + {:resize, {width, height}}, + %State{tui_pid: tui_pid, screen_processes: [screen_pid | _]} = state + ) do new_state = %State{state | width: width, height: height} - if height <= @max_terminal_height || width <= @max_terminal_width do - send(tui_pid, {:send_data, render(new_state)}) + if height <= @max_terminal_height && width <= @max_terminal_width do + send(screen_pid, {:render, width, height}) + else + send(tui_pid, {:send_data, @terminal_bad_dim_msg}) end {:noreply, new_state} @@ -101,22 +113,4 @@ defmodule Chessh.SSH.Client do x -> x end end - - def terminal_size_allowed(width, height) do - Enum.member?(@min_terminal_width..@max_terminal_width, width) && - Enum.member?(@min_terminal_height..@max_terminal_height, height) - end - - def render( - %State{width: width, height: height, state_stack: [{module, _screen_state} | _]} = state - ) do - if terminal_size_allowed(width, height) do - [ - @clear_codes ++ - module.render(state) - ] - else - @terminal_bad_dim_msg - end - end end diff --git a/lib/chessh/ssh/client/menu.ex b/lib/chessh/ssh/client/menu.ex new file mode 100644 index 0000000..a69ad88 --- /dev/null +++ b/lib/chessh/ssh/client/menu.ex @@ -0,0 +1,100 @@ +defmodule Chessh.SSH.Client.Menu do + alias Chessh.Utils + alias IO.ANSI + + require Logger + + defmodule State do + defstruct dy: 0, + dx: 0, + tui_pid: nil, + selected: 0 + end + + use Chessh.SSH.Client.Screen + + def init([%State{} = state | _]) do + {:ok, state} + end + + @logo " Simponic's + dP MP\"\"\"\"\"\"`MM MP\"\"\"\"\"\"`MM M\"\"MMMMM\"\"MM + 88 M mmmmm..M M mmmmm..M M MMMMM MM +.d8888b. 88d888b. .d8888b. M. `YM M. `YM M `M +88' `\"\" 88' `88 88ooood8 MMMMMMM. M MMMMMMM. 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 + MMMMMMMMMMM MMMMMMMMMMM MMMMMMMMMMMM" + + # @options [ + # {"Option 1", {Chessh.SSH.Client.Board, [%Chessh.SSH.Client.Board.State{}]}}, + # {"Option 2", {Chessh.SSH.Client.Board, [%Chessh.SSH.Client.Board.State{}]}} + # ] + @options [ + {"Option 1", {}}, + {"Option 2", {}}, + {"Option 3", {}} + ] + + def handle_info({:render, width, height}, %State{} = state) do + render(width, height, state) + {:noreply, state} + end + + def handle_info({:input, width, height, action}, %State{selected: selected} = state) do + new_state = + case(action) do + :up -> + %State{ + state + | selected: wrap_around(selected, -1, length(@options)) + } + + :down -> + %State{state | selected: wrap_around(selected, 1, length(@options))} + + # :return -> + # {_, new_state} = Enum.at(@options, selected) + # new_state + + _ -> + state + end + + render(width, height, new_state) + {:noreply, new_state} + end + + def render(width, height, %State{tui_pid: tui_pid, dy: dy, dx: dx, selected: selected}) do + text = String.split(@logo, "\n") + {logo_width, logo_height} = Utils.text_dim(@logo) + {y, x} = center_rect({logo_width, logo_height + length(text)}, {width, height}) + + rendered = + Enum.flat_map( + Enum.zip(1..length(text), text), + fn {i, line} -> + [ + ANSI.cursor(y + i + dy, x + dx), + line + ] + end + ) ++ + Enum.flat_map( + Enum.zip(0..(length(@options) - 1), @options), + fn {i, {option, _}} -> + [ + ANSI.cursor(y + length(text) + i + dy, x + dx), + if(i == selected, do: ANSI.format([:light_cyan, "* #{option}"]), else: option) + ] + end + ) ++ [ANSI.home()] + + send(tui_pid, {:send_data, rendered}) + end + + defp wrap_around(index, delta, length) do + calc = index + delta + if(calc < 0, do: length, else: 0) + rem(calc, length) + end +end diff --git a/lib/chessh/ssh/client/screen.ex b/lib/chessh/ssh/client/screen.ex new file mode 100644 index 0000000..2dd9f9c --- /dev/null +++ b/lib/chessh/ssh/client/screen.ex @@ -0,0 +1,31 @@ +defmodule Chessh.SSH.Client.Screen do + @callback handle_info( + {:render, width :: integer(), height :: integer()}, + state :: any() + ) :: + {:noreply, any()} + @callback handle_info({:input, width :: integer(), height :: integer(), action :: any()}) :: + {:noreply, any()} + + # @callback render(state :: Chessh.SSH.Client.State.t() | any()) :: any() + # @callback handle_input(action :: any(), state :: Chessh.SSH.Client.State.t()) :: + # Chessh.SSH.Client.State.t() + + defmacro __using__(_) do + quote do + @behaviour Chessh.SSH.Client.Screen + use GenServer + + @ascii_chars Application.compile_env!(:chessh, :ascii_chars_json_file) + |> File.read!() + |> Jason.decode!() + + def center_rect({rect_width, rect_height}, {parent_width, parent_height}) do + { + div(parent_height - rect_height, 2), + div(parent_width - rect_width, 2) + } + end + end + end +end diff --git a/lib/chessh/ssh/screens/board.ex b/lib/chessh/ssh/screens/board.ex deleted file mode 100644 index 09babad..0000000 --- a/lib/chessh/ssh/screens/board.ex +++ /dev/null @@ -1,257 +0,0 @@ -defmodule Chessh.SSH.Client.Board do - alias Chessh.SSH.Client - alias IO.ANSI - - defmodule State do - defstruct cursor: %{x: 0, y: 0}, - highlighted: %{}, - move_from: nil - end - - use Chessh.SSH.Client.Screen - - @chess_board_height 8 - @chess_board_width 8 - @tile_width 7 - @tile_height 4 - - @dark_piece_color ANSI.magenta() - @light_piece_color ANSI.red() - - def tileIsLight(row, col) do - rem(row, 2) == rem(col, 2) - end - - def piece_type(char) do - case String.capitalize(char) do - "P" -> "pawn" - "N" -> "knight" - "R" -> "rook" - "B" -> "bishop" - "K" -> "king" - "Q" -> "queen" - _ -> nil - end - end - - def make_board({tile_width, tile_height}) do - rows = - Enum.map(0..(@chess_board_height - 1), fn row -> - Enum.map(0..(@chess_board_width - 1), fn col -> - if(tileIsLight(row, col), do: ' ', else: '▊') - |> List.duplicate(tile_width) - end) - |> Enum.join("") - end) - - Enum.flat_map(rows, fn row -> Enum.map(1..tile_height, fn _ -> row end) 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: "dark", else: "light") - - {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 - - def draw_board( - fen, - {tile_width, tile_height} = tile_dims, - highlights - ) do - coordinate_to_piece = make_coordinate_to_piece_art_map(fen) - board = make_board(tile_dims) - - Enum.zip_with([board, 0..(length(board) - 1)], fn [rowStr, row] -> - curr_y = div(row, tile_height) - - %{row_chars: row_chars} = - Enum.reduce( - Enum.zip(String.graphemes(rowStr), 0..(String.length(rowStr) - 1)), - %{current_color: ANSI.black(), row_chars: []}, - fn {char, col}, %{current_color: current_color, row_chars: row_chars} = row_state -> - curr_x = div(col, tile_width) - key = "#{curr_y}, #{curr_x}" - 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 - - case Map.fetch(coordinate_to_piece, key) do - {:ok, {shade, type}} -> - piece = @ascii_chars["pieces"][shade][type] - piece_line = Enum.at(piece, row - curr_y * tile_height) - - piece_line_len = String.length(piece_line) - pad_left_right = div(tile_width - piece_line_len, 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 - - color = - if piece_char == " ", - do: ANSI.default_color(), - else: if(shade == "dark", do: @dark_piece_color, else: @light_piece_color) - - if color != current_color do - %{ - row_state - | current_color: color, - row_chars: row_chars ++ [prefix, color, new_char] - } - else - %{ - row_state - | current_color: current_color, - row_chars: row_chars ++ [prefix, new_char] - } - end - else - %{ - row_state - | current_color: ANSI.default_color(), - row_chars: row_chars ++ [prefix, ANSI.default_color(), char] - } - end - - _ -> - if ANSI.white() != current_color do - %{ - row_state - | current_color: ANSI.default_color(), - row_chars: row_chars ++ [prefix, ANSI.default_color(), char] - } - else - %{ - row_state - | row_chars: row_chars ++ [prefix, char] - } - end - end - end - ) - - row_chars - |> Enum.join("") - end) - end - - def render(%Client.State{ - state_stack: [{_this_module, %State{highlighted: highlighted}} | _] - }) do - board = - draw_board( - "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", - {@tile_width, @tile_height}, - highlighted - ) - - [ANSI.home()] ++ - Enum.map( - Enum.zip(1..length(board), board), - fn {i, line} -> - [ANSI.cursor(i, 0), line] - end - ) - end - - def handle_input( - action, - %Client.State{ - state_stack: [ - {this_module, - %State{ - move_from: move_from, - cursor: %{x: cursor_x, y: cursor_y} = cursor - } = screen_state} - | rest_stack - ] - } = state - ) do - new_cursor = - case action do - :left -> %{y: cursor_y, x: cursor_x - 1} - :right -> %{y: cursor_y, x: cursor_x + 1} - :down -> %{y: cursor_y + 1, x: cursor_x} - :up -> %{y: cursor_y - 1, x: cursor_x} - _ -> cursor - end - - {new_move_from, _move_to} = - if action == :return do - coords = {new_cursor.y, new_cursor.x} - - case move_from do - nil -> {coords, nil} - _ -> {nil, coords} - end - else - {move_from, nil} - end - - %Client.State{ - state - | state_stack: [ - {this_module, - %State{ - screen_state - | cursor: new_cursor, - move_from: new_move_from, - highlighted: %{ - new_move_from => ANSI.green_background(), - {new_cursor.y, new_cursor.x} => ANSI.green_background() - } - }} - | rest_stack - ] - } - end -end diff --git a/lib/chessh/ssh/screens/menu.ex b/lib/chessh/ssh/screens/menu.ex deleted file mode 100644 index d9df1b7..0000000 --- a/lib/chessh/ssh/screens/menu.ex +++ /dev/null @@ -1,104 +0,0 @@ -defmodule Chessh.SSH.Client.Menu do - alias Chessh.SSH.Client - alias Chessh.Utils - alias IO.ANSI - - require Logger - - defmodule State do - defstruct y: 0, - x: 0, - selected: 0 - end - - use Chessh.SSH.Client.Screen - - @logo " Simponic's - dP MP\"\"\"\"\"\"`MM MP\"\"\"\"\"\"`MM M\"\"MMMMM\"\"MM - 88 M mmmmm..M M mmmmm..M M MMMMM MM -.d8888b. 88d888b. .d8888b. M. `YM M. `YM M `M -88' `\"\" 88' `88 88ooood8 MMMMMMM. M MMMMMMM. 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 - MMMMMMMMMMM MMMMMMMMMMM MMMMMMMMMMMM" - - @options [ - {"Option 1", {Chessh.SSH.Client.Board, %Chessh.SSH.Client.Board.State{}}}, - {"Option 2", {Chessh.SSH.Client.Board, %Chessh.SSH.Client.Board.State{}}} - ] - - def render(%Client.State{ - width: width, - height: height, - state_stack: [{_this_module, %State{selected: selected, y: dy, x: dx}} | _] - }) do - text = String.split(@logo, "\n") - {logo_width, logo_height} = Utils.text_dim(@logo) - {y, x} = center_rect({logo_width, logo_height + length(text)}, {width, height}) - - Enum.flat_map( - Enum.zip(1..length(text), text), - fn {i, line} -> - [ - ANSI.cursor(y + i + dy, x + dx), - line - ] - end - ) ++ - Enum.flat_map( - Enum.zip(0..(length(@options) - 1), @options), - fn {i, {option, _}} -> - [ - ANSI.cursor(y + length(text) + i + dy, x + dx), - if(i == selected, do: ANSI.format([:light_cyan, "* #{option}"]), else: option) - ] - end - ) ++ [ANSI.home()] - end - - defp wrap_around(index, delta, length) do - calc = index + delta - if(calc < 0, do: length, else: 0) + rem(calc, length) - end - - def handle_input( - data, - %Client.State{ - state_stack: - [{this_module, %State{selected: selected} = screen_state} | tail] = state_stack - } = state - ) do - case(data) do - :up -> - %Client.State{ - state - | state_stack: [ - {this_module, - %State{screen_state | selected: wrap_around(selected, -1, length(@options))}} - | tail - ] - } - - :down -> - %Client.State{ - state - | state_stack: [ - {this_module, - %State{screen_state | selected: wrap_around(selected, 1, length(@options))}} - | tail - ] - } - - :return -> - {_, new_state} = Enum.at(@options, selected) - - %Client.State{ - state - | state_stack: [new_state] ++ state_stack - } - - _ -> - state - end - end -end diff --git a/lib/chessh/ssh/screens/screen.ex b/lib/chessh/ssh/screens/screen.ex deleted file mode 100644 index 3d9e9ec..0000000 --- a/lib/chessh/ssh/screens/screen.ex +++ /dev/null @@ -1,22 +0,0 @@ -defmodule Chessh.SSH.Client.Screen do - @callback render(state :: Chessh.SSH.Client.State.t() | any()) :: any() - @callback handle_input(action :: any(), state :: Chessh.SSH.Client.State.t()) :: - Chessh.SSH.Client.State.t() - - defmacro __using__(_) do - quote do - @behaviour Chessh.SSH.Client.Screen - - @ascii_chars Application.compile_env!(:chessh, :ascii_chars_json_file) - |> File.read!() - |> Jason.decode!() - - def center_rect({rect_width, rect_height}, {parent_width, parent_height}) do - { - div(parent_height - rect_height, 2), - div(parent_width - rect_width, 2) - } - end - end - end -end diff --git a/mix.exs b/mix.exs index fd5a4b8..e4b0631 100644 --- a/mix.exs +++ b/mix.exs @@ -27,7 +27,7 @@ defmodule Chessh.MixProject do # Run "mix help deps" to learn about dependencies. defp deps do [ - {:chess, "~> 0.4.1"}, + {:binbo, "~> 4.0.2"}, {:ecto, "~> 3.9"}, {:ecto_sql, "~> 3.9"}, {:postgrex, "~> 0.16.5"}, diff --git a/mix.lock b/mix.lock index 48fe5e1..cd09aef 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,6 @@ %{ "bcrypt_elixir": {:hex, :bcrypt_elixir, "3.0.1", "9be815469e6bfefec40fa74658ecbbe6897acfb57614df1416eeccd4903f602c", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "486bb95efb645d1efc6794c1ddd776a186a9a713abf06f45708a6ce324fb96cf"}, + "binbo": {:hex, :binbo, "4.0.3", "f6d884e523504595f4445000c5520eb8532dafb94d34b7c572290795868a12e0", [:rebar3], [{:uef, "2.6.0", [hex: :uef, repo: "hexpm", optional: false]}], "hexpm", "3029775a3afaa3377a9ffac4973a3cfc7aa51c1f4b1ceebd2e546ad0161fe1bb"}, "chess": {:hex, :chess, "0.4.1", "34c04abed2db81e0c56476c8e74fd85ef4e1bae23a4cd528e0ce8a052ada976f", [:mix], [], "hexpm", "692e0def99dc25af4af2413839a4605a2a0a713c2646f9afcf3a47c76a6de43d"}, "comeonin": {:hex, :comeonin, "5.3.3", "2c564dac95a35650e9b6acfe6d2952083d8a08e4a89b93a481acb552b325892e", [:mix], [], "hexpm", "3e38c9c2cb080828116597ca8807bb482618a315bfafd98c90bc22a821cc84df"}, "connection": {:hex, :connection, "1.1.0", "ff2a49c4b75b6fb3e674bfc5536451607270aac754ffd1bdfe175abe4a6d7a68", [:mix], [], "hexpm", "722c1eb0a418fbe91ba7bd59a47e28008a189d47e37e0e7bb85585a016b2869c"}, @@ -16,4 +17,5 @@ "postgrex": {:hex, :postgrex, "0.16.5", "fcc4035cc90e23933c5d69a9cd686e329469446ef7abba2cf70f08e2c4b69810", [:mix], [{:connection, "~> 1.1", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "edead639dc6e882618c01d8fc891214c481ab9a3788dfe38dd5e37fd1d5fb2e8"}, "syn": {:hex, :syn, "3.3.0", "4684a909efdfea35ce75a9662fc523e4a8a4e8169a3df275e4de4fa63f99c486", [:rebar3], [], "hexpm", "e58ee447bc1094bdd21bf0acc102b1fbf99541a508cd48060bf783c245eaf7d6"}, "telemetry": {:hex, :telemetry, "1.1.0", "a589817034a27eab11144ad24d5c0f9fab1f58173274b1e9bae7074af9cbee51", [:rebar3], [], "hexpm", "b727b2a1f75614774cff2d7565b64d0dfa5bd52ba517f16543e6fc7efcc0df48"}, + "uef": {:hex, :uef, "2.6.0", "0aa813d125c429e48c1d5abbab89837d8b7bd0499e6ee2f7dc9cc4287a475cfd", [:rebar3], [], "hexpm", "1585cba305dc7c0a3f75aeab15937b324184673d2922148b432edf9beba73d62"}, }