hubConnection 에서 커넥트가 최초 성공하면 수신기 만들어죠

protected override async Task OnInitializedAsync()
{
    LoginId = Guid.NewGuid().ToString();    //Fake Login ID

    hubConnection = new HubConnectionBuilder()
        .WithUrl(_navigationManager.ToAbsoluteUri("/chathub"))
        .Build();

    await hubConnection.StartAsync();

    // 수신기 설정
    hubConnection.On<RoomInfo, UserInfo, UpdateUserPos>("OnJoinRoom", (room, user, pos) =>
    {
        Console.WriteLine($"WS - OnJoinRoom");
        if (user.Id == LoginId)
        {
            Name = user.Name;
            RoomName = room.Name;

            SyncRoom syndMsg = new SyncRoom()
            {
                UserInfo = new UserInfo() { Name = "user", Id = LoginId },
                RoomInfo = new RoomInfo() { Name = "room1" }
            };

            hubConnection.SendAsync("SyncRoom", syndMsg).Wait();
        }
        else
        {
            _chatLand.AddUser(user.Id, user.Name, pos.AbsPosX, pos.AbsPosY, false, resource);
        }
    });

    // 다른 수신기 설정
    hubConnection.On<UserInfo, List<UpdateUserPos>>("OnSyncRoom", (user, updateUserPos) =>
    {
        Console.WriteLine($"WS - OnSyncRoom");
        if (user.Id == LoginId)
        {
            foreach (var pos in updateUserPos)
            {
                bool isMine = pos.Id == LoginId ? true : false;

                _chatLand.AddUser(pos.Id, pos.Name, pos.AbsPosX, pos.AbsPosY, isMine, resource);
            }
        }
    });

    //ChatMessage
    hubConnection.On<ChatMessage>("OnChatMessage", (chatMessage) =>
    {
        bool isMine = chatMessage.From.Id == LoginId ? true : false;
        _messages.Add(new Message(chatMessage.From.Name, chatMessage.Message, isMine));
        Console.WriteLine($"WS - OnChatMessage");
        _chatLand.ChatMessage(chatMessage);

        // Inform blazor the UI needs updating
        StateHasChanged();
    });

    hubConnection.On<UpdateUserPos>("OnUpdateUserPos", (userPos) =>
    {
        Console.WriteLine($"WS - OnUpdateUserPos");
        _chatLand.UpdateUserPos(userPos);
    });

    hubConnection.On<LeaveRoom>("OnLeaveRoom", (room) =>
    {
        Console.WriteLine($"WS - OnLeaveRoom");
        _chatLand.RemoveUser(room.UserInfo.Id);

        StateHasChanged();
    });

    JoinRoom sendMsg = new JoinRoom()
    {
        UserInfo = new UserInfo() { Name = "user", Id = LoginId },
        RoomInfo = new RoomInfo() { Name = "room1" }
    };

    await hubConnection.SendAsync("JoInRoom", sendMsg);
}




  ChatMessage 의 History를 큐로 최대 50개까지만 최신기준 유지하고싶습니다. 그리고 이 히스토리를 뷰티 json형태로 반환하는 함수도 만들어

using System.Collections.Concurrent;

namespace BlazorChatApp.Server.Hubs
{
    public class RoomActor : ReceiveActor
    {
        private readonly ILoggingAdapter log = Context.GetLogger();
        public Dictionary<string, UpdateUserPos> users = new Dictionary<string, UpdateUserPos>();
        private string roomName;
        private int userAutoNo = 0;
        private readonly IServiceScopeFactory scopeFactory;
        private readonly ConcurrentQueue<ChatMessage> chatHistory = new ConcurrentQueue<ChatMessage>();
        private const int MaxChatHistoryCount = 50;
        Random random = new Random();

        public RoomActor(string _roomName, IServiceScopeFactory _scopeFactory)
        {
            scopeFactory = _scopeFactory;
            roomName = _roomName;
            log.Info($"Create Room{roomName}");

            Receive<RoomCmd>(cmd => {
                log.Info("Received String message: {0}", cmd);
                //Sender.Tell(message);                
            });

            Receive<JoinRoom>(async cmd => {
                userAutoNo++;
                string jsonString = JsonSerializer.Serialize(cmd);
                log.Info("Received JoinRoom message: {0}", jsonString);
                string RandomColor = string.Format("#{0:X6}", random.Next(0xFFFFFF));

                UserInfo userInfo = new UserInfo()
                {
                    Id = cmd.UserInfo.Id,
                    Name = $"User-{userAutoNo}",
                    Color = RandomColor
                };

                // Default Position
                double posx = random.Next(0, 300);
                double posy = random.Next(400, 550);

                UpdateUserPos updateUserPos = new UpdateUserPos()
                {
                    Id = cmd.UserInfo.Id,
                    Name = $"User-{userAutoNo}",
                    PosX = posx,
                    PosY = posy,
                    AbsPosX = posx,
                    AbsPosY = posy,
                    ConnectionId = cmd.ConnectionId
                };

                if (!users.ContainsKey(updateUserPos.Id))
                    users[cmd.UserInfo.Id] = updateUserPos;

                await OnJoinRoom(cmd.RoomInfo, userInfo, updateUserPos);
            });

            Receive<SyncRoom>(async cmd => {
                string jsonString = JsonSerializer.Serialize(cmd);
                log.Info("Received SyncRoom message: {0}", jsonString);

                List<UpdateUserPos> updateUserPosList = users.Values.ToList();
                string RandomColor = string.Format("#{0:X6}", random.Next(0xFFFFFF));

                UserInfo userInfo = new UserInfo()
                {
                    Id = cmd.UserInfo.Id,
                    Name = $"User-{userAutoNo}",
                    Color = RandomColor
                };

                await OnSyncRoom(userInfo, updateUserPosList);
            });

            Receive<ChatMessage>(async cmd => {
                string jsonString = JsonSerializer.Serialize(cmd);
                log.Info("Received ChatMessage message: {0}", jsonString);

                ChatMessage chatMessage = new ChatMessage()
                {
                    From = cmd.From,
                    Message = cmd.Message
                };

                AddChatMessageToHistory(chatMessage);
                await OnChatMessage(chatMessage);
            });

            Receive<UpdateUserPos>(async cmd => {
                string jsonString = JsonSerializer.Serialize(cmd);
                log.Info("Received UpdateUserPos message: {0}", jsonString);

                double AbsPosX = users[cmd.Id].PosX + cmd.PosX;
                double AbsPosY = users[cmd.Id].PosY + cmd.PosY;

                if (users.ContainsKey(cmd.Id))
                {
                    users[cmd.Id].PosX = AbsPosX;
                    users[cmd.Id].PosY = AbsPosY;
                    users[cmd.Id].AbsPosX = AbsPosX;
                    users[cmd.Id].AbsPosY = AbsPosY;
                }

                log.Info($"UpdateUser : X=>{users[cmd.Id].AbsPosX} Y=>{users[cmd.Id].AbsPosY}");

                UpdateUserPos updateUserPos = new UpdateUserPos()
                {
                    Id = cmd.Id,
                    Name = cmd.Name,
                    PosX = cmd.PosX,
                    PosY = cmd.PosY,
                    AbsPosX = AbsPosX,
                    AbsPosY = AbsPosY
                };

                await OnUpdateUserPos(updateUserPos);
            });

            Receive<Disconnect>(async cmd => {
                string jsonString = JsonSerializer.Serialize(cmd);
                log.Info("Received Disconnect message: {0}", jsonString);
                var disconnectUser = users.Values.Where(e => e.ConnectionId == cmd.ConnectionId).FirstOrDefault();

                if (disconnectUser != null)
                {
                    if (users.ContainsKey(disconnectUser.Id))
                    {
                        users.Remove(disconnectUser.Id);

                        var leaveMsg = new LeaveRoom()
                        {
                            UserInfo = new UserInfo() { Id = disconnectUser.Id }
                        };
                        await OnLeaveRoom(leaveMsg);
                    }
                }
            });

            Receive<LeaveRoom>(async cmd => {
                string jsonString = JsonSerializer.Serialize(cmd);
                log.Info("Received LeaveRoom message: {0}", jsonString);

                if (users.ContainsKey(cmd.UserInfo.Id))
                {
                    users.Remove(cmd.UserInfo.Id);
                    await OnLeaveRoom(cmd);
                }
            });
        }

