【愚公系列】2022年12月 .NET CORE 即时通讯-使用SignalR进行井字游戏

举报
愚公搬代码 发表于 2022/12/30 23:11:02 2022/12/30
【摘要】 前言ASP.NET Core SignalR 是一个开放源代码库,可用于简化向应用添加实时 Web 功能。 实时 Web 功能使服务器端代码能够将内容推送到客户端。 一、SignalR的基本使用SignalR的基本使用步骤如下所示:创建 Web 项目。添加 SignalR 客户端库。创建 SignalR 中心。配置项目以使用 SignalR。添加可将消息从任何客户端发送到所有连接客户端的代...

前言

ASP.NET Core SignalR 是一个开放源代码库,可用于简化向应用添加实时 Web 功能。 实时 Web 功能使服务器端代码能够将内容推送到客户端。

一、SignalR的基本使用

SignalR的基本使用步骤如下所示:

  • 创建 Web 项目。
  • 添加 SignalR 客户端库。
  • 创建 SignalR 中心。
  • 配置项目以使用 SignalR。
  • 添加可将消息从任何客户端发送到所有连接客户端的代码。

本文以.NET 7为例,创建 Web 项目就不做多说明。

1.添加 SignalR 客户端库

ASP.NET Core 共享框架中包含 SignalR 服务器库。 JavaScript 客户端库不会自动包含在项目中。使用库管理器 (LibMan) 从 unpkg 获取客户端库。 unpkg 是一个快速的全局内容分发网络,适用于 npm 上的所有内容。

1、在“解决方案资源管理器”>中,右键单击项目,然后选择“添加”“客户端库”。
在这里插入图片描述

2、在“添加客户端库”对话框中

  • 为“提供程序”选择“unpkg”
  • 对于“库”,输入 @microsoft/signalr@latest
  • 选择“选择特定文件”,展开“dist/browser”文件夹,然后选择 signalr.js 和 signalr.min.js。
  • 将“目标位置”设置为 wwwroot/lib/microsoft/signalr/
  • 选择“安装”

在这里插入图片描述

2.创建 SignalR 中心

中心是一个类,用作处理客户端 - 服务器通信的高级管道。

GameHub.cs

public class GameHub : Hub
{
    private static int Id;
    private static Game _game = new();

    public Task Join(IHubContext<GameHub> hubContext)
    {
        var id = Interlocked.Increment(ref Id);
        _game.AddPlayer(Context.ConnectionId, hubContext);
        return Clients.Caller.SendAsync("PlayerRegistered", id);
    }

    public void Result(int square)
    {
        if (Context.ConnectionId == _game.ClientTurn)
        {
            _game.ClientResult.SetResult(square);
        }
        else
        {
            //...
        }
    }
}

Game.cs

public class Game
{
    private string _id1;
    private string _id2;
    private int[] _squares = new int[9];

    //添加玩家,2个人就开始游戏
    public void AddPlayer(string id, IHubContext<GameHub> hubContext)
    {
        if (string.IsNullOrEmpty(_id1))
        {
            _id1 = id;
        }
        else
        {
            _id2 = id;
            _ = RunGame(hubContext);
        }
    }
    //开始玩游戏
    public async Task RunGame(IHubContext<GameHub> hubContext)
    {
        //取消多线程
        var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
        while (true)
        {
            var square = await hubContext.Clients.Client(_id1).InvokeAsync<int>("Turn", cts.Token);
            _squares[square - 1] = 1;
            await hubContext.Clients.Client(_id2).SendAsync("Disable", square);
            if (await GameOver())
            {
                break;
            }

            ResetCts(ref cts);
            square = await hubContext.Clients.Client(_id2).InvokeAsync<int>("Turn", cts.Token);
            _squares[square - 1] = 2;
            await hubContext.Clients.Client(_id1).SendAsync("Disable", square);
            if (await GameOver())
            {
                break;
            }
            ResetCts(ref cts);
        }
        cts.Dispose();

        #region GameOver check
        async Task<bool> GameOver()
        {
            if (_squares[0] != 0 && _squares[0] == _squares[1] && _squares[1] == _squares[2])
            {
                await hubContext.Clients.Clients(_id1, _id2).SendAsync("Win", _squares[0]);
                return true;
            }
            if (_squares[0] != 0 && _squares[0] == _squares[3] && _squares[3] == _squares[6])
            {
                await hubContext.Clients.Clients(_id1, _id2).SendAsync("Win", _squares[0]);
                return true;
            }
            if (_squares[0] != 0 && _squares[0] == _squares[4] && _squares[4] == _squares[8])
            {
                await hubContext.Clients.Clients(_id1, _id2).SendAsync("Win", _squares[0]);
                return true;
            }
            if (_squares[1] != 0 && _squares[1] == _squares[4] && _squares[4] == _squares[7])
            {
                await hubContext.Clients.Clients(_id1, _id2).SendAsync("Win", _squares[1]);
                return true;
            }
            if (_squares[2] != 0 && _squares[2] == _squares[4] && _squares[4] == _squares[6])
            {
                await hubContext.Clients.Clients(_id1, _id2).SendAsync("Win", _squares[2]);
                return true;
            }
            if (_squares[2] != 0 && _squares[2] == _squares[5] && _squares[5] == _squares[8])
            {
                await hubContext.Clients.Clients(_id1, _id2).SendAsync("Win", _squares[2]);
                return true;
            }
            if (_squares[3] != 0 && _squares[3] == _squares[4] && _squares[4] == _squares[5])
            {
                await hubContext.Clients.Clients(_id1, _id2).SendAsync("Win", _squares[3]);
                return true;
            }
            if (_squares[6] != 0 && _squares[6] == _squares[7] && _squares[7] == _squares[8])
            {
                await hubContext.Clients.Clients(_id1, _id2).SendAsync("Win", _squares[6]);
                return true;
            }
            return false;
        }
        #endregion
    }

