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:
parent
1e411e2e73
commit
2314dc6a19
@ -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%;
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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!"),
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user