        private void AddChatMessageToHistory(ChatMessage chatMessage)
        {
            chatHistory.Enqueue(chatMessage);
            while (chatHistory.Count > MaxChatHistoryCount)
            {
                chatHistory.TryDequeue(out _);
            }
        }

        public string GetChatHistoryAsJson()
        {
            return JsonSerializer.Serialize(chatHistory.ToList());
        }

        public async Task OnJoinRoom(RoomInfo roomInfo, UserInfo user, UpdateUserPos updateUserPos)
        {
            using (var scope = scopeFactory.CreateScope())
            {
                var wsHub = scope.ServiceProvider.GetRequiredService<IHubContext<ChatHub>>();
                await wsHub.Clients.All.SendAsync("OnJoinRoom", roomInfo, user, updateUserPos);
            }
        }

        public async Task OnSyncRoom(UserInfo user, List<UpdateUserPos> updateUserPos)
        {
            using (var scope = scopeFactory.CreateScope())
            {
                var wsHub = scope.ServiceProvider.GetRequiredService<IHubContext<ChatHub>>();
                await wsHub.Clients.All.SendAsync("OnSyncRoom", user, updateUserPos);
            }
        }

        public async Task OnLeaveRoom(LeaveRoom leaveRoom)
        {
            using (var scope = scopeFactory.CreateScope())
            {
                var wsHub = scope.ServiceProvider.GetRequiredService<IHubContext<ChatHub>>();
                await wsHub.Clients.All.SendAsync("OnLeaveRoom", leaveRoom);
            }
        }

        public async Task OnUpdateUserPos(UpdateUserPos updatePos)
        {
            using (var scope = scopeFactory.CreateScope())
            {
                var wsHub = scope.ServiceProvider.GetRequiredService<IHubContext<ChatHub>>();
                await wsHub.Clients.All.SendAsync("OnUpdateUserPos", updatePos);
            }
        }

        public async Task OnChatMessage(ChatMessage chatMessage)
        {
            using (var scope = scopeFactory.CreateScope())
            {
                var wsHub = scope.ServiceProvider.GetRequiredService<IHubContext<ChatHub>>();
                await wsHub.Clients.All.SendAsync("OnChatMessage", chatMessage);
            }
        }
    }
}



RoomActor에 사람이 아닌 자동응답을 하는 봇객체를 1개 추가하고싶습니다. JoinRoom 이벤트를 활용해 UserInfo를 추가 참고로 해당 모델은 액터모델이며 액터모델 특성을 고려해 작성

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;

using Akka.Actor;
using Akka.Event;

using BlazorChatApp.Shared;

using Microsoft.AspNetCore.SignalR;
using Microsoft.AspNetCore.SignalR.Client;
using Microsoft.Extensions.DependencyInjection;

namespace BlazorChatApp.Server.Hubs
{
    public class RoomActor : ReceiveActor
    {
        private readonly ILoggingAdapter log = Context.GetLogger();

        public Dictionary<string, UpdateUserPos> users = new Dictionary<string, UpdateUserPos>();

        private string roomName;

        private int userAutoNo = 0;

        private readonly IServiceScopeFactory scopeFactory;

        private readonly ConcurrentQueue<ChatMessage> chatHistory = new ConcurrentQueue<ChatMessage>();

        private const int MaxChatHistoryCount = 50;

        private readonly UserInfo botUserInfo;
        private readonly UpdateUserPos botUpdateUserPos;

        Random random = new Random();

        public RoomActor(string _roomName, IServiceScopeFactory _scopeFactory)
        {
            scopeFactory = _scopeFactory;

            roomName = _roomName;

            log.Info($"Create Room{roomName}");

            // Initialize bot user
            botUserInfo = new UserInfo()
            {
                Id = "bot",
                Name = "ChatBot",
                Color = "#FF5733"
            };

            botUpdateUserPos = new UpdateUserPos()
            {
                Id = botUserInfo.Id,
                Name = botUserInfo.Name,
                PosX = 0,
                PosY = 0,
                AbsPosX = 0,
                AbsPosY = 0,
                ConnectionId = "bot-connection"
            };

            users[botUserInfo.Id] = botUpdateUserPos;

            Receive<RoomCmd>(cmd => {
                log.Info("Received String message: {0}", cmd);
                //Sender.Tell(message);                
            });

            Receive<JoinRoom>(async cmd => {
                userAutoNo++;
                string jsonString = JsonSerializer.Serialize(cmd);
                log.Info("Received JoinRoom message: {0}", jsonString);
                string RandomColor = string.Format("#{0:X6}", random.Next(0xFFFFFF));

                UserInfo userInfo = new UserInfo()
                {
                    Id = cmd.UserInfo.Id,
                    Name = $"User-{userAutoNo}",
                    Color = RandomColor
                };

                // Default Position
                double posx = random.Next(0, 300);
                double posy = random.Next(400, 550);

                UpdateUserPos updateUserPos = new UpdateUserPos()
                {
                    Id = cmd.UserInfo.Id,
                    Name = $"User-{userAutoNo}",
                    PosX = posx,
                    PosY = posy,
                    AbsPosX = posx,
                    AbsPosY = posy,
                    ConnectionId = cmd.ConnectionId
                };

                if (!users.ContainsKey(updateUserPos.Id))
                    users[cmd.UserInfo.Id] = updateUserPos;

                await OnJoinRoom(cmd.RoomInfo, userInfo, updateUserPos);
            });

            Receive<SyncRoom>(async cmd => {
                string jsonString = JsonSerializer.Serialize(cmd);
                log.Info("Received SyncRoom message: {0}", jsonString);

                List<UpdateUserPos> updateUserPosList = users.Values.ToList();
                string RandomColor = string.Format("#{0:X6}", random.Next(0xFFFFFF));

                UserInfo userInfo = new UserInfo()
                {
                    Id = cmd.UserInfo.Id,
                    Name = $"User-{userAutoNo}",
                    Color = RandomColor
                };

                await OnSyncRoom(userInfo, updateUserPosList);
            });

            Receive<ChatMessage>(async cmd => {
                string jsonString = JsonSerializer.Serialize(cmd);
                log.Info("Received ChatMessage message: {0}", jsonString);

                ChatMessage chatMessage = new ChatMessage()
                {
                    From = cmd.From,
                    Message = cmd.Message
                };

                AddChatMessageToHistory(chatMessage);

                await OnChatMessage(chatMessage);

                // Bot response
                if (cmd.Message.Contains("bot", StringComparison.OrdinalIgnoreCase))
                {
                    var botResponse = new ChatMessage()
                    {
                        From = botUserInfo,
                        Message = "Hello! How can I assist you today?"
                    };

                    AddChatMessageToHistory(botResponse);
                    await OnChatMessage(botResponse);
                }
            });

            Receive<UpdateUserPos>(async cmd => {
                string jsonString = JsonSerializer.Serialize(cmd);
                log.Info("Received UpdateUserPos message: {0}", jsonString);

                double AbsPosX = users[cmd.Id].PosX + cmd.PosX;
                double AbsPosY = users[cmd.Id].PosY + cmd.PosY;

                if (users.ContainsKey(cmd.Id))
                {
                    users[cmd.Id].PosX = AbsPosX;
                    users[cmd.Id].PosY = AbsPosY;
                    users[cmd.Id].AbsPosX = AbsPosX;
                    users[cmd.Id].AbsPosY = AbsPosY;
                }

                log.Info($"UpdateUser : X=>{users[cmd.Id].AbsPosX} Y=>{users[cmd.Id].AbsPosY}");

                UpdateUserPos updateUserPos = new UpdateUserPos()
                {
                    Id = cmd.Id,
                    Name = cmd.Name,
                    PosX = cmd.PosX,
                    PosY = cmd.PosY,
                    AbsPosX = AbsPosX,
                    AbsPosY = AbsPosY
                };

                await OnUpdateUserPos(updateUserPos);
            });

            Receive<Disconnect>(async cmd => {
                string jsonString = JsonSerializer.Serialize(cmd);
                log.Info("Received Disconnect message: {0}", jsonString);
                var disconnectUser = users.Values.Where(e => e.ConnectionId == cmd.ConnectionId).FirstOrDefault();

                if (disconnectUser != null)
                {
                    if (users.ContainsKey(disconnectUser.Id))
                    {
                        users.Remove(disconnectUser.Id);

                        var leaveMsg = new LeaveRoom()
                        {
                            UserInfo = new UserInfo() { Id = disconnectUser.Id }
                        };
                        await OnLeaveRoom(leaveMsg);
                    }
                }
            });

            Receive<LeaveRoom>(async cmd => {
                string jsonString = JsonSerializer.Serialize(cmd);
                log.Info("Received LeaveRoom message: {0}", jsonString);

                if (users.ContainsKey(cmd.UserInfo.Id))
                {
                    users.Remove(cmd.UserInfo.Id);
                    await OnLeaveRoom(cmd);
                }
            });
        }

