Xử Lý Logic Kết Thúc Ván Đấu, Tính Điểm Và Lưu Dữ Liệu Người Chơi Trên Server

Cập nhật logic kiểm tra kết thúc ván đấu

Trong kiến trúc server, component GameControllerComponent cần được điều chỉnh để đánh giá trạng thái ván đấu sau mỗi lượt ra bài. Khi người chơi hiện tại đánh hết bài trên tay, hệ thống sẽ xác định người chiến thắng, yêu cầu các người chơi còn lại lật bài và khởi tạo luồng xử lý kết thúc.

Đường dẫn tệp: \\Server\\Hotfix\\Landlords\\System\\GameControllerComponentSystem.cs

/// <summary>
/// Đánh giá lượt đánh để xác định ván đấu tiếp tục hay kết thúc
/// </summary>
public static void EvaluateTurn(this GameControllerComponent controller, Gamer activePlayer)
{
    Room currentRoom = controller.GetParent<Room>();
    OrderControllerComponent turnManager = currentRoom.GetComponent<OrderControllerComponent>();

    bool isGameOver = activePlayer.GetComponent<HandCardsComponent>().CardsCount == 0;
    
    if (isGameOver)
    {
        Identity winningRole = currentRoom.GetGamerFromUserID(turnManager.Biggest).GetComponent<HandCardsComponent>().AccessIdentity;
        List<PlayerResult> results = new List<PlayerResult>();

        foreach (var player in currentRoom.gamers)
        {
            results.Add(new PlayerResult()
            {
                PlayerId = player.UserID,
                Points = controller.CalculatePlayerScore(player, winningRole)
            });

            if (player.UserID != activePlayer.UserID)
            {
                Card[] remainingCards = player.GetComponent<HandCardsComponent>().GetAll();
                currentRoom.Broadcast(new Actor_RevealCards_Ntt() { PlayerId = player.UserID, Cards = To.RepeatedField(remainingCards) });
            }
        }

        controller.HandleGameEnd(results, winningRole);
    }
    else
    {
        turnManager.Biggest = activePlayer.UserID;
        turnManager.Turn();
        currentRoom.Broadcast(new Actor_NextTurn_Ntt() { PlayerId = turnManager.CurrentAuthority, IsFirstRound = false });
    }
}

Xử lý kết thúc ván đấu, tính điểm và đồng bộ cơ sở dữ liệu

Sau khi xác định ván đấu kết thúc, server cần thực hiện các tác vụ: tính toán điểm số dựa trên vai trò và hệ số nhân, cập nhật số dư tài khoản của người chơi vào database, và phát tán thông báo kết thúc tới tất cả client. Đồng thời, hệ thống cũng kiểm tra các điều kiện để loại bỏ người chơi khỏi phòng (như mất kết nối hoặc không đủ tiền cược cho ván tiếp theo).

/// <summary>
/// Xử lý các tác vụ khi ván đấu kết thúc
/// </summary>
public static async void HandleGameEnd(this GameControllerComponent controller, List<PlayerResult> results, Identity winningRole)
{
    Room currentRoom = controller.GetParent<Room>();
    Gamer[] players = currentRoom.gamers;

    controller.ReturnCardsToDeck();
    currentRoom.GetComponent<DeskCardsCacheComponent>().Clear();
    
    Dictionary<long, long> updatedBalances = new Dictionary<long, long>();
    foreach (PlayerResult result in results)
    {
        Gamer player = currentRoom.GetGamerFromUserID(result.PlayerId);
        long newBalance = await controller.UpdatePlayerBalance(player, result.Points);
        updatedBalances[player.UserID] = newBalance;
    }

    currentRoom.Broadcast(new Actor_GameEnd_Ntt()
    {
        WinningRole = (byte)winningRole,
        BaseRate = controller.BasePointPerMatch,
        Multiplier = controller.Multiples,
        Results = To.RepeatedField(results)
    });

    foreach (var player in players)
    {
        if (player.isOffline)
        {
            // Logic loại bỏ người chơi mất kết nối
        }
        else if (updatedBalances[player.UserID] < controller.MinThreshold)
        {
            // Logic loại bỏ người chơi không đủ số dư tối thiểu
        }
    }
}

/// <summary>
/// Tính toán điểm số cho từng người chơi
/// </summary>
public static long CalculatePlayerScore(this GameControllerComponent controller, Gamer player, Identity winningRole)
{
    HandCardsComponent hand = player.GetComponent<HandCardsComponent>();

    // Công thức: Điểm cơ bản * Hệ số nhân bàn * Hệ số vai trò
    long score = controller.BasePointPerMatch * controller.Multiples * (int)hand.AccessIdentity;
    if (hand.AccessIdentity != winningRole)
    {
        score = -score;
    }
    return score;
}

/// <summary>
/// Cập nhật số dư tài khoản và lưu vào cơ sở dữ liệu
/// </summary>
public static async Task<long> UpdatePlayerBalance(this GameControllerComponent controller, Gamer player, long scoreChange)
{
    DBProxyComponent dbProxy = Game.Scene.GetComponent<DBProxyComponent>();

    UserInfo userData = await dbProxy.Query<UserInfo>(player.UserID);
    long finalMoney = userData.Money + scoreChange;
    userData.Money = finalMoney < 0 ? 0 : finalMoney;

    await dbProxy.Save(userData);

    return userData.Money;
}

Định nghĩa giao thức Protobuf

Các message mới cần được khai báo trong tệp .proto để hỗ trợ việc truyền tải dữ liệu điểm số và thông báo kết thúc ván đấu giữa server và client.

Đường dẫn tệp: \\Proto\\HotfixMessage.proto

message PlayerResult
{
    int64 PlayerId = 1;
    int64 Points = 2;
}

message Actor_GameEnd_Ntt // IActorMessage
{
    int32 RpcId = 90;
    int64 ActorId = 93;
    int32 WinningRole = 1; 
    int64 BaseRate = 2;
    int32 Multiplier = 3;
    repeated PlayerResult Results = 4;
}

message Actor_InsufficientFunds_Ntt // IActorMessage
{
    int32 RpcId = 90;
    int64 ActorId = 93;
    int64 PlayerId = 1;
}

Quy trình dọn dẹp và tái sử dụng phòng đấu

Sau khi giao diện kết thúc ván đấu được hiển thị ở phía client, server cần quản lý vòng đời của phòng đấu dựa trên hành vi của người chơi. Nếu không có người chơi nào yêu cầu bắt đầu ván mới, server sẽ giải phóng toàn bộ dữ liệu người chơi và phòng, đồng thời gửi thông báo buộc tất cả client thoát về sảnh. Ngược lại, nếu có yêu cầu chơi tiếp, server sẽ chuyển trạng thái phòng về chế độ chờ (idle), cập nhật trạng thái matching của người chơi, và chỉ gửi thông báo rời phòng cho những người chơi không tiếp tục tham gia.

Thẻ: ET Framework C# protobuf Game Server Actor Model

Đăng vào ngày 4 tháng 9 lúc 04:29