Add functionality to accept a challenge in chess game
Introduced capability to accept a challenge, updated challenge viewer model to include challenge id, and updated the In-Memory database to handle adding new games. The corresponding endpoint was updated and the front-end scripts were added to handle challenge acceptance and game redirection. Changes were also made to ensure challenges are only seen when not accepted in the public view.
This commit is contained in:
@@ -4,6 +4,7 @@ using Aiursoft.ChessServer.Data;
|
||||
using Aiursoft.ChessServer.Models;
|
||||
using Aiursoft.ChessServer.Models.ViewModels;
|
||||
using Aiursoft.CSTools.Services;
|
||||
using Aiursoft.WebTools.Attributes;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Aiursoft.ChessServer.Controllers;
|
||||
@@ -17,7 +18,7 @@ public class HomeController(
|
||||
{
|
||||
var model = new IndexViewModel
|
||||
{
|
||||
Challenges = database.GetPublicChallenges()
|
||||
Challenges = database.GetPublicUnAcceptedChallenges()
|
||||
};
|
||||
return View(model);
|
||||
}
|
||||
@@ -119,7 +120,7 @@ public class HomeController(
|
||||
}
|
||||
var model = new ChallengeViewModel
|
||||
{
|
||||
RoomId = id,
|
||||
ChallengeId = id,
|
||||
};
|
||||
return View(model);
|
||||
}
|
||||
@@ -139,6 +140,40 @@ public class HomeController(
|
||||
return RedirectToAction(nameof(Index));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method accepts a challenge and updates the challenge's accepter.
|
||||
/// </summary>
|
||||
/// <param name="id">The ID of the challenge to accept.</param>
|
||||
/// <param name="playerId">The ID of the player accepting the challenge.</param>
|
||||
/// <returns>An IActionResult indicating the result of the operation.</returns>
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> AcceptChallenge([FromRoute]int id, [FromQuery]Guid playerId)
|
||||
{
|
||||
var challenge = database.GetChallenge(id);
|
||||
if (challenge == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
if (challenge.Accepter != null)
|
||||
{
|
||||
// Challenge already accepted.
|
||||
return BadRequest("Challenge already accepted.");
|
||||
}
|
||||
if (challenge.Creator.Id == playerId)
|
||||
{
|
||||
// Cannot accept your own challenge.
|
||||
return BadRequest("Cannot accept your own challenge!");
|
||||
}
|
||||
challenge.Accepter = database.GetOrAddPlayer(playerId);
|
||||
var (newGameId, newGame) = database.AddNewGameAndGetId();
|
||||
challenge.Game = newGame;
|
||||
challenge.GameId = newGameId;
|
||||
await challenge.ChallengeChangedChannel.BroadcastAsync("game-started-at-" + newGameId);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[Route("listen-challenge/{id:int}.ws")]
|
||||
[EnforceWebSocket]
|
||||
public async Task ListenChallenge(int id)
|
||||
{
|
||||
var pusher = await HttpContext.AcceptWebSocketClient();
|
||||
|
||||
@@ -25,6 +25,20 @@ public class InMemoryDatabase : ISingletonDependency
|
||||
return Games.GetOrAdd(id, _ => new Game());
|
||||
}
|
||||
}
|
||||
|
||||
public (int gameId, Game game) AddNewGameAndGetId()
|
||||
{
|
||||
lock (Games)
|
||||
{
|
||||
for (var id = 1;; id++)
|
||||
{
|
||||
if (Games.ContainsKey(id)) continue;
|
||||
var newGame = new Game();
|
||||
Games.TryAdd(id, newGame);
|
||||
return (id, newGame);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Player GetOrAddPlayer(Guid id)
|
||||
{
|
||||
@@ -37,12 +51,13 @@ public class InMemoryDatabase : ISingletonDependency
|
||||
}
|
||||
}
|
||||
|
||||
public IReadOnlyCollection<KeyValuePair<int, Challenge>> GetPublicChallenges()
|
||||
public IReadOnlyCollection<KeyValuePair<int, Challenge>> GetPublicUnAcceptedChallenges()
|
||||
{
|
||||
lock (Challenges)
|
||||
{
|
||||
return Challenges
|
||||
.Where(t => t.Value.Permission == ChallengePermission.Public)
|
||||
.Where(t => t.Value.Accepter == null)
|
||||
.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ public class Challenge(Player creator)
|
||||
|
||||
public Player? Accepter { get; set; } = null;
|
||||
|
||||
public int? GameId { get; set; } = null;
|
||||
public Game? Game { get; set; } = null;
|
||||
|
||||
public RoleRule RoleRule { get; set; } = RoleRule.Random;
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
|
||||
public class ChallengeViewModel
|
||||
{
|
||||
public int RoomId { get; init; }
|
||||
public int ChallengeId { get; init; }
|
||||
}
|
||||
@@ -6,9 +6,9 @@
|
||||
<h1 class="display-4">Waiting for opponent 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/{nameof(HomeController.Challenge)}/{Model.RoomId}";
|
||||
var link = $"{Context.Request.Scheme}://{Context.Request.Host}/Home/{nameof(HomeController.Challenge)}/{Model.ChallengeId}";
|
||||
}
|
||||
<form asp-controller="Home" asp-action="DropChallenge" asp-route-id="@Model.RoomId" method="post" class="d-inline" asp-antiforgery="false">
|
||||
<form asp-controller="Home" asp-action="DropChallenge" asp-route-id="@Model.ChallengeId" method="post" class="d-inline" asp-antiforgery="false">
|
||||
<div asp-validation-summary="All" class="text-danger"></div>
|
||||
<input type="hidden" name="playerId" value=""/>
|
||||
|
||||
@@ -42,4 +42,33 @@
|
||||
placement: 'bottom'
|
||||
});
|
||||
</script>
|
||||
<script type="module">
|
||||
import { acceptChallenge, getUserId } from "/scripts/player.js";
|
||||
|
||||
// Listen to the channel
|
||||
const wsScheme = window.location.protocol === "https:" ? "wss://" : "ws://";
|
||||
const socket = new WebSocket(
|
||||
`${wsScheme}${window.location.host}/listen-challenge/${@Model.ChallengeId}.ws`
|
||||
);
|
||||
|
||||
socket.addEventListener('open', function (event) {
|
||||
console.log('WebSocket is open now.');
|
||||
|
||||
// Accept the challenge
|
||||
acceptChallenge(@Model.ChallengeId, getUserId());
|
||||
});
|
||||
|
||||
socket.onmessage = function (event) {
|
||||
// alert(event.data);
|
||||
// May get data like: game-started-at-{newGameId}
|
||||
if (event.data.startsWith('game-started-at-')) {
|
||||
const gameId = event.data.replace('game-started-at-', '');
|
||||
const gameIdNumber = parseInt(gameId);
|
||||
if (gameIdNumber) {
|
||||
window.location.href = `/games/${gameId}.html`;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
</script>
|
||||
}
|
||||
@@ -22,4 +22,9 @@ const changeName = async function (newName) {
|
||||
await fetch(`/players/${getUserId()}/new-name/${newName}`, { method: 'PUT' });
|
||||
}
|
||||
|
||||
export { getUserId, getUserName, changeName };
|
||||
const acceptChallenge = async function (challengeId, playerId) {
|
||||
// call /home/AcceptChallenge/{challengeId}?playerId={playerId} HTTP POST API:
|
||||
await fetch(`/home/AcceptChallenge/${challengeId}?playerId=${playerId}`, { method: 'POST' });
|
||||
}
|
||||
|
||||
export { getUserId, getUserName, changeName, acceptChallenge };
|
||||
Reference in New Issue
Block a user