        private void AddChatMessageToHistory(ChatMessage chatMessage)
        {
            chatHistory.Enqueue(chatMessage);
            while (chatHistory.Count > MaxChatHistoryCount)
            {
                chatHistory.TryDequeue(out _);
            }
        }

        private string GetChatHistoryAsJson()
        {
            return JsonSerializer.Serialize(chatHistory.ToList());
        }

        public async Task OnJoinRoom(RoomInfo roomInfo, UserInfo user, UpdateUserPos updateUserPos)
        {
            using (var scope = scopeFactory.CreateScope())
            {
                var wsHub = scope.ServiceProvider.GetRequiredService<IHubContext<ChatHub>>();
                await wsHub.Clients.All.SendAsync("OnJoinRoom", roomInfo, user, updateUserPos);
            }
        }

        public async Task OnSyncRoom(UserInfo user, List<UpdateUserPos> updateUserPos)
        {
            using (var scope = scopeFactory.CreateScope())
            {
                var wsHub = scope.ServiceProvider.GetRequiredService<IHubContext<ChatHub>>();
                await wsHub.Clients.All.SendAsync("OnSyncRoom", user, updateUserPos);
            }
        }

        public async Task OnLeaveRoom(LeaveRoom leaveRoom)
        {
            using (var scope = scopeFactory.CreateScope())
            {
                var wsHub = scope.ServiceProvider.GetRequiredService<IHubContext<ChatHub>>();
                await wsHub.Clients.All.SendAsync("OnLeaveRoom", leaveRoom);
            }
        }

        public async Task OnUpdateUserPos(UpdateUserPos updatePos)
        {
            using (var scope = scopeFactory.CreateScope())
            {
                var wsHub = scope.ServiceProvider.GetRequiredService<IHubContext<ChatHub>>();
                await wsHub.Clients.All.SendAsync("OnUpdateUserPos", updatePos);
            }
        }

        public async Task OnChatMessage(ChatMessage chatMessage)
        {
            using (var scope = scopeFactory.CreateScope())
            {
                var wsHub = scope.ServiceProvider.GetRequiredService<IHubContext<ChatHub>>();
                await wsHub.Clients.All.SendAsync("OnChatMessage", chatMessage);
            }
        }
    }
}




RoomActor 에 타이머기능을 넣어죠 10초에한번씩 반복작동 이벤트를 발생 액터타이머를 이용

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;

using Akka.Actor;
using Akka.Event;

using BlazorChatApp.Shared;

using Microsoft.AspNetCore.SignalR;
using Microsoft.AspNetCore.SignalR.Client;
using Microsoft.Extensions.DependencyInjection;

namespace BlazorChatApp.Server.Hubs
{
    public class RoomActor : ReceiveActor
    {
        private readonly ILoggingAdapter log = Context.GetLogger();

        public Dictionary<string, UpdateUserPos> users = new Dictionary<string, UpdateUserPos>();

        private string roomName;

        private int userAutoNo = 0;

        private readonly IServiceScopeFactory scopeFactory;

        private readonly ConcurrentQueue<ChatMessage> chatHistory = new ConcurrentQueue<ChatMessage>();

        private const int MaxChatHistoryCount = 50;

        private readonly UserInfo botUserInfo;
        private readonly UpdateUserPos botUpdateUserPos;

        Random random = new Random();

        public RoomActor(string _roomName, IServiceScopeFactory _scopeFactory)
        {
            scopeFactory = _scopeFactory;

            roomName = _roomName;

            log.Info($"Create Room{roomName}");

            string RandomColor = string.Format("#{0:X6}", random.Next(0xFFFFFF));

            // Initialize bot user
            botUserInfo = new UserInfo()
            {
                Id = "bot-1",
                Name = "ChatBot-1",
                Color = RandomColor
            };

            botUpdateUserPos = new UpdateUserPos()
            {
                Id = botUserInfo.Id,
                Name = botUserInfo.Name,
                PosX = 250,
                PosY = 250,
                AbsPosX = 250,
                AbsPosY = 250,
                ConnectionId = "bot-connection"
            };

            users[botUserInfo.Id] = botUpdateUserPos;

            // Schedule a timer to send a Tick message every 10 seconds
            Context.System.Scheduler.ScheduleTellRepeatedly(
                TimeSpan.FromSeconds(10),
                TimeSpan.FromSeconds(10),
                Self,
                new Tick(),
                Self
            );

            Receive<RoomCmd>(cmd => {
                log.Info("Received String message: {0}", cmd);
                //Sender.Tell(message);                
            });

            Receive<JoinRoom>(async cmd => {
                userAutoNo++;
                string jsonString = JsonSerializer.Serialize(cmd);
                log.Info("Received JoinRoom message: {0}", jsonString);
                string RandomColor = string.Format("#{0:X6}", random.Next(0xFFFFFF));

                UserInfo userInfo = new UserInfo()
                {
                    Id = cmd.UserInfo.Id,
                    Name = $"User-{userAutoNo}",
                    Color = RandomColor
                };

                // Default Position
                double posx = random.Next(0, 300);
                double posy = random.Next(400, 550);

                UpdateUserPos updateUserPos = new UpdateUserPos()
                {
                    Id = cmd.UserInfo.Id,
                    Name = $"User-{userAutoNo}",
                    PosX = posx,
                    PosY = posy,
                    AbsPosX = posx,
                    AbsPosY = posy,
                    ConnectionId = cmd.ConnectionId
                };

                if (!users.ContainsKey(updateUserPos.Id))
                    users[cmd.UserInfo.Id] = updateUserPos;

                await OnJoinRoom(cmd.RoomInfo, userInfo, updateUserPos);
            });

            Receive<SyncRoom>(async cmd => {
                string jsonString = JsonSerializer.Serialize(cmd);
                log.Info("Received SyncRoom message: {0}", jsonString);

                List<UpdateUserPos> updateUserPosList = users.Values.ToList();
                string RandomColor = string.Format("#{0:X6}", random.Next(0xFFFFFF));

                UserInfo userInfo = new UserInfo()
                {
                    Id = cmd.UserInfo.Id,
                    Name = $"User-{userAutoNo}",
                    Color = RandomColor
                };

                await OnSyncRoom(botUserInfo, updateUserPosList);

                await OnSyncRoom(userInfo, updateUserPosList);
            });

            Receive<ChatMessage>(async cmd => {
                string jsonString = JsonSerializer.Serialize(cmd);
                log.Info("Received ChatMessage message: {0}", jsonString);

                ChatMessage chatMessage = new ChatMessage()
                {
                    From = cmd.From,
                    Message = cmd.Message
                };

                AddChatMessageToHistory(chatMessage);

                await OnChatMessage(chatMessage);
            });

            Receive<UpdateUserPos>(async cmd => {
                string jsonString = JsonSerializer.Serialize(cmd);
                log.Info("Received UpdateUserPos message: {0}", jsonString);

                double AbsPosX = users[cmd.Id].PosX + cmd.PosX;
                double AbsPosY = users[cmd.Id].PosY + cmd.PosY;

                if (users.ContainsKey(cmd.Id))
                {
                    users[cmd.Id].PosX = AbsPosX;
                    users[cmd.Id].PosY = AbsPosY;
                    users[cmd.Id].AbsPosX = AbsPosX;
                    users[cmd.Id].AbsPosY = AbsPosY;
                }

                log.Info($"UpdateUser : X=>{users[cmd.Id].AbsPosX} Y=>{users[cmd.Id].AbsPosY}");

                UpdateUserPos updateUserPos = new UpdateUserPos()
                {
                    Id = cmd.Id,
                    Name = cmd.Name,
                    PosX = cmd.PosX,
                    PosY = cmd.PosY,
                    AbsPosX = AbsPosX,
                    AbsPosY = AbsPosY
                };

                await OnUpdateUserPos(updateUserPos);
            });

            Receive<Disconnect>(async cmd => {
                string jsonString = JsonSerializer.Serialize(cmd);
                log.Info("Received Disconnect message: {0}", jsonString);
                var disconnectUser = users.Values.Where(e => e.ConnectionId == cmd.ConnectionId).FirstOrDefault();

                if (disconnectUser != null)
                {
                    if (users.ContainsKey(disconnectUser.Id))
                    {
                        users.Remove(disconnectUser.Id);

                        var leaveMsg = new LeaveRoom()
                        {
                            UserInfo = new UserInfo() { Id = disconnectUser.Id }
                        };
                        await OnLeaveRoom(leaveMsg);
                    }
                }
            });

            Receive<LeaveRoom>(async cmd => {
                string jsonString = JsonSerializer.Serialize(cmd);
                log.Info("Received LeaveRoom message: {0}", jsonString);

                if (users.ContainsKey(cmd.UserInfo.Id))
                {
                    users.Remove(cmd.UserInfo.Id);
                    await OnLeaveRoom(cmd);
                }
            });

            // Handle Tick message
            Receive<Tick>(_ => {
                log.Info("Tick event triggered");
                // Add your custom logic here to be executed every 10 seconds
            });
        }

