More room features.

This commit is contained in:
AnduinXue
2024-01-13 10:45:32 +00:00
parent 5a0c99a95b
commit 7453a05b98
12 changed files with 180 additions and 23 deletions
@@ -0,0 +1,20 @@
using System.ComponentModel.DataAnnotations;
using System.Text.RegularExpressions;
namespace Aiursoft.ChessServer.Attributes;
public class ValidNickName : ValidationAttribute
{
private readonly string _keyWordRegex = "^[-a-zA-Z0-9_]+$";
public override bool IsValid(object? value)
{
var regex = new Regex(_keyWordRegex, RegexOptions.Compiled);
return value is string input && regex.IsMatch(input);
}
protected override ValidationResult? IsValid(object? value, ValidationContext validationContext)
{
return this.IsValid(value) ? ValidationResult.Success : new ValidationResult("The " + validationContext.DisplayName + " can only contain numbers, alphabet and underline.");
}
}
@@ -18,18 +18,41 @@ public class HomeController : Controller
_database = database; _database = database;
} }
[HttpGet]
public IActionResult Index() public IActionResult Index()
{ {
var model = new IndexViewModel(_database.Challenges); var model = new IndexViewModel(_database.Challenges);
return View(model); return View(model);
} }
[HttpGet]
public IActionResult Auto(Guid playerId)
{
// I created a challenge. Go to my challenge.
var iHaveAChallenge = _database.Challenges.FirstOrDefault(t => t.Value.Creator.Id == playerId);
if (iHaveAChallenge.Value != null)
{
return RedirectToAction(nameof(Challenge), new { id = iHaveAChallenge.Key, playerId });
}
// Exists a public challenge. Go to that challenge.
var otherChallenge = _database.Challenges.FirstOrDefault(t => t.Value.Permission == ChallengePermission.Public);
if (otherChallenge.Value != null)
{
return RedirectToAction(nameof(Challenge), new { id = otherChallenge.Key, playerId });
}
// Create a new challenge.
return RedirectToAction(nameof(Create), new { playerId });
}
[HttpGet]
public IActionResult Create(Guid playerId) public IActionResult Create(Guid playerId)
{ {
var iHaveAChallenge = _database.Challenges.FirstOrDefault(t => t.Value.Creator.Id == playerId); var iHaveAChallenge = _database.Challenges.FirstOrDefault(t => t.Value.Creator.Id == playerId);
if (iHaveAChallenge.Value != null) if (iHaveAChallenge.Value != null)
{ {
return RedirectToAction(nameof(Room), new { id = iHaveAChallenge.Key }); return RedirectToAction(nameof(Challenge), new { id = iHaveAChallenge.Key, playerId });
} }
var model = new CreateChallengeViewModel(); var model = new CreateChallengeViewModel();
return View(model); return View(model);
@@ -38,6 +61,11 @@ public class HomeController : Controller
[HttpPost] [HttpPost]
public IActionResult Create(CreateChallengeViewModel model) public IActionResult Create(CreateChallengeViewModel model)
{ {
if (!ModelState.IsValid)
{
return View(model);
}
var player = _database.GetOrAddPlayer(model.CreatorId); var player = _database.GetOrAddPlayer(model.CreatorId);
var challenge = new Challenge(player) var challenge = new Challenge(player)
{ {
@@ -46,14 +74,38 @@ public class HomeController : Controller
Permission = model.Permission, Permission = model.Permission,
TimeLimit = model.TimeLimit, TimeLimit = model.TimeLimit,
}; };
var uniqueId = _counter.GetUniqueNo(); var roomId = _counter.GetUniqueNo();
_database.Challenges.TryAdd(uniqueId, challenge); _database.Challenges.TryAdd(roomId, challenge);
return RedirectToAction(nameof(Room), new { id = uniqueId }); return RedirectToAction(nameof(Challenge), new { id = roomId, playerId = model.CreatorId });
} }
public IActionResult Room(int id) [HttpGet]
public IActionResult Challenge(int id, Guid playerId)
{ {
// Not implemented. var player = _database.GetOrAddPlayer(playerId);
return Ok(); var challenge = _database.GetOrAddChallenge(id, player);
var model = new ChallengeViewModel()
{
RoomId = id,
PlayerId = playerId,
IsCreator = challenge.Creator.Id == playerId,
};
return View(model);
}
[HttpPost]
public IActionResult DropChallenge(DropChallengeViewModel model)
{
if (!ModelState.IsValid)
{
return RedirectToAction(nameof(Challenge), new { id = model.Id, playerId = model.PlayerId });
}
var player = _database.GetOrAddPlayer(model.PlayerId);
var challenge = _database.GetOrAddChallenge(model.Id, player);
if (challenge.Creator.Id == model.PlayerId)
{
_database.Challenges.TryRemove(model.Id, out _);
}
return RedirectToAction(nameof(Index));
} }
} }
@@ -1,6 +1,6 @@
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using Aiursoft.ChessServer.Attributes;
using Aiursoft.ChessServer.Data; using Aiursoft.ChessServer.Data;
using Aiursoft.CSTools.Attributes;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
namespace Aiursoft.ChessServer.Controllers; namespace Aiursoft.ChessServer.Controllers;
@@ -24,7 +24,7 @@ public class PlayersController : ControllerBase
[HttpPut] [HttpPut]
[Route("{id:guid}/new-name/{nickname}")] [Route("{id:guid}/new-name/{nickname}")]
public IActionResult ChangeNickname([Required]Guid id, [Required][MaxLength(20)][ValidDomainName]string nickname) public IActionResult ChangeNickname([Required]Guid id, [Required][MaxLength(20)][ValidNickName]string nickname)
{ {
if (ModelState.IsValid == false) if (ModelState.IsValid == false)
{ {
@@ -35,4 +35,12 @@ public class InMemoryDatabase : ISingletonDependency
}); });
} }
} }
public Challenge GetOrAddChallenge(int id, Player creator)
{
lock (Challenges)
{
return Challenges.GetOrAdd(id, _ => new Challenge(creator));
}
}
} }
@@ -0,0 +1,10 @@
namespace Aiursoft.ChessServer.Models;
public class ChallengeViewModel
{
public int RoomId { get; set; }
public Guid PlayerId { get; set; }
public bool IsCreator { get; set; }
}
@@ -1,14 +1,23 @@
namespace Aiursoft.ChessServer.Models; using System.ComponentModel.DataAnnotations;
namespace Aiursoft.ChessServer.Models;
public class CreateChallengeViewModel public class CreateChallengeViewModel
{ {
public Guid CreatorId { get; set; } [Required]
public Guid CreatorId { get; init; }
public string Message { get; set; } = "A chess room."; [Required]
[MinLength(3)]
[MaxLength(20)]
public string Message { get; init; } = "A chess room.";
public RoleRule RoleRule { get; set; } = RoleRule.Random; [Required]
public RoleRule RoleRule { get; init; } = RoleRule.Random;
public TimeSpan TimeLimit { get; set; } = TimeSpan.FromMinutes(10); [Required]
public TimeSpan TimeLimit { get; init; } = TimeSpan.FromMinutes(10);
public ChallengePermission Permission { get; set; } = ChallengePermission.Public; [Required]
public ChallengePermission Permission { get; init; } = ChallengePermission.Public;
} }
@@ -0,0 +1,15 @@
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Mvc;
namespace Aiursoft.ChessServer.Models;
public class DropChallengeViewModel
{
[FromRoute]
[Required]
public int Id { get; set; }
[FromForm]
[Required]
public Guid PlayerId { get; set; }
}
@@ -0,0 +1,38 @@
@model Aiursoft.ChessServer.Models.ChallengeViewModel
<div class="jumbotron">
<div class="container">
<h1 class="display-4">Waiting for joining...</h1>
<p class="lead">Please share the link of this room to your friend!</p>
@{
var link = $"{Context.Request.Scheme}://{Context.Request.Host}/Home/Room/{Model.RoomId}";
}
<form asp-controller="Home" asp-action="DropChallenge" asp-route-id="@Model.RoomId" method="post" class="d-inline" asp-antiforgery="false">
<div asp-validation-summary="All" class="text-danger"></div>
<input type="hidden" name="playerId" value="@Model.PlayerId"/>
<input type="text" class="form-control" value="@link" readonly>
<a class="btn btn-secondary btn-lg mt-4" data-clipboard-text="@link" role="button" data-toggle="tooltip" data-placement="top" title="Copied!">
Copy link
</a>
<button type="submit" class="btn btn-danger btn-lg mt-4">
Leave
</button>
</form>
</div>
</div>
@section scripts
{
<script src="/node_modules/clipboard/dist/clipboard.min.js"></script>
<script>
// Initialize clipboard.
const clipboard = new ClipboardJS('.btn');
// Activate tooltip tool
$('[data-toggle="tooltip"]').tooltip({
trigger: 'click',
placement: 'bottom'
});
</script>
}
@@ -15,7 +15,8 @@
<div class="card mb-2 col-sm-12 px-1"> <div class="card mb-2 col-sm-12 px-1">
<div class="card-body"> <div class="card-body">
<h5 class="card-title">Create a new room</h5> <h5 class="card-title">Create a new room</h5>
<form asp-controller="Home" asp-action="Create" method="post"> <form asp-controller="Home" asp-action="Create" method="post" asp-antiforgery="false">
<div asp-validation-summary="All" class="text-danger"></div>
<input type="hidden" asp-for="CreatorId"/> <input type="hidden" asp-for="CreatorId"/>
<div class="form-group"> <div class="form-group">
<label asp-for="Message"></label> <label asp-for="Message"></label>
@@ -5,7 +5,7 @@
<h1 class="display-4">Welcome challenger!</h1> <h1 class="display-4">Welcome challenger!</h1>
<p class="lead">Join a room, or create a room!</p> <p class="lead">Join a room, or create a room!</p>
<p> <p>
<button class="btn btn-success btn-lg mt-4" role="button">Auto join</button> <a class="btn btn-success btn-lg mt-4" role="button" id="autoButton" asp-controller="Home" asp-action="Auto" >Auto join</a>
<a class="btn btn-secondary btn-lg mt-4" role="button" id="createButton" asp-controller="Home" asp-action="Create">Create a new room</a> <a class="btn btn-secondary btn-lg mt-4" role="button" id="createButton" asp-controller="Home" asp-action="Create">Create a new room</a>
</p> </p>
</div> </div>
@@ -47,11 +47,13 @@
<script type="module"> <script type="module">
import { getUserId } from "/scripts/player.js"; import { getUserId } from "/scripts/player.js";
const createButton = document.getElementById("createButton"); const createButton = document.getElementById("createButton");
const autoButton = document.getElementById("autoButton");
const playerId = getUserId(); const playerId = getUserId();
// Append player id to create button. // Append player id to create button.
if (playerId) { if (playerId) {
createButton.href += `?playerId=${playerId}`; createButton.href += `?playerId=${playerId}`;
autoButton.href += `?playerId=${playerId}`;
} }
</script> </script>
} }
@@ -59,11 +59,12 @@
</a> </a>
</div> </div>
</footer> </footer>
<script src="~/node_modules/jquery/dist/jquery.min.js"></script>
<script type="module" src="~/node_modules/jquery/dist/jquery.min.js" defer></script> <script src="~/node_modules/jquery-validation/dist/jquery.validate.min.js"></script>
<script type="module" src="~/node_modules/bootstrap/dist/js/bootstrap.bundle.min.js" defer></script> <script src="~/node_modules/jquery-validation-unobtrusive/dist/jquery.validate.unobtrusive.min.js"></script>
<script type="module" src="~/node_modules/@@chrisoakman/chessboardjs/dist/chessboard-1.0.0.min.js" defer></script> <script src="~/node_modules/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
<script type="module" src="~/scripts/layout.js" defer></script> <script src="~/node_modules/@@chrisoakman/chessboardjs/dist/chessboard-1.0.0.min.js"></script>
<script type="module" src="~/scripts/layout.js"></script>
@(await RenderSectionAsync("scripts", false)) @(await RenderSectionAsync("scripts", false))
</body> </body>
@@ -8,7 +8,8 @@
"@aiursoft/autodark.js": "^1.2.0", "@aiursoft/autodark.js": "^1.2.0",
"@chrisoakman/chessboardjs": "^1.0.0", "@chrisoakman/chessboardjs": "^1.0.0",
"chess.js": "^1.0.0-beta.6", "chess.js": "^1.0.0-beta.6",
"clipboard": "^2.0.11",
"jquery-validation": "^1.19.5", "jquery-validation": "^1.19.5",
"jquery-validation-unobtrusive": "^3.2.12" "jquery-validation-unobtrusive": "^3.2.12"
} }
} }