Created GameController, and generic controller. Created some methods in the client to send and receive data

This commit is contained in:
Martin Berg Alstad 2023-05-18 19:24:15 +02:00
parent ef85dec657
commit 19c87bae68
13 changed files with 237 additions and 72 deletions

View File

@ -1,5 +1,5 @@
import React from "react";
import {Counter} from "./pages/Counter";
import {Counter} from "./pages/counter";
import Home from "./pages/home";
const AppRoutes = [

View File

@ -7,12 +7,14 @@ interface IWebSocket {
export default class WebSocketService {
private ws?: WebSocket;
private readonly _url: string;
private _onOpen?: VoidFunction;
private _onReceive?: MessageEventFunction;
private _onClose?: VoidFunction;
private _onError?: VoidFunction;
constructor({onOpen, onReceive, onClose, onError}: IWebSocket) {
constructor(url: string, {onOpen, onReceive, onClose, onError}: IWebSocket = {}) {
this._url = url;
this._onOpen = onOpen;
this._onReceive = onReceive;
this._onClose = onClose;
@ -20,22 +22,48 @@ export default class WebSocketService {
}
public open(): void {
this.ws = new WebSocket("wss://localhost:3000/api/ws");
this.ws = new WebSocket(this._url);
}
public registerEvents(): void {
if (!this.ws) return;
if (this._onOpen) this.ws.onopen = this._onOpen;
if (this._onReceive) this.ws.onmessage = this._onReceive;
if (this._onClose) this.ws.onclose = this._onClose;
if (this._onError) this.ws.onerror = this._onError;
}
public send(data: string | ArrayBufferLike | Blob | ArrayBufferView) {
public send(data: WebSocketData): void {
this.ws?.send(data);
}
public close() {
this.ws?.close();
public async sendAndReceive<T>(data: WebSocketData): Promise<T> {
if (!this.isOpen()) return Promise.reject("WebSocket is not open");
let result: T | undefined;
this.onReceive = (event: MessageEvent) => {
result = JSON.parse(event.data) as T;
};
this.send(data);
return new Promise<T>((resolve) => {
function f() {
if (result === undefined) {
setTimeout(f, 50);
return;
}
}
f();
return resolve(result!);
});
}
public isOpen() {
public async close(): Promise<void> {
return new Promise(() => this.ws?.close());
}
public isOpen(): boolean {
return this.ws?.readyState === WebSocket.OPEN;
}

View File

@ -0,0 +1,23 @@
import React from "react";
interface AllDiceProps extends ComponentProps {
values: number[],
}
export const AllDice: Component<AllDiceProps> = ({className, values}) => {
return (
<>
{values?.map((value, index) => <Dice key={index} className={className}/>)}
</>
);
};
interface DiceProps extends ComponentProps {
value?: number,
}
export const Dice: Component<DiceProps> = ({className, value}) => {
return (
<div className={className}>{value?.toString()}</div>
);
};

View File

@ -1,18 +1,36 @@
import React from "react";
import GameCanvas from "../components/gameCanvas";
import Game from "../game/game";
import {AllDice} from "./dice";
export const GameComponent: Component = () => {
const [dice, setDice] = React.useState<number[]>([0, 0]);
React.useEffect(() => {
const game = new Game();
const id = setInterval(game.gameLoop, 1000);
return () => clearInterval(id);
let game: Game = new Game();
game.connectToServer();
function f() {
if (!game.isConnected()) {
setTimeout(f, 50);
return;
}
game.gameLoop(setDice);
}
f();
// TODO only call gameLoop after the previous one has finished
// const id = setInterval(() => game.gameLoop(), 5000);
// return () => clearInterval(id);
}, []);
React.useEffect(() => {
console.log(dice);
}, [dice]);
return (
<div>
<h1 className={"w-fit mx-auto"}>Pac-Man</h1>
<AllDice values={dice}/>
<GameCanvas className={"mx-auto"}/>
</div>
);

View File

@ -1,6 +1,11 @@
import WebSocketService from "../classes/WebSocketService";
export default class Game {
private wsService: WebSocketService;
constructor() {
this.wsService = new WebSocketService("wss://localhost:3000/api/game");
// Connect to the server
// Create players
@ -10,8 +15,12 @@ export default class Game {
// Roll to start
}
public gameLoop(): void {
public gameLoop(setDice: Setter<number[]>): void {
// Throw the dices
this.rollDice().then((dices) => {
console.log(dices);
setDice(dices);
});
// Choose a dice and move pac-man or a ghost
@ -22,8 +31,13 @@ export default class Game {
// If not, next player
}
private connectToServer(): void {
throw new Error("Not implemented");
public connectToServer(): void {
this.wsService.open();
this.wsService.registerEvents();
}
public isConnected(): boolean {
return this.wsService.isOpen();
}
private createPlayers(): void {
@ -38,8 +52,10 @@ export default class Game {
throw new Error("Not implemented");
}
private throwDices(): number[] {
throw new Error("Not implemented");
private async rollDice(): Promise<number[]> {
let result: number[];
result = await this.wsService.sendAndReceive<number[]>("roll");
return result;
}
private chooseDice(dices: number[]): number {

View File

@ -1,19 +1,19 @@
import React from "react";
import WebSocketService from "../classes/WebSocketService";
const ws = new WebSocketService({});
const ws = new WebSocketService("wss://localhost:3000/api/ws");
export const Counter: Component = () => {
const [currentCount, setCurrentCount] = React.useState(0);
function incrementCounterAndSend() {
async function incrementCounterAndSend() {
if (ws.isOpen()) {
ws.send((currentCount + 1).toString());
await ws.send((currentCount + 1).toString());
}
}
function receiveMessage(data: MessageEvent<any>) {
function receiveMessage(data: MessageEvent<string>) {
const count = parseInt(data.data);
if (!isNaN(count))
setCurrentCount(count);
@ -22,6 +22,7 @@ export const Counter: Component = () => {
React.useEffect(() => {
ws.onReceive = receiveMessage;
ws.open();
ws.registerEvents();
return () => {
ws.close();
};

View File

@ -1,3 +1,5 @@
type MessageEventFunction = (data: MessageEvent<any>) => void;
type Setter<T> = React.Dispatch<React.SetStateAction<T>>;
type WebSocketData = string | ArrayBufferLike | Blob | ArrayBufferView;

View File

@ -0,0 +1,35 @@
using System.Net.WebSockets;
using System.Text;
using System.Text.Json;
using Microsoft.AspNetCore.Mvc;
using pacMan.Game.Interfaces;
using pacMan.Game.Items;
using pacMan.Interfaces;
using pacMan.Utils;
namespace pacMan.Controllers;
[ApiController]
[Route("api/[controller]")]
public class GameController : GenericController
{
private readonly IDiceCup _diceCup;
public GameController(ILogger<GameController> logger, IWebSocketService wsService) : base(logger, wsService)
{
_diceCup = new DiceCup();
}
[HttpGet]
public override async Task Accept() => await base.Accept();
protected override ArraySegment<byte> Run(WebSocketReceiveResult result, byte[] data)
{
var stringResult = data.GetString(data.Length);
Logger.Log(LogLevel.Information, "Received: {}", stringResult);
var rolls = _diceCup.Roll();
Logger.Log(LogLevel.Information, "Rolled {}", string.Join(", ", rolls));
return rolls.ToArraySegment();
}
}

View File

@ -0,0 +1,63 @@
using System.Net.WebSockets;
using Microsoft.AspNetCore.Mvc;
using pacMan.Interfaces;
namespace pacMan.Controllers;
public abstract class GenericController : ControllerBase
{
protected readonly ILogger<GenericController> Logger;
private readonly IWebSocketService _wsService;
private const int BufferSize = 1024 * 4;
protected GenericController(ILogger<GenericController> logger, IWebSocketService wsService)
{
Logger = logger;
_wsService = wsService;
Logger.Log(LogLevel.Debug, "WebSocket Controller created");
}
public virtual async Task Accept()
{
if (HttpContext.WebSockets.IsWebSocketRequest)
{
using var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync();
Logger.Log(LogLevel.Information, "WebSocket connection established to {}", HttpContext.Connection.Id);
_wsService.Add(webSocket);
await Echo(webSocket);
}
else
{
HttpContext.Response.StatusCode = StatusCodes.Status400BadRequest;
}
}
protected virtual async Task Echo(WebSocket webSocket)
{
try
{
var buffer = new byte[BufferSize];
WebSocketReceiveResult? result;
do
{
result = await _wsService.Receive(webSocket, buffer);
if (result.CloseStatus.HasValue) break;
var segment = Run(result, buffer);
await _wsService.SendToAll(segment);
} while (true);
await _wsService.Close(webSocket, result.CloseStatus.Value, result.CloseStatusDescription ?? "No reason");
}
catch (WebSocketException e)
{
Logger.Log(LogLevel.Error, "{}", e.Message);
}
_wsService.Remove(webSocket);
}
protected abstract ArraySegment<byte> Run(WebSocketReceiveResult result, byte[] data);
}

View File

@ -6,57 +6,18 @@ namespace pacMan.Controllers;
[ApiController]
[Route("api/[controller]")]
public class WsController : ControllerBase
public class WsController : GenericController
{
private readonly ILogger<WsController> _logger;
private readonly IWebSocketService _wsService;
private const int BufferSize = 1024 * 4;
public WsController(ILogger<WsController> logger, IWebSocketService wsService)
public WsController(ILogger<WsController> logger, IWebSocketService wsService) : base(logger, wsService)
{
_logger = logger;
_wsService = wsService;
_logger.Log(LogLevel.Debug, "WebSocket Controller created");
}
[HttpGet]
public async Task Get()
public override async Task Accept() => await base.Accept();
protected override ArraySegment<byte> Run(WebSocketReceiveResult result, byte[] data)
{
if (HttpContext.WebSockets.IsWebSocketRequest)
{
using var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync();
_logger.Log(LogLevel.Information, "WebSocket connection established to {}", HttpContext.Connection.Id);
_wsService.Add(webSocket);
await Echo(webSocket);
}
else
{
HttpContext.Response.StatusCode = StatusCodes.Status400BadRequest;
}
}
private async Task Echo(WebSocket webSocket)
{
try
{
var buffer = new byte[BufferSize];
WebSocketReceiveResult? result;
do
{
result = await _wsService.Receive(webSocket, buffer);
if (result.CloseStatus.HasValue) break;
await _wsService.SendToAll(buffer, result.Count);
} while (true);
await _wsService.Close(webSocket, result.CloseStatus.Value, result.CloseStatusDescription ?? "No reason");
}
catch (WebSocketException e)
{
_logger.Log(LogLevel.Error, "{}", e.Message);
}
_wsService.Remove(webSocket);
var segment = new ArraySegment<byte>(data, 0, result.Count);
return segment;
}
}

View File

@ -8,8 +8,10 @@ public interface IWebSocketService
bool Remove(WebSocket webSocket);
Task Send(WebSocket webSocket, string message, int length);
Task Send(WebSocket webSocket, byte[] message, int length);
Task Send(WebSocket webSocket, ArraySegment<byte> segment);
Task SendToAll(string message, int length);
Task SendToAll(byte[] message, int length);
Task SendToAll(ArraySegment<byte> segment);
Task<WebSocketReceiveResult> Receive(WebSocket webSocket, byte[] buffer);
Task Close(WebSocket webSocket, WebSocketCloseStatus closeStatus, string closeStatusDescription);
int CountConnected();

View File

@ -8,7 +8,7 @@ namespace pacMan.Services;
public class WebSocketService : IWebSocketService
{
private readonly ILogger<WebSocketService> _logger;
private readonly SynchronizedCollection<WebSocket> _webSockets = new();
private readonly SynchronizedCollection<WebSocket> _webSockets = new(); // TODO separate connections into groups
public WebSocketService(ILogger<WebSocketService> logger)
{
@ -38,15 +38,18 @@ public class WebSocketService : IWebSocketService
public async Task Send(WebSocket webSocket, byte[] message, int length)
{
var msgSegment = new ArraySegment<byte>(message, 0, length);
await Send(webSocket, msgSegment);
}
public async Task Send(WebSocket webSocket, ArraySegment<byte> segment)
{
await webSocket.SendAsync(
msgSegment,
segment,
WebSocketMessageType.Text,
true,
CancellationToken.None);
_logger.Log(LogLevel.Trace,
"Message \"{}\" sent to WebSocket",
message.GetString(length));
_logger.Log(LogLevel.Trace, "Message sent to WebSocket");
}
public async Task SendToAll(string message, int length)
@ -62,6 +65,11 @@ public class WebSocketService : IWebSocketService
_logger.Log(LogLevel.Debug, "Message sent to all WebSockets");
}
public async Task SendToAll(ArraySegment<byte> segment)
{
foreach (var ws in _webSockets) await Send(ws, segment);
}
public async Task<WebSocketReceiveResult> Receive(WebSocket webSocket, byte[] buffer)
{
var result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
@ -80,6 +88,6 @@ public class WebSocketService : IWebSocketService
CancellationToken.None);
_logger.Log(LogLevel.Information, "WebSocket connection closed");
}
public int CountConnected() => _webSockets.Count;
}

View File

@ -1,4 +1,5 @@
using System.Text;
using System.Text.Json;
namespace pacMan.Utils;
@ -8,4 +9,11 @@ public static class Extensions
{
return Encoding.UTF8.GetString(bytes, 0, length);
}
public static ArraySegment<byte> ToArraySegment(this object obj)
{
var json = JsonSerializer.Serialize(obj);
var bytes = Encoding.UTF8.GetBytes(json);
return new ArraySegment<byte>(bytes, 0, json.Length);
}
}