        private void AddChatMessageToHistory(ChatMessage chatMessage)
        {
            chatHistory.Enqueue(chatMessage);
            while (chatHistory.Count > MaxChatHistoryCount)
            {
                chatHistory.TryDequeue(out _);
            }
        }

        private string GetChatHistoryAsJson()
        {
            return JsonSerializer.Serialize(chatHistory.ToList());
        }

        public async Task OnJoinRoom(RoomInfo roomInfo, UserInfo user, UpdateUserPos updateUserPos)
        {
            using (var scope = scopeFactory.CreateScope())
            {
                var wsHub = scope.ServiceProvider.GetRequiredService<IHubContext<ChatHub>>();
                await wsHub.Clients.All.SendAsync("OnJoinRoom", roomInfo, user, updateUserPos);
            }
        }

        public async Task OnSyncRoom(UserInfo user, List<UpdateUserPos> updateUserPos)
        {
            using (var scope = scopeFactory.CreateScope())
            {
                var wsHub = scope.ServiceProvider.GetRequiredService<IHubContext<ChatHub>>();
                await wsHub.Clients.All.SendAsync("OnSyncRoom", user, updateUserPos);
            }
        }

        public async Task OnLeaveRoom(LeaveRoom leaveRoom)
        {
            using (var scope = scopeFactory.CreateScope())
            {
                var wsHub = scope.ServiceProvider.GetRequiredService<IHubContext<ChatHub>>();
                await wsHub.Clients.All.SendAsync("OnLeaveRoom", leaveRoom);
            }
        }

        public async Task OnUpdateUserPos(UpdateUserPos updatePos)
        {
            using (var scope = scopeFactory.CreateScope())
            {
                var wsHub = scope.ServiceProvider.GetRequiredService<IHubContext<ChatHub>>();
                await wsHub.Clients.All.SendAsync("OnUpdateUserPos", updatePos);
            }
        }

        public async Task OnChatMessage(ChatMessage chatMessage)
        {
            using (var scope = scopeFactory.CreateScope())
            {
                var wsHub = scope.ServiceProvider.GetRequiredService<IHubContext<ChatHub>>();
                await wsHub.Clients.All.SendAsync("OnChatMessage", chatMessage);
            }
        }

        // Define Tick message class
        public class Tick { }
    }
}



OnUpdateUserPos에 botUpdateUserPos 를 이용해 Tick에서 특정반경내에서 알아서 움직이게 코드추가

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;

using Akka.Actor;
using Akka.Event;

using BlazorChatApp.Shared;

using Microsoft.AspNetCore.SignalR;
using Microsoft.AspNetCore.SignalR.Client;
using Microsoft.Extensions.DependencyInjection;

namespace BlazorChatApp.Server.Hubs
{
    // Define Tick message class
    public class Tick { }

    public class RoomActor : ReceiveActor
    {
        private readonly ILoggingAdapter log = Context.GetLogger();

        public Dictionary<string, UpdateUserPos> users = new Dictionary<string, UpdateUserPos>();

        private string roomName;

        private int userAutoNo = 0;

        private readonly IServiceScopeFactory scopeFactory;

        private readonly ConcurrentQueue<ChatMessage> chatHistory = new ConcurrentQueue<ChatMessage>();

        private const int MaxChatHistoryCount = 50;

        private readonly UserInfo botUserInfo;
        private readonly UpdateUserPos botUpdateUserPos;

        Random random = new Random();

        public RoomActor(string _roomName, IServiceScopeFactory _scopeFactory)
        {
            scopeFactory = _scopeFactory;

            roomName = _roomName;

            log.Info($"Create Room{roomName}");

            string RandomColor = string.Format("#{0:X6}", random.Next(0xFFFFFF));

            // Schedule a timer to send a Tick message every 10 seconds
            Context.System.Scheduler.ScheduleTellRepeatedly(
                TimeSpan.FromSeconds(10),
                TimeSpan.FromSeconds(10),
                Self,
                new Tick(),
                Self
            );

            // Initialize bot user
            botUserInfo = new UserInfo()
            {
                Id = "bot-1",
                Name = "ChatBot-1",
                Color = RandomColor
            };

            botUpdateUserPos = new UpdateUserPos()
            {
                Id = botUserInfo.Id,
                Name = botUserInfo.Name,
                PosX = 250,
                PosY = 250,
                AbsPosX = 250,
                AbsPosY = 250,
                ConnectionId = "bot-connection"
            };

            users[botUserInfo.Id] = botUpdateUserPos;

            Receive<RoomCmd>(cmd => {
                log.Info("Received String message: {0}", cmd);
                //Sender.Tell(message);                
            });

            Receive<JoinRoom>(async cmd => {
                userAutoNo++;
                string jsonString = JsonSerializer.Serialize(cmd);
                log.Info("Received JoinRoom message: {0}", jsonString);
                string RandomColor = string.Format("#{0:X6}", random.Next(0xFFFFFF));

                UserInfo userInfo = new UserInfo()
                {
                    Id = cmd.UserInfo.Id,
                    Name = $"User-{userAutoNo}",
                    Color = RandomColor
                };

                // Default Position
                double posx = random.Next(0, 300);
                double posy = random.Next(400, 550);

                UpdateUserPos updateUserPos = new UpdateUserPos()
                {
                    Id = cmd.UserInfo.Id,
                    Name = $"User-{userAutoNo}",
                    PosX = posx,
                    PosY = posy,
                    AbsPosX = posx,
                    AbsPosY = posy,
                    ConnectionId = cmd.ConnectionId
                };

                if (!users.ContainsKey(updateUserPos.Id))
                    users[cmd.UserInfo.Id] = updateUserPos;

                await OnJoinRoom(cmd.RoomInfo, userInfo, updateUserPos);
            });

            Receive<SyncRoom>(async cmd => {
                string jsonString = JsonSerializer.Serialize(cmd);
                log.Info("Received SyncRoom message: {0}", jsonString);

                List<UpdateUserPos> updateUserPosList = users.Values.ToList();
                string RandomColor = string.Format("#{0:X6}", random.Next(0xFFFFFF));

                UserInfo userInfo = new UserInfo()
                {
                    Id = cmd.UserInfo.Id,
                    Name = $"User-{userAutoNo}",
                    Color = RandomColor
                };

                await OnSyncRoom(botUserInfo, updateUserPosList);

                await OnSyncRoom(userInfo, updateUserPosList);
            });

            Receive<ChatMessage>(async cmd => {
                string jsonString = JsonSerializer.Serialize(cmd);
                log.Info("Received ChatMessage message: {0}", jsonString);

                ChatMessage chatMessage = new ChatMessage()
                {
                    From = cmd.From,
                    Message = cmd.Message
                };

                AddChatMessageToHistory(chatMessage);

                await OnChatMessage(chatMessage);
            });

            Receive<UpdateUserPos>(async cmd => {
                string jsonString = JsonSerializer.Serialize(cmd);
                log.Info("Received UpdateUserPos message: {0}", jsonString);

                double AbsPosX = users[cmd.Id].PosX + cmd.PosX;
                double AbsPosY = users[cmd.Id].PosY + cmd.PosY;

                if (users.ContainsKey(cmd.Id))
                {
                    users[cmd.Id].PosX = AbsPosX;
                    users[cmd.Id].PosY = AbsPosY;
                    users[cmd.Id].AbsPosX = AbsPosX;
                    users[cmd.Id].AbsPosY = AbsPosY;
                }

                log.Info($"UpdateUser : X=>{users[cmd.Id].AbsPosX} Y=>{users[cmd.Id].AbsPosY}");

                UpdateUserPos updateUserPos = new UpdateUserPos()
                {
                    Id = cmd.Id,
                    Name = cmd.Name,
                    PosX = cmd.PosX,
                    PosY = cmd.PosY,
                    AbsPosX = AbsPosX,
                    AbsPosY = AbsPosY
                };

                await OnUpdateUserPos(updateUserPos);
            });

            Receive<Disconnect>(async cmd => {
                string jsonString = JsonSerializer.Serialize(cmd);
                log.Info("Received Disconnect message: {0}", jsonString);
                var disconnectUser = users.Values.Where(e => e.ConnectionId == cmd.ConnectionId).FirstOrDefault();

                if (disconnectUser != null)
                {
                    if (users.ContainsKey(disconnectUser.Id))
                    {
                        users.Remove(disconnectUser.Id);

                        var leaveMsg = new LeaveRoom()
                        {
                            UserInfo = new UserInfo() { Id = disconnectUser.Id }
                        };
                        await OnLeaveRoom(leaveMsg);
                    }
                }
            });

            Receive<LeaveRoom>(async cmd => {
                string jsonString = JsonSerializer.Serialize(cmd);
                log.Info("Received LeaveRoom message: {0}", jsonString);

                if (users.ContainsKey(cmd.UserInfo.Id))
                {
                    users.Remove(cmd.UserInfo.Id);
                    await OnLeaveRoom(cmd);
                }
            });

            // Handle Tick message
            Receive<Tick>(_ => {
                log.Info("Tick event triggered");

                // Move botUpdateUserPos within a specific range
                double moveX = random.NextDouble() * 20 - 10; // Move between -10 and 10
                double moveY = random.NextDouble() * 20 - 10; // Move between -10 and 10

                botUpdateUserPos.PosX += moveX;
                botUpdateUserPos.PosY += moveY;
                botUpdateUserPos.AbsPosX += moveX;
                botUpdateUserPos.AbsPosY += moveY;

                // Ensure bot stays within a specific range (e.g., 200 to 300 for both X and Y)
                botUpdateUserPos.PosX = Math.Clamp(botUpdateUserPos.PosX, 200, 300);
                botUpdateUserPos.PosY = Math.Clamp(botUpdateUserPos.PosY, 200, 300);
                botUpdateUserPos.AbsPosX = Math.Clamp(botUpdateUserPos.AbsPosX, 200, 300);
                botUpdateUserPos.AbsPosY = Math.Clamp(botUpdateUserPos.AbsPosY, 200, 300);

                // Update bot position
                Self.Tell(botUpdateUserPos);
            });
        }

