using System; using System.Collections.Generic; using Unity.Netcode; using UnityEngine; using UnityEngine.UI; namespace LobbyRelaySample.ngo { /// /// Handles selecting the randomized sequence of symbols to spawn. This also selects a subset of the selected symbols to be the target /// sequence that each player needs to select in order. /// public class SequenceSelector : NetworkBehaviour { [SerializeField] private SymbolData m_symbolData = default; [SerializeField] private Image[] m_targetSequenceOutput = default; public const int k_symbolCount = 100; private bool m_hasReceivedTargetSequence = false; // TODO: Perhaps split up members by client vs. host? private ulong m_localId; private List m_fullSequence = new List(); // This is owned by the host, and each index is assigned as a NetworkVariable to each SymbolObject. private NetworkList m_targetSequence; // This is owned by the host but needs to be available to all clients, so it's a NetworkedList here. private Dictionary m_targetSequenceIndexPerPlayer = new Dictionary(); // Also owned by the host, indexed by client ID. public void Awake() { m_targetSequence = new NetworkList(); } public override void OnNetworkSpawn() { if (IsHost) { // Choose some subset of the list of symbols to be present in this game, along with a target sequence. List symbolsForThisGame = SelectSymbols(m_symbolData.m_availableSymbols.Count, 8); m_targetSequence.Add(symbolsForThisGame[0]); m_targetSequence.Add(symbolsForThisGame[1]); m_targetSequence.Add(symbolsForThisGame[2]); // Then, ensure that the target sequence is present in order throughout most of the full set of symbols to spawn. int numTargetSequences = k_symbolCount / 6; // About 1/2 of the 3 symbols will be definitely part of the target sequence. for (; numTargetSequences >= 0; numTargetSequences--) { m_fullSequence.Add(m_targetSequence[2]); // We want a List instead of a Queue or Stack for faster insertion, but we will remove indices backwards so as to not reshift other entries. m_fullSequence.Add(m_targetSequence[1]); m_fullSequence.Add(m_targetSequence[0]); } // Then, fill in with a good mix of the remaining symbols. AddHalfRemaining(3, 2); AddHalfRemaining(4, 2); AddHalfRemaining(5, 2); AddHalfRemaining(6, 2); AddHalfRemaining(7, 1); void AddHalfRemaining(int symbolIndex, int divider) { int remaining = k_symbolCount - m_fullSequence.Count; for (int n = 0; n < remaining / divider; n++) { int randomIndex = UnityEngine.Random.Range(0, m_fullSequence.Count); m_fullSequence.Insert(randomIndex, symbolsForThisGame[symbolIndex]); } } } m_localId = NetworkManager.Singleton.LocalClientId; AddClient_ServerRpc(m_localId); } [ServerRpc(RequireOwnership = false)] private void AddClient_ServerRpc(ulong id) { m_targetSequenceIndexPerPlayer.Add(id, 0); } // Very simple random selection. Duplicates are allowed. private static List SelectSymbols(int numOptions, int targetCount) { List list = new List(); for (int n = 0; n < targetCount; n++) list.Add(UnityEngine.Random.Range(0, numOptions)); return list; } public void Update() { // We can't guarantee timing with the host's selection of the target sequence, so retrieve it once it's available. if (!m_hasReceivedTargetSequence && m_targetSequence.Count > 0) { for (int n = 0; n < m_targetSequence.Count; n++) m_targetSequenceOutput[n].sprite = m_symbolData.GetSymbolForIndex(m_targetSequence[n]); m_hasReceivedTargetSequence = true; ScaleTargetUi(m_localId, 0); } } /// /// If the index is correct, this will advance the current sequence index. /// public bool ConfirmSymbolCorrect(ulong id, int symbolIndex) { int index = m_targetSequenceIndexPerPlayer[id]; if (symbolIndex != m_targetSequence[index]) return false; if (++index >= m_targetSequence.Count) index = 0; m_targetSequenceIndexPerPlayer[id] = index; ScaleTargetUi_ClientRpc(id, index); return true; } [ClientRpc] private void ScaleTargetUi_ClientRpc(ulong id, int sequenceIndex) { ScaleTargetUi(id, sequenceIndex); } private void ScaleTargetUi(ulong id, int sequenceIndex) { if (NetworkManager.Singleton.LocalClientId == id) for (int i = 0; i < m_targetSequenceOutput.Length; i++) m_targetSequenceOutput[i].transform.localScale = Vector3.one * (sequenceIndex == i ? 1 : 0.7f); } public int GetNextSymbol(int symbolObjectIndex) { return m_fullSequence[symbolObjectIndex]; } } }