1
0
mirror of https://github.com/danbee/chess synced 2025-03-04 08:39:06 +00:00

Game now works using LiveView

This commit is contained in:
Daniel Barber 2023-10-25 16:21:02 -05:00
parent 1e411e2e73
commit 2314dc6a19
12 changed files with 119 additions and 80 deletions

View File

@ -8,16 +8,13 @@
width: 1.5%;
}
.board__container {
grid-area: board;
}
.board {
background: $background-color;
border-collapse: unset;
border-radius: 2.8%;
border-spacing: 1px;
color: $foreground-color;
grid-area: board;
height: var(--board-size);
padding: calc(var(--board-size) / 20);
position: relative;
@ -61,14 +58,6 @@
display: flex;
flex-direction: row;
}
.board__body {
flex-direction: column-reverse;
}
.board__row {
flex-direction: row;
}
}
.board--player-is-black {
@ -81,45 +70,20 @@
display: flex;
flex-direction: row-reverse;
}
.board__body {
flex-direction: column;
}
.board__row {
flex-direction: row-reverse;
}
}
.board__body {
background: $background-color;
border: 0.25rem solid $foreground-color;
border-radius: calc(var(--board-size) / 100);
display: flex;
flex-direction: column;
display: grid;
grid-template-columns: repeat(8, 1fr);
grid-template-rows: repeat(8, 1fr);
height: 100%;
padding: 1%;
width: 100%;
}
.board__row {
display: flex;
flex-grow: 1;
@include odd-between(1, 8) {
.square {
@include odd-between(1, 8) { @extend %square--black; }
@include even-between(1, 8) { @extend %square--white; }
}
}
@include even-between(1, 8) {
.square {
@include odd-between(1, 8) { @extend %square--white; }
@include even-between(1, 8) { @extend %square--black; }
}
}
}
.board__rank-labels {
display: none;
height: 90%;

View File

@ -1,6 +1,8 @@
defmodule Chess.Board do
@moduledoc false
require Logger
def search(board, %{"type" => type, "colour" => colour}) do
board
|> Enum.filter(fn {_index, piece} ->
@ -17,9 +19,13 @@ defmodule Chess.Board do
|> indexes_to_tuples
end
def piece(board, {file, rank}) do
board["#{file},#{rank}"]
end
def move_piece(board, %{
"from" => [from_file, from_rank],
"to" => [to_file, to_rank]
from: {from_file, from_rank},
to: {to_file, to_rank}
}) do
{piece, board} = Map.pop(board, to_index({from_file, from_rank}))
{piece_captured, board} = Map.pop(board, to_index({to_file, to_rank}))
@ -54,15 +60,15 @@ defmodule Chess.Board do
def castling_move(board, %{from: {4, rank}, to: {2, _rank}}) do
move_piece(board, %{
"from" => [0, rank],
"to" => [3, rank]
from: {0, rank},
to: {3, rank}
})
end
def castling_move(board, %{"from" => [4, rank], "to" => [6, _rank]}) do
move_piece(board, %{
"from" => [7, rank],
"to" => [5, rank]
from: {7, rank},
to: {5, rank}
})
end

View File

@ -14,6 +14,8 @@ defmodule Chess.Moves do
alias Chess.Moves.Pieces.Queen
alias Chess.Moves.Pieces.King
require Logger
def make_move(game, move_params) do
params =
game.board

View File

@ -39,7 +39,8 @@ defmodule ChessWeb do
# Import convenience functions from controllers
import Phoenix.Controller,
only: [get_csrf_token: 0, get_flash: 2, view_module: 1]
import Phoenix.LiveView.Helpers
import Phoenix.Component
# Use all HTML functionality (forms, tags, etc)
use Phoenix.HTML
@ -52,11 +53,10 @@ defmodule ChessWeb do
end
end
def live_view do
quote do
use Phoenix.LiveView,
layout: {ChessWeb.LayoutView, "live.html"}
layout: {ChessWeb.LayoutView, :live}
unquote(view_helpers())
end
@ -90,6 +90,7 @@ defmodule ChessWeb do
def router do
quote do
use Phoenix.Router
import Phoenix.LiveView.Router
end
end

View File

@ -5,7 +5,6 @@ defmodule ChessWeb.GameChannel do
import ChessWeb.GameView, only: [player: 2, opponent: 2]
alias Chess.Board
alias Chess.Emails
alias Chess.Mailer
alias Chess.MoveList
@ -29,7 +28,7 @@ defmodule ChessWeb.GameChannel do
opponent_id: opponent(game, socket.assigns.user_id).id,
player: player(game, socket.assigns.user_id),
opponent: opponent(game, socket.assigns.user_id).name,
board: Board.transform(game.board),
board: game.board,
turn: game.turn,
state: game.state,
moves: MoveList.transform(game.moves)
@ -135,7 +134,7 @@ defmodule ChessWeb.GameChannel do
|> Queries.game_with_moves(socket.assigns.game_id)
payload = %{
board: Board.transform(game.board),
board: game.board,
turn: game.turn,
state: game.state,
moves: MoveList.transform(game.moves)

View File

@ -4,15 +4,14 @@ defmodule ChessWeb.Endpoint do
@session_options [
store: :cookie,
key: "_chess_key",
signing_salt: "9LqUhZTU"
signing_salt: "9LqUhZTU",
same_site: "Lax"
]
if sandbox = Application.compile_env(:chess, :sandbox) do
plug(Phoenix.Ecto.SQL.Sandbox, sandbox: sandbox)
end
socket("/socket", ChessWeb.UserSocket)
socket("/live", Phoenix.LiveView.Socket, websocket: [connect_info: [session: @session_options]])
# Serve at "/" the static files from "priv/static" directory.

View File

@ -42,13 +42,6 @@ defmodule ChessWeb.Router do
resources("/password", PasswordController, only: [:edit, :update], singleton: true)
end
# Other scopes may use custom stacks.
scope "/api", as: :api do
pipe_through([:api, :auth, :ensure_auth])
resources("/opponents", ChessWeb.Api.OpponentsController, only: [:index])
end
if Mix.env() == :dev do
forward("/sent_emails", Bamboo.SentEmailViewerPlug)
end

View File

@ -21,9 +21,12 @@
<div class="board__label">h</div>
</div>
<% rank_range = if white?(@user, @game), do: 7..0, else: 0..7 %>
<% file_range = if black?(@user, @game), do: 7..0, else: 0..7 %>
<div class="board__body">
<%= for rank <- 0..7 do %>
<%= for file <- 0..7 do %>
<%= for rank <- rank_range do %>
<%= for file <- file_range do %>
<%= render ChessWeb.SquareView,
"square.html",
rank: rank,
@ -36,6 +39,6 @@
</div>
<div class="game-state game-state--<%= @game.state %>">
<%= states(@game.state) %>
<%= state_text(@game.state) %>
</div>
</div>

View File

@ -15,10 +15,23 @@
</tr>
</thead>
<tbody>
<tr>
<th scope="row" class="move-list__line-number">1.</th>
<td class="move-list__move move-list__move--white">e4</td>
</tr>
<%= for {move_pair, i} <- @game.moves |>
Enum.chunk_every(2) |> Enum.with_index do %>
<tr>
<th scope="row" class="move-list__line-number"><%= i + 1 %>.</th>
<%= case move_pair do %>
<% [white_move, black_move] -> %>
<td class="move-list__move move-list__move--<%=
white_move.piece["colour"] %>"><%= move_text(white_move) %></td>
<td class="move-list__move move-list__move--<%=
black_move.piece["colour"] %>"><%= move_text(black_move) %></td>
<% [white_move] -> %>
<td class="move-list__move move-list__move--<%=
white_move.piece["colour"] %>"><%= move_text(white_move) %></td>
<td></td>
<% end %>
</tr>
<% end %>
</tbody>
</table>
</div>

View File

@ -6,6 +6,15 @@ defmodule ChessWeb.GameView do
import Phoenix.Component
import Chess.Auth, only: [current_user: 1]
@pieces %{
pawn: "",
knight: "N",
bishop: "B",
rook: "R",
queen: "Q",
king: "K"
}
def won_lost(user, game) do
if game_over?(game) && game.state == "checkmate" do
(your_turn?(user, game) &&
@ -21,7 +30,7 @@ defmodule ChessWeb.GameView do
def state(user, game) do
cond do
GameState.game_over?(game) ->
states(game.state)
state_text(game.state)
your_turn?(user, game) ->
gettext("Your turn")
@ -37,6 +46,14 @@ defmodule ChessWeb.GameView do
end
end
def white?(user, game) do
player_colour(user, game) == "white"
end
def black?(user, game) do
player_colour(user, game) == "black"
end
def your_turn?(user, game) do
user
|> player_colour(game) == game.turn
@ -47,7 +64,7 @@ defmodule ChessWeb.GameView do
end
def piece(board, {file, rank}) do
board["#{file},#{rank}"]
Chess.Board.piece(board, {file, rank})
end
def files(user, game) do
@ -79,7 +96,19 @@ defmodule ChessWeb.GameView do
end
end
def states(state) do
def move_text(move) do
move = Chess.Store.Move.transform(move)
piece_type = move.piece["type"] |> String.to_atom()
[
@pieces[piece_type],
move.to
]
|> Enum.join()
end
def state_text(state) do
Map.get(
%{
"checkmate" => gettext("Checkmate!"),

View File

@ -3,12 +3,18 @@ defmodule ChessWeb.BoardLive do
alias Chess.Store.User
alias Chess.Store.Game
alias Chess.Store.Move
alias Chess.Repo
alias Chess.Board
alias Chess.Moves
alias ChessWeb.GameView
import Ecto.Query
require Logger
def render(assigns) do
Phoenix.View.render(ChessWeb.GameView, "board.html", assigns)
Phoenix.View.render(GameView, "board.html", assigns)
end
def mount(_params, %{"user_id" => user_id, "game_id" => game_id}, socket) do
@ -41,6 +47,7 @@ defmodule ChessWeb.BoardLive do
end
def handle_info(%{event: "move", payload: state}, socket) do
Logger.info("Handling move from board")
{:noreply, assign(socket, state)}
end
@ -49,7 +56,7 @@ defmodule ChessWeb.BoardLive do
board = game.board
user = socket.assigns[:user]
colour = ChessWeb.GameView.player_colour(user, game)
colour = GameView.player_colour(user, game)
assigns =
if colour == game.turn do
@ -66,7 +73,7 @@ defmodule ChessWeb.BoardLive do
end
defp handle_selection(board, colour, file, rank) do
case Board.piece(board, {file, rank}) do
case GameView.piece(board, {file, rank}) do
%{"colour" => ^colour} ->
[
{:selected, {file, rank}},
@ -88,14 +95,21 @@ defmodule ChessWeb.BoardLive do
|> Moves.make_move(%{from: selected, to: {file, rank}})
|> case do
{:ok, %{game: game}} ->
board = Board.transform(game.board)
broadcast_move(game, board)
game
|> Repo.preload([:user, :opponent])
|> Repo.preload(
moves:
from(
move in Move,
order_by: [asc: move.inserted_at]
)
)
|> broadcast_move(game.board)
[
{:selected, nil},
{:available, []},
{:board, board},
{:board, game.board},
{:game, game}
]
end

View File

@ -3,23 +3,39 @@ defmodule ChessWeb.GameInfoLive do
alias Chess.Store.User
alias Chess.Store.Game
alias Chess.Store.Move
alias Chess.Repo
import Ecto.Query
require Logger
def render(assigns) do
Phoenix.View.render(ChessWeb.GameView, "game_info.html", assigns)
end
def mount(_params, %{"game_id" => game_id, "user_id" => user_id}, socket) do
ChessWeb.Endpoint.subscribe("game:#{game_id}")
user = Repo.get!(User, user_id)
game =
Game.for_user(user)
|> preload(:user)
|> preload(:opponent)
|> preload(
moves:
^from(
move in Move,
order_by: [asc: move.inserted_at]
)
)
|> Repo.get!(game_id)
{:ok, assign(socket, game: game, user: user)}
end
def handle_info(%{event: "move", payload: state}, socket) do
{:noreply, assign(socket, state)}
end
end