        private void AddChatMessageToHistory(ChatMessage chatMessage)
        {
            chatHistory.Enqueue(chatMessage);
            while (chatHistory.Count > MaxChatHistoryCount)
            {
                chatHistory.TryDequeue(out _);
            }
        }

        private string GetChatHistoryAsJson()
        {
            return JsonSerializer.Serialize(chatHistory.ToList());
        }

        public async Task OnJoinRoom(RoomInfo roomInfo, UserInfo user, UpdateUserPos updateUserPos)
        {
            using (var scope = scopeFactory.CreateScope())
            {
                var wsHub = scope.ServiceProvider.GetRequiredService<IHubContext<ChatHub>>();
                await wsHub.Clients.All.SendAsync("OnJoinRoom", roomInfo, user, updateUserPos);
            }
        }

        public async Task OnSyncRoom(UserInfo user, List<UpdateUserPos> updateUserPos)
        {
            using (var scope = scopeFactory.CreateScope())
            {
                var wsHub = scope.ServiceProvider.GetRequiredService<IHubContext<ChatHub>>();
                await wsHub.Clients.All.SendAsync("OnSyncRoom", user, updateUserPos);
            }
        }

        public async Task OnLeaveRoom(LeaveRoom leaveRoom)
        {
            using (var scope = scopeFactory.CreateScope())
            {
                var wsHub = scope.ServiceProvider.GetRequiredService<IHubContext<ChatHub>>();
                await wsHub.Clients.All.SendAsync("OnLeaveRoom", leaveRoom);
            }
        }

        public async Task OnUpdateUserPos(UpdateUserPos updatePos)
        {
            using (var scope = scopeFactory.CreateScope())
            {
                var wsHub = scope.ServiceProvider.GetRequiredService<IHubContext<ChatHub>>();
                await wsHub.Clients.All.SendAsync("OnUpdateUserPos", updatePos);
            }
        }

        public async Task OnChatMessage(ChatMessage chatMessage)
        {
            using (var scope = scopeFactory.CreateScope())
            {
                var wsHub = scope.ServiceProvider.GetRequiredService<IHubContext<ChatHub>>();
                await wsHub.Clients.All.SendAsync("OnChatMessage", chatMessage);
            }
        }
    }
}



화면밖에 안나가게 ~ 반대로 움직일수있게 조절 한번에 움직이는 것은 5로 제약

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;

using Akka.Actor;
using Akka.Event;

using BlazorChatApp.Shared;

using Microsoft.AspNetCore.SignalR;
using Microsoft.AspNetCore.SignalR.Client;
using Microsoft.Extensions.DependencyInjection;

namespace BlazorChatApp.Server.Hubs
{
    // Define Tick message class
    public class Tick { }

    public class RoomActor : ReceiveActor
    {
        private readonly ILoggingAdapter log = Context.GetLogger();

        public Dictionary<string, UpdateUserPos> users = new Dictionary<string, UpdateUserPos>();

        private string roomName;

        private int userAutoNo = 0;

        private readonly IServiceScopeFactory scopeFactory;

        private readonly ConcurrentQueue<ChatMessage> chatHistory = new ConcurrentQueue<ChatMessage>();

        private const int MaxChatHistoryCount = 50;

        private readonly UserInfo botUserInfo;
        private readonly UpdateUserPos botUpdateUserPos;

        Random random = new Random();