    private void ResetCts(ref CancellationTokenSource cts)
    {
        if (cts.TryReset())
        {
            cts.CancelAfter(TimeSpan.FromSeconds(30));
        }
        else
        {
            cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
        }
    }

    public TaskCompletionSource<int>? ClientResult { get; private set; }
    
    public string? ClientTurn { get; private set; }
}

在这里插入图片描述

3.配置 SignalR

必须将 SignalR 服务器配置为将 SignalR 请求传递给 SignalR。 将以下突出显示的代码添加到 Program.cs 文件。

//添加SignalR服务
builder.Services.AddSignalR();
//配置路由
app.MapHub<GameHub>("/game");

在这里插入图片描述

4.添加 SignalR 客户端代码

使用以下代码替换 Pages/Index.cshtml 中的内容:

@page
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
<link rel="stylesheet" href="~/WebApplication17.styles.css" asp-append-version="true" />
<div id="main">
    <input type="text" id="b1" readonly>
   
    <input type="text" id="b2" readonly>
   
    <input type="text" id="b3" readonly>
    <br><br>
   
    <input type="text" id="b4" readonly>
               
    <input type="text" id="b5" readonly>
   
    <input type="text" id="b6" readonly>
    <br><br>
   
    <input type="text" id="b7" readonly>
   
    <input type="text" id="b8" readonly>
   
    <input type="text" id="b9" readonly>
</div>
<p id="playerTurn"></p>
<br />
<p id="playerId"></p>

<input type="button" id="join" value="Join Game" />

<script src="~/lib/microsoft/signalr/dist/browser/signalr.js"></script>
@*井字游戏的规则是:在一个井字格子的棋盘里下棋,横竖斜一旦三子连子,则胜。而事实上,遵循一定的规则,该游戏便能保证不败,即至少是平局。*@
<script src="~/js/tictactoe.js"></script>

tictactoe.js是井字游戏,规则是:在一个井字格子的棋盘里下棋,横竖斜一旦三子连子,则胜。而事实上,遵循一定的规则,该游戏便能保证不败,即至少是平局。

"use strict";

let b1 = document.getElementById("b1");
let b2 = document.getElementById("b2");
let b3 = document.getElementById("b3");
let b4 = document.getElementById("b4");
let b5 = document.getElementById("b5");
let b6 = document.getElementById("b6");
let b7 = document.getElementById("b7");
let b8 = document.getElementById("b8");
let b9 = document.getElementById("b9");
let buttons = [b1, b2, b3, b4, b5, b6, b7, b8, b9];
disableButtons();

var connection = new signalR.HubConnectionBuilder().withUrl("/game").build();
let playerId = 0;
connection.on("PlayerRegistered", function (id) {
    if (document.getElementById("join").disabled) {
        if (playerId === 0) {
            playerId = id;
            document.getElementById("playerId").innerHTML = `You are ${id === 1 ? "X" : "O"}`;
        }
        if (id === 1) {
            document.getElementById("playerTurn").innerHTML = "waiting for another player";
        } else {
            document.getElementById("playerTurn").innerHTML = "other players turn";
        }
    }
});

let promise;


connection.on("Win", function (winId) {
    disableButtons();
    if (winId === playerId) {
        alert("you win");
    } else {
        alert("other player won");
    }
});

connection.on("Disable", function (square) {
    buttons[square - 1].disabled = true;
    if (buttons[square - 1].value === '') {
        buttons[square - 1].value = playerId === 1 ? 'O' : 'X';
    }
});

connection.start().then(function () {
}).catch(function (err) {
    return console.error(err.toString());
});

let promiseResolve;

function disableButtons() {
    for (let i = 0; i < 9; i++) {
        buttons[i].disabled = true;
        buttons[i].removeEventListener("click", onClick);
    }
}

connection.on("Turn", function () {
    document.getElementById("playerTurn").innerHTML = "your turn";
    enableButtons();
    return promise;
});

function enableButtons() {
    promise = new Promise(function (resolve, reject) {
        promiseResolve = resolve;
    });
    for (let i = 0; i < 9; i++) {
        if (buttons[i].disabled && buttons[i].value === '') {
            buttons[i].disabled = false;
            buttons[i].addEventListener("click", onClick);
        }
    }
}

function onClick(element) {
    document.getElementById("playerTurn").innerHTML = "other players turn";
    element.target.value = playerId === 1 ? 'X' : 'O';
    disableButtons();
    promiseResolve(Number(element.target.id.split('b')[1]));
}

document.getElementById("join").addEventListener("click", function (event) {
    document.getElementById("join").disabled = true;
    connection.send("Join");
});

在这里插入图片描述

5.运行应用

在这里插入图片描述

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。