reintroduce microsoft login. upgrade a bunch of stuff.
This commit is contained in:
11
Shogi.UI/Pages/Home/GameBoard/EmptyGameBoard.razor
Normal file
11
Shogi.UI/Pages/Home/GameBoard/EmptyGameBoard.razor
Normal file
@@ -0,0 +1,11 @@
|
||||
@using Contracts.Types;
|
||||
|
||||
<GameBoardPresentation Perspective="WhichPlayer.Player1" />
|
||||
|
||||
@code {
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
base.OnInitialized();
|
||||
Console.WriteLine("Empty Game Board.");
|
||||
}
|
||||
}
|
||||
45
Shogi.UI/Pages/Home/GameBoard/GameBoard.razor
Normal file
45
Shogi.UI/Pages/Home/GameBoard/GameBoard.razor
Normal file
@@ -0,0 +1,45 @@
|
||||
@using Shogi.Contracts.Api
|
||||
@using Shogi.Contracts.Types;
|
||||
@using System.Text.RegularExpressions;
|
||||
@inject IShogiApi ShogiApi
|
||||
@inject AccountState Account;
|
||||
@inject PromotePrompt PromotePrompt;
|
||||
|
||||
@if (session == null)
|
||||
{
|
||||
<EmptyGameBoard />
|
||||
}
|
||||
else if (isSpectating)
|
||||
{
|
||||
<SpectatorGameBoard Session="session" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<SeatedGameBoard Perspective="perspective" Session="session" />
|
||||
}
|
||||
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public string? SessionName { get; set; }
|
||||
|
||||
Session? session;
|
||||
private WhichPlayer perspective;
|
||||
private bool isSpectating;
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(SessionName))
|
||||
{
|
||||
this.session = await ShogiApi.GetSession(SessionName);
|
||||
if (this.session != null)
|
||||
{
|
||||
var accountId = Account.User?.Id;
|
||||
|
||||
this.perspective = accountId == session.Player2 ? WhichPlayer.Player2 : WhichPlayer.Player1;
|
||||
this.isSpectating = !(accountId == this.session.Player1 || accountId == this.session.Player2);
|
||||
Console.WriteLine($"IsSpectating - {isSpectating}. AccountId - {accountId}. Player1 - {this.session.Player1}. Player2 - {this.session.Player2}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
116
Shogi.UI/Pages/Home/GameBoard/GameBoardPresentation.razor
Normal file
116
Shogi.UI/Pages/Home/GameBoard/GameBoardPresentation.razor
Normal file
@@ -0,0 +1,116 @@
|
||||
@using Shogi.Contracts.Types;
|
||||
@inject PromotePrompt PromotePrompt;
|
||||
|
||||
<article class="game-board">
|
||||
<!-- Game board -->
|
||||
<section class="board" data-perspective="@Perspective">
|
||||
@for (var rank = 1; rank < 10; rank++)
|
||||
{
|
||||
foreach (var file in Files)
|
||||
{
|
||||
var position = $"{file}{rank}";
|
||||
var piece = Session?.BoardState.Board[position];
|
||||
var isSelected = piece != null && SelectedPosition == position;
|
||||
<div class="tile" @onclick="OnClickTileInternal(piece, position)"
|
||||
data-position="@(position)"
|
||||
data-selected="@(isSelected)"
|
||||
style="grid-area: @position">
|
||||
<GamePiece Piece="piece" Perspective="Perspective" />
|
||||
</div>
|
||||
}
|
||||
}
|
||||
<div class="ruler vertical" style="grid-area: rank">
|
||||
<span>9</span>
|
||||
<span>8</span>
|
||||
<span>7</span>
|
||||
<span>6</span>
|
||||
<span>5</span>
|
||||
<span>4</span>
|
||||
<span>3</span>
|
||||
<span>2</span>
|
||||
<span>1</span>
|
||||
</div>
|
||||
<div class="ruler" style="grid-area: file">
|
||||
<span>A</span>
|
||||
<span>B</span>
|
||||
<span>C</span>
|
||||
<span>D</span>
|
||||
<span>E</span>
|
||||
<span>F</span>
|
||||
<span>G</span>
|
||||
<span>H</span>
|
||||
<span>I</span>
|
||||
</div>
|
||||
<!-- Promote prompt -->
|
||||
<div class="promote-prompt" data-visible="@PromotePrompt.IsVisible">
|
||||
<p>Do you wish to promote?</p>
|
||||
<div>
|
||||
<button type="button">Yes</button>
|
||||
<button type="button">No</button>
|
||||
<button type="button">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<!-- Side board -->
|
||||
@if (Session != null)
|
||||
{
|
||||
<aside class="side-board">
|
||||
<div class="hand">
|
||||
@foreach (var piece in OpponentHand)
|
||||
{
|
||||
<div class="tile">
|
||||
<GamePiece Piece="piece" Perspective="Perspective" />
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="spacer" />
|
||||
|
||||
<div class="hand">
|
||||
@foreach (var piece in UserHand)
|
||||
{
|
||||
<div class="title" @onclick="OnClickHandInternal(piece)">
|
||||
<GamePiece Piece="piece" Perspective="Perspective" />
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</aside>
|
||||
}
|
||||
</article>
|
||||
|
||||
@code {
|
||||
[Parameter] public WhichPlayer Perspective { get; set; }
|
||||
[Parameter] public Session? Session { get; set; }
|
||||
[Parameter] public string? SelectedPosition { get; set; }
|
||||
// TODO: Exchange these OnClick actions for events like "SelectionChangedEvent" and "MoveFromBoardEvent" and "MoveFromHandEvent".
|
||||
[Parameter] public Action<Piece?, string>? OnClickTile { get; set; }
|
||||
[Parameter] public Action<Piece>? OnClickHand { get; set; }
|
||||
|
||||
static readonly string[] Files = new[] { "A", "B", "C", "D", "E", "F", "G", "H", "I" };
|
||||
|
||||
private IReadOnlyCollection<Piece> OpponentHand
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.Session == null) return Array.Empty<Piece>();
|
||||
|
||||
return Perspective == WhichPlayer.Player1
|
||||
? this.Session.BoardState.Player1Hand
|
||||
: this.Session.BoardState.Player2Hand;
|
||||
}
|
||||
}
|
||||
IReadOnlyCollection<Piece> UserHand
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.Session == null) return Array.Empty<Piece>();
|
||||
|
||||
return Perspective == WhichPlayer.Player1
|
||||
? this.Session.BoardState.Player1Hand
|
||||
: this.Session.BoardState.Player2Hand;
|
||||
}
|
||||
}
|
||||
|
||||
private Action OnClickTileInternal(Piece? piece, string position) => () => OnClickTile?.Invoke(piece, position);
|
||||
private Action OnClickHandInternal(Piece piece) => () => OnClickHand?.Invoke(piece);
|
||||
}
|
||||
112
Shogi.UI/Pages/Home/GameBoard/GameboardPresentation.razor.css
Normal file
112
Shogi.UI/Pages/Home/GameBoard/GameboardPresentation.razor.css
Normal file
@@ -0,0 +1,112 @@
|
||||
.game-board {
|
||||
display: grid;
|
||||
grid-template-areas: "board side-board";
|
||||
grid-template-columns: 3fr minmax(10rem, 1fr);
|
||||
gap: 0.5rem;
|
||||
background-color: #444;
|
||||
}
|
||||
|
||||
.board {
|
||||
grid-area: board;
|
||||
}
|
||||
|
||||
.side-board {
|
||||
grid-area: side-board;
|
||||
}
|
||||
|
||||
.board {
|
||||
position: relative;
|
||||
display: grid;
|
||||
grid-template-areas:
|
||||
"rank A9 B9 C9 D9 E9 F9 G9 H9 I9"
|
||||
"rank A8 B8 C8 D8 E8 F8 G8 H8 I8"
|
||||
"rank A7 B7 C7 D7 E7 F7 G7 H7 I7"
|
||||
"rank A6 B6 C6 D6 E6 F6 G6 H6 I6"
|
||||
"rank A5 B5 C5 D5 E5 F5 G5 H5 I5"
|
||||
"rank A4 B4 C4 D4 E4 F4 G4 H4 I4"
|
||||
"rank A3 B3 C3 D3 E3 F3 G3 H3 I3"
|
||||
"rank A2 B2 C2 D2 E2 F2 G2 H2 I2"
|
||||
"rank A1 B1 C1 D1 E1 F1 G1 H1 I1"
|
||||
". file file file file file file file file file";
|
||||
grid-template-columns: auto repeat(9, minmax(0, 1fr));
|
||||
grid-template-rows: repeat(9, minmax(0, 1fr)) auto;
|
||||
max-height: calc(100vh - 3rem);
|
||||
width: calc(100vmin * 0.9167);
|
||||
aspect-ratio: 0.9167;
|
||||
gap: 3px;
|
||||
}
|
||||
|
||||
.board[data-perspective="Player2"] {
|
||||
grid-template-areas:
|
||||
"file file file file file file file file file ."
|
||||
"I1 H1 G1 F1 E1 D1 C1 B1 A1 rank"
|
||||
"I2 H2 G2 F2 E2 D2 C2 B2 A2 rank"
|
||||
"I3 H3 G3 F3 E3 D3 C3 B3 A3 rank"
|
||||
"I4 H4 G4 F4 E4 D4 C4 B4 A4 rank"
|
||||
"I5 H5 G5 F5 E5 D5 C5 B5 A5 rank"
|
||||
"I6 H6 G6 F6 E6 D6 C6 B6 A6 rank"
|
||||
"I7 H7 G7 F7 E7 D7 C7 B7 A7 rank"
|
||||
"I8 H8 G8 F8 E8 D8 C8 B8 A8 rank"
|
||||
"I9 H9 G9 F9 E9 D9 C9 B9 A9 rank";
|
||||
grid-template-columns: repeat(9, minmax(0, 1fr)) auto;
|
||||
grid-template-rows: auto repeat(9, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.tile {
|
||||
background-color: beige;
|
||||
display: grid;
|
||||
place-content: center;
|
||||
padding: 0.25rem;
|
||||
overflow: hidden; /* Because SVGs are shaped weird */
|
||||
transition: filter linear 0.25s;
|
||||
}
|
||||
|
||||
.tile[data-selected] {
|
||||
filter: invert(0.8);
|
||||
}
|
||||
|
||||
.ruler {
|
||||
color: beige;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.ruler.vertical {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.board[data-perspective="Player2"] .ruler {
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
|
||||
.board[data-perspective="Player2"] .ruler.vertical {
|
||||
flex-direction: column-reverse;
|
||||
}
|
||||
|
||||
.side-board {
|
||||
display: grid;
|
||||
grid-template-rows: auto 1fr auto;
|
||||
}
|
||||
|
||||
.side-board .hand {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.promote-prompt {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
border: 2px solid #444;
|
||||
background-color: #eaeaea;
|
||||
padding: 1rem;
|
||||
box-shadow: 1px 1px 1px #444;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.promote-prompt[data-visible="true"] {
|
||||
display: block;
|
||||
}
|
||||
71
Shogi.UI/Pages/Home/GameBoard/SeatedGameBoard.razor
Normal file
71
Shogi.UI/Pages/Home/GameBoard/SeatedGameBoard.razor
Normal file
@@ -0,0 +1,71 @@
|
||||
@using Shogi.Contracts.Api;
|
||||
@using Shogi.Contracts.Types;
|
||||
@using System.Text.RegularExpressions;
|
||||
@inject PromotePrompt PromotePrompt;
|
||||
@inject IShogiApi ShogiApi;
|
||||
|
||||
<GameBoardPresentation OnClickHand="OnClickHand" OnClickTile="OnClickTile" Session="Session" Perspective="Perspective" />
|
||||
|
||||
@code {
|
||||
[Parameter] public WhichPlayer Perspective { get; set; }
|
||||
[Parameter] public Session Session { get; set; }
|
||||
private bool IsMyTurn => Session?.BoardState.WhoseTurn == Perspective;
|
||||
private string? selectedBoardPosition;
|
||||
private WhichPiece? selectedPieceFromHand;
|
||||
|
||||
bool ShouldPromptForPromotion(string position)
|
||||
{
|
||||
if (Perspective == WhichPlayer.Player1 && Regex.IsMatch(position, ".[7-9]"))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (Perspective == WhichPlayer.Player2 && Regex.IsMatch(position, ".[1-3]"))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
async void OnClickTile(Piece? piece, string position)
|
||||
{
|
||||
if (!IsMyTurn) return;
|
||||
|
||||
if (selectedBoardPosition == null || piece?.Owner == Perspective)
|
||||
{
|
||||
// Select a position.
|
||||
selectedBoardPosition = position;
|
||||
return;
|
||||
}
|
||||
if (selectedBoardPosition == position)
|
||||
{
|
||||
// Deselect the selected position.
|
||||
selectedBoardPosition = null;
|
||||
return;
|
||||
}
|
||||
if (piece == null)
|
||||
{
|
||||
if (ShouldPromptForPromotion(position) || ShouldPromptForPromotion(selectedBoardPosition))
|
||||
{
|
||||
PromotePrompt.Show(Session.SessionName, new MovePieceCommand
|
||||
{
|
||||
From = selectedBoardPosition,
|
||||
To = position
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
await ShogiApi.Move(Session.SessionName, new MovePieceCommand
|
||||
{
|
||||
From = selectedBoardPosition,
|
||||
IsPromotion = false,
|
||||
To = position
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OnClickHand(Piece piece)
|
||||
{
|
||||
selectedPieceFromHand = piece.WhichPiece;
|
||||
}
|
||||
}
|
||||
8
Shogi.UI/Pages/Home/GameBoard/SpectatorGameBoard.razor
Normal file
8
Shogi.UI/Pages/Home/GameBoard/SpectatorGameBoard.razor
Normal file
@@ -0,0 +1,8 @@
|
||||
@using Contracts.Types;
|
||||
|
||||
<p>You are spectating</p>
|
||||
<GameBoardPresentation Perspective="WhichPlayer.Player1" Session="Session" />
|
||||
|
||||
@code {
|
||||
[Parameter] public Session Session { get; set; }
|
||||
}
|
||||
Reference in New Issue
Block a user