        public RoomActor(string _roomName, IServiceScopeFactory _scopeFactory)
        {
            scopeFactory = _scopeFactory;

            roomName = _roomName;

            log.Info($"Create Room{roomName}");

            string RandomColor = string.Format("#{0:X6}", random.Next(0xFFFFFF));

            // Schedule a timer to send a Tick message every 10 seconds
            Context.System.Scheduler.ScheduleTellRepeatedly(
                TimeSpan.FromSeconds(10),
                TimeSpan.FromSeconds(3),
                Self,
                new Tick(),
                Self
            );

            // Initialize bot user
            botUserInfo = new UserInfo()
            {
                Id = "bot-1",
                Name = "ChatBot-1",
                Color = RandomColor
            };

            botUpdateUserPos = new UpdateUserPos()
            {
                Id = botUserInfo.Id,
                Name = botUserInfo.Name,
                PosX = 250,
                PosY = 250,
                AbsPosX = 250,
                AbsPosY = 250,
                ConnectionId = "bot-connection"
            };

            users[botUserInfo.Id] = botUpdateUserPos;

            Receive<RoomCmd>(cmd => {
                log.Info("Received String message: {0}", cmd);
                //Sender.Tell(message);                
            });

            Receive<JoinRoom>(async cmd => {
                userAutoNo++;
                string jsonString = JsonSerializer.Serialize(cmd);
                log.Info("Received JoinRoom message: {0}", jsonString);
                string RandomColor = string.Format("#{0:X6}", random.Next(0xFFFFFF));

                UserInfo userInfo = new UserInfo()
                {
                    Id = cmd.UserInfo.Id,
                    Name = $"User-{userAutoNo}",
                    Color = RandomColor
                };

                // Default Position
                double posx = random.Next(0, 300);
                double posy = random.Next(400, 550);

                UpdateUserPos updateUserPos = new UpdateUserPos()
                {
                    Id = cmd.UserInfo.Id,
                    Name = $"User-{userAutoNo}",
                    PosX = posx,
                    PosY = posy,
                    AbsPosX = posx,
                    AbsPosY = posy,
                    ConnectionId = cmd.ConnectionId
                };

                if (!users.ContainsKey(updateUserPos.Id))
                    users[cmd.UserInfo.Id] = updateUserPos;

                await OnJoinRoom(cmd.RoomInfo, userInfo, updateUserPos);
            });

            Receive<SyncRoom>(async cmd => {
                string jsonString = JsonSerializer.Serialize(cmd);
                log.Info("Received SyncRoom message: {0}", jsonString);

                List<UpdateUserPos> updateUserPosList = users.Values.ToList();
                string RandomColor = string.Format("#{0:X6}", random.Next(0xFFFFFF));

                UserInfo userInfo = new UserInfo()
                {
                    Id = cmd.UserInfo.Id,
                    Name = $"User-{userAutoNo}",
                    Color = RandomColor
                };

                await OnSyncRoom(botUserInfo, updateUserPosList);

                await OnSyncRoom(userInfo, updateUserPosList);
            });

            Receive<ChatMessage>(async cmd => {
                string jsonString = JsonSerializer.Serialize(cmd);
                log.Info("Received ChatMessage message: {0}", jsonString);

                ChatMessage chatMessage = new ChatMessage()
                {
                    From = cmd.From,
                    Message = cmd.Message
                };

                AddChatMessageToHistory(chatMessage);

                await OnChatMessage(chatMessage);
            });

            Receive<UpdateUserPos>(async cmd => {
                string jsonString = JsonSerializer.Serialize(cmd);
                log.Info("Received UpdateUserPos message: {0}", jsonString);

                double AbsPosX = users[cmd.Id].PosX + cmd.PosX;
                double AbsPosY = users[cmd.Id].PosY + cmd.PosY;

                if (users.ContainsKey(cmd.Id))
                {
                    users[cmd.Id].PosX = AbsPosX;
                    users[cmd.Id].PosY = AbsPosY;
                    users[cmd.Id].AbsPosX = AbsPosX;
                    users[cmd.Id].AbsPosY = AbsPosY;
                }

                log.Info($"UpdateUser : X=>{users[cmd.Id].AbsPosX} Y=>{users[cmd.Id].AbsPosY}");

                UpdateUserPos updateUserPos = new UpdateUserPos()
                {
                    Id = cmd.Id,
                    Name = cmd.Name,
                    PosX = cmd.PosX,
                    PosY = cmd.PosY,
                    AbsPosX = AbsPosX,
                    AbsPosY = AbsPosY
                };

                await OnUpdateUserPos(updateUserPos);
            });

            Receive<Disconnect>(async cmd => {
                string jsonString = JsonSerializer.Serialize(cmd);
                log.Info("Received Disconnect message: {0}", jsonString);
                var disconnectUser = users.Values.Where(e => e.ConnectionId == cmd.ConnectionId).FirstOrDefault();

                if (disconnectUser != null)
                {
                    if (users.ContainsKey(disconnectUser.Id))
                    {
                        users.Remove(disconnectUser.Id);

                        var leaveMsg = new LeaveRoom()
                        {
                            UserInfo = new UserInfo() { Id = disconnectUser.Id }
                        };
                        await OnLeaveRoom(leaveMsg);
                    }
                }
            });

            Receive<LeaveRoom>(async cmd => {
                string jsonString = JsonSerializer.Serialize(cmd);
                log.Info("Received LeaveRoom message: {0}", jsonString);

                if (users.ContainsKey(cmd.UserInfo.Id))
                {
                    users.Remove(cmd.UserInfo.Id);
                    await OnLeaveRoom(cmd);
                }
            });

            // Handle Tick message
            Receive<Tick>(_ => {
                log.Info("Tick event triggered");

                // Move botUpdateUserPos within a specific range
                double moveX = random.NextDouble() * 10 - 5; // Move between -5 and 5
                double moveY = random.NextDouble() * 10 - 5; // Move between -5 and 5

                botUpdateUserPos.PosX += moveX;
                botUpdateUserPos.PosY += moveY;
                botUpdateUserPos.AbsPosX += moveX;
                botUpdateUserPos.AbsPosY += moveY;

                // Ensure bot stays within a specific range (e.g., 200 to 300 for both X and Y)
                if (botUpdateUserPos.PosX < 200 || botUpdateUserPos.PosX > 300)
                {
                    botUpdateUserPos.PosX -= moveX; // Reverse the movement
                    botUpdateUserPos.AbsPosX -= moveX;
                }

                if (botUpdateUserPos.PosY < 200 || botUpdateUserPos.PosY > 300)
                {
                    botUpdateUserPos.PosY -= moveY; // Reverse the movement
                    botUpdateUserPos.AbsPosY -= moveY;
                }

                // Update bot position
                Self.Tell(botUpdateUserPos);
            });
        }

        private void AddChatMessageToHistory(ChatMessage chatMessage)
        {
            chatHistory.Enqueue(chatMessage);
            while (chatHistory.Count > MaxChatHistoryCount)
            {
                chatHistory.TryDequeue(out _);
            }
        }

        private string GetChatHistoryAsJson()
        {
            return JsonSerializer.Serialize(chatHistory.ToList());
        }

        public async Task OnJoinRoom(RoomInfo roomInfo, UserInfo user, UpdateUserPos updateUserPos)
        {
            using (var scope = scopeFactory.CreateScope())
            {
                var wsHub = scope.ServiceProvider.GetRequiredService<IHubContext<ChatHub>>();
                await wsHub.Clients.All.SendAsync("OnJoinRoom", roomInfo, user, updateUserPos);
            }
        }

        public async Task OnSyncRoom(UserInfo user, List<UpdateUserPos> updateUserPos)
        {
            using (var scope = scopeFactory.CreateScope())
            {
                var wsHub = scope.ServiceProvider.GetRequiredService<IHubContext<ChatHub>>();
                await wsHub.Clients.All.SendAsync("OnSyncRoom", user, updateUserPos);
            }
        }

        public async Task OnLeaveRoom(LeaveRoom leaveRoom)
        {
            using (var scope = scopeFactory.CreateScope())
            {
                var wsHub = scope.ServiceProvider.GetRequiredService<IHubContext<ChatHub>>();
                await wsHub.Clients.All.SendAsync("OnLeaveRoom", leaveRoom);
            }
        }

        public async Task OnUpdateUserPos(UpdateUserPos updatePos)
        {
            using (var scope = scopeFactory.CreateScope())
            {
                var wsHub = scope.ServiceProvider.GetRequiredService<IHubContext<ChatHub>>();
                await wsHub.Clients.All.SendAsync("OnUpdateUserPos", updatePos);
            }
        }

        public async Task OnChatMessage(ChatMessage chatMessage)
        {
            using (var scope = scopeFactory.CreateScope())
            {
                var wsHub = scope.ServiceProvider.GetRequiredService<IHubContext<ChatHub>>();
                await wsHub.Clients.All.SendAsync("OnChatMessage", chatMessage);
            }
        }
    }
}






소원을 들어주는 메타공간 새해복 많이 받으세요 메시지를 포함 다양한 새해 인사를 랜덤하게 덕담으로

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;

using Akka.Actor;
using Akka.Event;

using BlazorChatApp.Shared;

using Microsoft.AspNetCore.SignalR;
using Microsoft.AspNetCore.SignalR.Client;
using Microsoft.Extensions.DependencyInjection;

namespace BlazorChatApp.Server.Hubs
{
    // Define Tick message class
    public class Tick { }

    public class RoomActor : ReceiveActor
    {
        private readonly ILoggingAdapter log = Context.GetLogger();

        public Dictionary<string, UpdateUserPos> users = new Dictionary<string, UpdateUserPos>();

        private string roomName;

        private int userAutoNo = 0;

        private readonly IServiceScopeFactory scopeFactory;

        private readonly ConcurrentQueue<ChatMessage> chatHistory = new ConcurrentQueue<ChatMessage>();

        private const int MaxChatHistoryCount = 50;

        private readonly UserInfo botUserInfo;
        private readonly UpdateUserPos botUpdateUserPos;

        Random random = new Random();

        public RoomActor(string _roomName, IServiceScopeFactory _scopeFactory)
        {
            scopeFactory = _scopeFactory;

            roomName = _roomName;

            log.Info($"Create Room{roomName}");

            string RandomColor = string.Format("#{0:X6}", random.Next(0xFFFFFF));

            // Schedule a timer to send a Tick message every 10 seconds
            Context.System.Scheduler.ScheduleTellRepeatedly(
                TimeSpan.FromSeconds(10),
                TimeSpan.FromSeconds(3),
                Self,
                new Tick(),
                Self
            );

            // Initialize bot user
            botUserInfo = new UserInfo()
            {
                Id = "bot-1",
                Name = "ChatBot-1",
                Color = RandomColor
            };

            botUpdateUserPos = new UpdateUserPos()
            {
                Id = botUserInfo.Id,
                Name = botUserInfo.Name,
                PosX = 250,
                PosY = 250,
                AbsPosX = 250,
                AbsPosY = 250,
                ConnectionId = "bot-connection"
            };

            users[botUserInfo.Id] = botUpdateUserPos;

            Receive<RoomCmd>(cmd => {
                log.Info("Received String message: {0}", cmd);
                //Sender.Tell(message);                
            });

            Receive<JoinRoom>(async cmd => {
                userAutoNo++;
                string jsonString = JsonSerializer.Serialize(cmd);
                log.Info("Received JoinRoom message: {0}", jsonString);
                string RandomColor = string.Format("#{0:X6}", random.Next(0xFFFFFF));

                UserInfo userInfo = new UserInfo()
                {
                    Id = cmd.UserInfo.Id,
                    Name = $"User-{userAutoNo}",
                    Color = RandomColor
                };

                // Default Position
                double posx = random.Next(0, 300);
                double posy = random.Next(400, 550);

                UpdateUserPos updateUserPos = new UpdateUserPos()
                {
                    Id = cmd.UserInfo.Id,
                    Name = $"User-{userAutoNo}",
                    PosX = posx,
                    PosY = posy,
                    AbsPosX = posx,
                    AbsPosY = posy,
                    ConnectionId = cmd.ConnectionId
                };

                if (!users.ContainsKey(updateUserPos.Id))
                    users[cmd.UserInfo.Id] = updateUserPos;

                await OnJoinRoom(cmd.RoomInfo, userInfo, updateUserPos);
            });

            Receive<SyncRoom>(async cmd => {
                string jsonString = JsonSerializer.Serialize(cmd);
                log.Info("Received SyncRoom message: {0}", jsonString);

                List<UpdateUserPos> updateUserPosList = users.Values.ToList();
                string RandomColor = string.Format("#{0:X6}", random.Next(0xFFFFFF));

                UserInfo userInfo = new UserInfo()
                {
                    Id = cmd.UserInfo.Id,
                    Name = $"User-{userAutoNo}",
                    Color = RandomColor
                };

                await OnSyncRoom(botUserInfo, updateUserPosList);

                await OnSyncRoom(userInfo, updateUserPosList);
            });

            Receive<ChatMessage>(async cmd => {
                string jsonString = JsonSerializer.Serialize(cmd);
                log.Info("Received ChatMessage message: {0}", jsonString);

                ChatMessage chatMessage = new ChatMessage()
                {
                    From = cmd.From,
                    Message = cmd.Message
                };

                AddChatMessageToHistory(chatMessage);

                await OnChatMessage(chatMessage);
            });

            Receive<UpdateUserPos>(async cmd => {
                string jsonString = JsonSerializer.Serialize(cmd);
                log.Info("Received UpdateUserPos message: {0}", jsonString);

                double AbsPosX = users[cmd.Id].PosX + cmd.PosX;
                double AbsPosY = users[cmd.Id].PosY + cmd.PosY;

                if (users.ContainsKey(cmd.Id))
                {
                    users[cmd.Id].PosX = AbsPosX;
                    users[cmd.Id].PosY = AbsPosY;
                    users[cmd.Id].AbsPosX = AbsPosX;
                    users[cmd.Id].AbsPosY = AbsPosY;
                }

                log.Info($"UpdateUser : X=>{users[cmd.Id].AbsPosX} Y=>{users[cmd.Id].AbsPosY}");

                UpdateUserPos updateUserPos = new UpdateUserPos()
                {
                    Id = cmd.Id,
                    Name = cmd.Name,
                    PosX = cmd.PosX,
                    PosY = cmd.PosY,
                    AbsPosX = AbsPosX,
                    AbsPosY = AbsPosY
                };

                await OnUpdateUserPos(updateUserPos);
            });

            Receive<Disconnect>(async cmd => {
                string jsonString = JsonSerializer.Serialize(cmd);
                log.Info("Received Disconnect message: {0}", jsonString);
                var disconnectUser = users.Values.Where(e => e.ConnectionId == cmd.ConnectionId).FirstOrDefault();

                if (disconnectUser != null)
                {
                    if (users.ContainsKey(disconnectUser.Id))
                    {
                        users.Remove(disconnectUser.Id);

                        var leaveMsg = new LeaveRoom()
                        {
                            UserInfo = new UserInfo() { Id = disconnectUser.Id }
                        };
                        await OnLeaveRoom(leaveMsg);
                    }
                }
            });

            Receive<LeaveRoom>(async cmd => {
                string jsonString = JsonSerializer.Serialize(cmd);
                log.Info("Received LeaveRoom message: {0}", jsonString);

                if (users.ContainsKey(cmd.UserInfo.Id))
                {
                    users.Remove(cmd.UserInfo.Id);
                    await OnLeaveRoom(cmd);
                }
            });

            // Handle Tick message
            Receive<Tick>(async _ => {
                log.Info("Tick event triggered");

                // Move botUpdateUserPos within a specific range
                double moveX = random.NextDouble() * 40 - 20; // Move between -20 and 20
                double moveY = 0;

                // 25% chance to move in Y direction
                if (random.Next(4) == 0)
                {
                    moveY = random.NextDouble() * 40 - 20; // Move between -20 and 20
                }

                double newPosX = botUpdateUserPos.PosX + moveX;
                double newPosY = botUpdateUserPos.PosY + moveY;

                // Ensure bot stays within the screen boundaries (e.g., 0 to 800 for X and 0 to 600 for Y)
                if (newPosX >= 0 && newPosX <= 800 && newPosY >= 0 && newPosY <= 600)
                {
                    botUpdateUserPos.PosX = newPosX;
                    botUpdateUserPos.PosY = newPosY;
                    botUpdateUserPos.AbsPosX += moveX;
                    botUpdateUserPos.AbsPosY += moveY;

                    // Update bot position
                    Self.Tell(botUpdateUserPos);
                }

                // Send random New Year greeting
                string[] greetings = new string[]
                {
                    "새해 복 많이 받으세요!",
                    "행복한 새해 되세요!",
                    "건강하고 행복한 한 해 되세요!",
                    "새해에는 모든 소원이 이루어지길 바랍니다!",
                    "희망찬 새해 되세요!"
                };

                string randomGreeting = greetings[random.Next(greetings.Length)];

                ChatMessage greetingMessage = new ChatMessage()
                {
                    From = botUserInfo.Name,
                    Message = randomGreeting
                };

                AddChatMessageToHistory(greetingMessage);

                await OnChatMessage(greetingMessage);
            });
        }

        private void AddChatMessageToHistory(ChatMessage chatMessage)
        {
            chatHistory.Enqueue(chatMessage);
            while (chatHistory.Count > MaxChatHistoryCount)
            {
                chatHistory.TryDequeue(out _);
            }
        }

        private string GetChatHistoryAsJson()
        {
            return JsonSerializer.Serialize(chatHistory.ToList());
        }

        public async Task OnJoinRoom(RoomInfo roomInfo, UserInfo user, UpdateUserPos updateUserPos)
        {
            using (var scope = scopeFactory.CreateScope())
            {
                var wsHub = scope.ServiceProvider.GetRequiredService<IHubContext<ChatHub>>();
                await wsHub.Clients.All.SendAsync("OnJoinRoom", roomInfo, user, updateUserPos);
            }
        }

        public async Task OnSyncRoom(UserInfo user, List<UpdateUserPos> updateUserPos)
        {
            using (var scope = scopeFactory.CreateScope())
            {
                var wsHub = scope.ServiceProvider.GetRequiredService<IHubContext<ChatHub>>();
                await wsHub.Clients.All.SendAsync("OnSyncRoom", user, updateUserPos);
            }
        }

        public async Task OnLeaveRoom(LeaveRoom leaveRoom)
        {
            using (var scope = scopeFactory.CreateScope())
            {
                var wsHub = scope.ServiceProvider.GetRequiredService<IHubContext<ChatHub>>();
                await wsHub.Clients.All.SendAsync("OnLeaveRoom", leaveRoom);
            }
        }

        public async Task OnUpdateUserPos(UpdateUserPos updatePos)
        {
            using (var scope = scopeFactory.CreateScope())
            {
                var wsHub = scope.ServiceProvider.GetRequiredService<IHubContext<ChatHub>>();
                await wsHub.Clients.All.SendAsync("OnUpdateUserPos", updatePos);
            }
        }

        public async Task OnChatMessage(ChatMessage chatMessage)
        {
            using (var scope = scopeFactory.CreateScope())
            {
                var wsHub = scope.ServiceProvider.GetRequiredService<IHubContext<ChatHub>>();
                await wsHub.Clients.All.SendAsync("OnChatMessage", chatMessage);
            }
        }
    }
}






명언중에서 새해 힘찬 목표를 가질수 있는 단어로 다시구성

string[] quotes = new string[]
{
    "새해에는 목표를 향해 힘차게 나아가세요!",
    "새해에는 더 큰 도전을 향해 나아가세요!",
    "새해에는 꿈을 이루기 위해 최선을 다하세요!",
    "새해에는 새로운 목표를 세우고 달성하세요!",
    "새해에는 더 높은 곳을 향해 도전하세요!",
    "새해에는 자신을 믿고 목표를 이루세요!",
    "새해에는 더 큰 성취를 이루세요!",
    "새해에는 끊임없이 도전하세요!",
    "새해에는 목표를 향해 한 걸음 더 나아가세요!",
    "새해에는 더 큰 꿈을 꾸고 이루세요!",
    "새해에는 새로운 시작을 맞이하세요!",
    "새해에는 더 큰 목표를 세우고 달성하세요!",
    "새해에는 더 많은 성취를 이루세요!",
    "새해에는 더 큰 도전을 향해 나아가세요!",
    "새해에는 목표를 향해 힘차게 나아가세요!",
    "새해에는 꿈을 이루기 위해 최선을 다하세요!",
    "새해에는 새로운 목표를 세우고 달성하세요!",
    "새해에는 더 높은 곳을 향해 도전하세요!",
    "새해에는 자신을 믿고 목표를 이루세요!",
    "새해에는 더 큰 성취를 이루세요!",
    "새해에는 끊임없이 도전하세요!",
    "새해에는 목표를 향해 한 걸음 더 나아가세요!",
    "새해에는 더 큰 꿈을 꾸고 이루세요!",
    "새해에는 새로운 시작을 맞이하세요!",
    "새해에는 더 큰 목표를 세우고 달성하세요!",
    "새해에는 더 많은 성취를 이루세요!",
    "새해에는 더 큰 도전을 향해 나아가세요!",
    "새해에는 목표를 향해 힘차게 나아가세요!",
    "새해에는 꿈을 이루기 위해 최선을 다하세요!",
    "새해에는 새로운 목표를 세우고 달성하세요!",
    "새해에는 더 높은 곳을 향해 도전하세요!",
    "새해에는 자신을 믿고 목표를 이루세요!",
    "새해에는 더 큰 성취를 이루세요!",
    "새해에는 끊임없이 도전하세요!",
    "새해에는 목표를 향해 한 걸음 더 나아가세요!",
    "새해에는 더 큰 꿈을 꾸고 이루세요!",
    "새해에는 새로운 시작을 맞이하세요!",
    "새해에는 더 큰 목표를 세우고 달성하세요!",
    "새해에는 더 많은 성취를 이루세요!",
    "새해에는 더 큰 도전을 향해 나아가세요!",
    "새해에는 목표를 향해 힘차게 나아가세요!",
    "새해에는 꿈을 이루기 위해 최선을 다하세요!",
    "새해에는 새로운 목표를 세우고 달성하세요!",
    "새해에는 더 높은 곳을 향해 도전하세요!",
    "새해에는 자신을 믿고 목표를 이루세요!",
    "새해에는 더 큰 성취를 이루세요!",
    "새해에는 끊임없이 도전하세요!",
    "새해에는 목표를 향해 한 걸음 더 나아가세요!",
    "새해에는 더 큰 꿈을 꾸고 이루세요!",
    "새해에는 새로운 시작을 맞이하세요!"
};





  DrawImageAsync img-chatbox 에서 , Chatbox를 그리니 이미지가 뭉개집니다. 이미지가아닌 도형을 말풍선처럼 그려 글자 크기에 맞게 표현하는것로 개선

public async ValueTask Render(SceneContext game, Canvas2DContext context)
{
    string NameText = Name;

    if (!string.IsNullOrEmpty(ChatMessage))
    {
        NameText = NameText + "-" + ChatMessage;
        ChatViewTime--;
    }

    if (ChatViewTime < 0)
    {
        ChatMessage = string.Empty;
    }

    await context.SaveAsync();

    // 닉네임
    await context.SetFontAsync("14px 바탕체");
    await context.SetFillStyleAsync("Blue");
    await context.FillTextAsync(Name, _transform.Local.Position.X + 10, _transform.Local.Position.Y + 75);

    if (!string.IsNullOrEmpty(ChatMessage))
    {
        // 채팅 Box
        int padding = 10;
        int dynamicWidth = 50 + ((ChatMessage.Length - 3) * 15);
        int dynamicHeight = 50;

        float boxX = _transform.Local.Position.X + 20;
        float boxY = _transform.Local.Position.Y - 40;

        // 말풍선 배경
        await context.SetFillStyleAsync("White");
        await context.FillRectAsync(boxX, boxY, dynamicWidth, dynamicHeight);

        // 말풍선 테두리
        await context.SetStrokeStyleAsync("Black");
        await context.StrokeRectAsync(boxX, boxY, dynamicWidth, dynamicHeight);

        // 채팅 메시지
        await context.SetFillStyleAsync("Black");
        await context.FillTextAsync(ChatMessage, boxX + padding, boxY + padding + 20);
    }

    if (_isMine)
    {
        // 추가적인 렌더링 로직이 필요하다면 여기에 작성
    }

    await context.RestoreAsync();
}




사각형이 아니라 말풍선처럼 타워에 꼭지점도 표현

public async ValueTask Render(SceneContext game, Canvas2DContext context)
{
    string NameText = Name;

    if (!string.IsNullOrEmpty(ChatMessage))
    {
        NameText = NameText + "-" + ChatMessage;
        ChatViewTime--;
    }

    if (ChatViewTime < 0)
    {
        ChatMessage = string.Empty;
    }

    await context.SaveAsync();

    // 닉네임
    await context.SetFontAsync("14px 바탕체");
    await context.SetFillStyleAsync("Blue");
    await context.FillTextAsync(Name, _transform.Local.Position.X + 10, _transform.Local.Position.Y + 75);

    if (!string.IsNullOrEmpty(ChatMessage))
    {
        // 채팅 Box
        int padding = 10;
        int dynamicWidth = 50 + ((ChatMessage.Length - 3) * 15);
        int dynamicHeight = 50;

        float boxX = _transform.Local.Position.X + 20;
        float boxY = _transform.Local.Position.Y - 40;
        float tailWidth = 10;
        float tailHeight = 10;

        // 말풍선 배경
        await context.BeginPathAsync();
        await context.MoveToAsync(boxX, boxY);
        await context.LineToAsync(boxX + dynamicWidth, boxY);
        await context.LineToAsync(boxX + dynamicWidth, boxY + dynamicHeight);
        await context.LineToAsync(boxX + dynamicWidth / 2 + tailWidth, boxY + dynamicHeight);
        await context.LineToAsync(boxX + dynamicWidth / 2, boxY + dynamicHeight + tailHeight);
        await context.LineToAsync(boxX + dynamicWidth / 2 - tailWidth, boxY + dynamicHeight);
        await context.LineToAsync(boxX, boxY + dynamicHeight);
        await context.ClosePathAsync();
        await context.SetFillStyleAsync("White");
        await context.FillAsync();

        // 말풍선 테두리
        await context.SetStrokeStyleAsync("Black");
        await context.StrokeAsync();

        // 채팅 메시지
        await context.SetFillStyleAsync("Black");
        await context.FillTextAsync(ChatMessage, boxX + padding, boxY + padding + 20);
    }

    if (_isMine)
    {
        // 추가적인 렌더링 로직이 필요하다면 여기에 작성
    }

    await context.RestoreAsync();
}