Latest
Loading...

Travel the world

Climb the mountains

GameManagerMahjong

this is whole script using System.Collections;

using System.Collections.Generic;

using UnityEngine;

using Photon.Pun;

using Photon.Realtime;

using TMPro;

using DG.Tweening;

using UnityEngine.UI;

using System.Linq;

using System;

public class GameManagerMahjong : MonoBehaviourPunCallbacks

{

[Header("References")]

public TextMeshProUGUI playerIndex;

public Transform[] playerStartPoints;

public List<Transform> discardPositions;

public GameObject distributionPanel, completePanel;

public TextMeshProUGUI winResultText;

public GameObject PongPanel, GangPanel;

public Button pongYes, pongNo, GangYes, GangNo, chiBtn;

[Header("Tiles Lists")]

public List<GameObject> allTiles = new();

public List<GameObject> myTiles = new();

public List<GameObject> stackTiles = new();

public List<GameObject> discardedTiles = new(); // ADD THIS LINE

[Header("Read Only")]

public bool isDistributionDone = false;

private bool ispanelShow = false;

public bool tileisDrawn = false;

public Vector3 myNextTilePos;

// public Quaternion myNextTileRot;

public GameObject MydrawnTile;

private List<int> stackTileViewIDs = new();

private List<int> DiscardTileViewIDs = new();

private TurnManager turnManager;

// Tracks next discard position offset per player

private Dictionary<int, Vector3> playerDiscardOffsets = new();

void Awake()

{

turnManager = GetComponent<TurnManager>();

}

void Start()

{

PongPanel.SetActive(false);

GangPanel.SetActive(false);

chiBtn.onClick.AddListener(OnChiButtonClicked);

pongYes.onClick.AddListener(Pong_Yes);

pongNo.onClick.AddListener(Pong_No);

GangYes.onClick.AddListener(Gang_Yes);

GangNo.onClick.AddListener(Gang_No);

distributionPanel.SetActive(true);

playerIndex.text = "Player id: " + PhotonNetwork.LocalPlayer.ActorNumber.ToString();

if (PhotonNetwork.IsMasterClient)

StartCoroutine(SpawnAndDistributeTiles());

}

IEnumerator SpawnAndDistributeTiles()

{

GameObject[] tilePrefabs = Resources.LoadAll<GameObject>("Tiles");

List<string> spawnQueue = new();

/* foreach (GameObject prefab in tilePrefabs)

{

for (int i = 0; i < 4; i++)

spawnQueue.Add(prefab.name);

} */

foreach (GameObject prefab in tilePrefabs)

{

string name = prefab.name.ToUpper().Replace("(CLONE)", "").Trim();

if (string.IsNullOrEmpty(name) || name.Length < 2)

continue;

char suit = name[0];

int count = suit switch

{

'A' => 1, // 🐔 Chicken, Cat, Rat, etc.

'F' => 2, // 🌸 Flower tiles

'C' => 4,

'D' => 4,

'R' => 4,

'S' => 4,

'W' => 4,

_ => 0

};

if (count == 0)

{

Debug.LogWarning($"Skipping unknown tile type: {name}");

continue;

}

Debug.Log($"Spawning {count} of tile: {name}");

for (int i = 0; i < count; i++)

spawnQueue.Add(name);

}

Shuffle(spawnQueue);

Player[] players = PhotonNetwork.PlayerList;

Dictionary<int, List<int>> playerTileViewIDs = new();

for (int i = 0; i < spawnQueue.Count; i++)

{

string prefabName = spawnQueue[i];

int playerIndex = i / 13;

int actorID = -1;

Vector3 spawnPos = Vector3.zero;

Quaternion spawnRot = Quaternion.identity;

if (playerIndex < players.Length && i < players.Length * 13)

{

actorID = players[playerIndex].ActorNumber;

Transform startPoint = playerStartPoints[actorID - 1];

spawnPos = startPoint.position + startPoint.right * (i % 13) * 0.1f;

spawnRot = startPoint.rotation;

}

GameObject tile = PhotonNetwork.Instantiate("Tiles/" + prefabName, spawnPos, spawnRot);

allTiles.Add(tile);

PhotonView pv = tile.GetComponent<PhotonView>();

if (actorID != -1)

{

pv.TransferOwnership(actorID);

if (!playerTileViewIDs.ContainsKey(actorID))

playerTileViewIDs[actorID] = new List<int>();

playerTileViewIDs[actorID].Add(pv.ViewID);

if (actorID == PhotonNetwork.LocalPlayer.ActorNumber)

{

myTiles.Add(tile);

// Save next tile position after the last one (13th tile)

if (myTiles.Count == 13)

{

myNextTilePos = tile.transform.position + playerStartPoints[actorID - 1].right * 0.1f;

}

}

}

else

{

tile.gameObject.tag = "Stack";

stackTiles.Add(tile);

stackTileViewIDs.Add(pv.ViewID); // Add to synced stack

}

yield return new WaitForSeconds(0.01f);

}

photonView.RPC(nameof(SyncTileLists), RpcTarget.OthersBuffered, GetViewIDArray(allTiles), stackTileViewIDs.ToArray());

StartCoroutine(DistributeAndHandleInitialFlowerTiles(playerTileViewIDs));

}

IEnumerator DistributeAndHandleInitialFlowerTiles(Dictionary<int, List<int>> playerTileViewIDs)

{

foreach (var entry in playerTileViewIDs.OrderBy(e => e.Key)) // sort by actorID for consistency

{

int actorID = entry.Key;

int[] viewIDs = entry.Value.ToArray();

// Send initial tiles to player

photonView.RPC(nameof(SyncMyTiles), PhotonNetwork.CurrentRoom.GetPlayer(actorID), viewIDs);

// Wait until this player is done with flower/animal draw

yield return new WaitUntil(() => playerReadyFlags.Contains(actorID));

playerReadyFlags.Remove(actorID);

}

Debug.Log("✅ All players done processing flower/animal tiles.");

// ✅ Fire a global RPC here

photonView.RPC(nameof(RPC_AllPlayersFinishedFlowerDraw), RpcTarget.All);

}

[PunRPC]

void RPC_AllPlayersFinishedFlowerDraw()

{

Debug.Log("🎉 All players finished initial setup! Start the game now.");

// Example:

isDistributionDone = true;

}

[PunRPC]

void SyncTileLists(int[] allIDs, int[] stackIDs)

{

allTiles.Clear();

stackTiles.Clear();

stackTileViewIDs.Clear();

foreach (int id in allIDs)

{

PhotonView pv = PhotonView.Find(id);

if (pv != null) allTiles.Add(pv.gameObject);

}

foreach (int id in stackIDs)

{

PhotonView pv = PhotonView.Find(id);

if (pv != null)

{

stackTiles.Add(pv.gameObject);

stackTileViewIDs.Add(id);

}

}

}

[PunRPC]

void RPC_NotifyFlowerDrawDone(int actorID)

{

if (PhotonNetwork.IsMasterClient)

playerReadyFlags.Add(actorID);

}

[PunRPC]

void SyncMyTiles(int[] viewIDs)

{

myTiles.Clear();

int actorID = PhotonNetwork.LocalPlayer.ActorNumber;

for (int i = 0; i < viewIDs.Length; i++)

{

PhotonView pv = PhotonView.Find(viewIDs[i]);

if (pv != null && pv.OwnerActorNr == actorID)

{

myTiles.Add(pv.gameObject);

if (i == 12)

{

Transform startPoint = playerStartPoints[actorID - 1];

myNextTilePos = pv.transform.position + startPoint.right * 0.1f;

}

}

else if (pv != null)

{

Debug.LogWarning($"🚫 Ignored tile {pv.name} - owned by {pv.OwnerActorNr}, expected {actorID}");

}

}

StartCoroutine(HandleInitialFlowerAnimalDraw());

}

[Header("Debug")]

public List<GameObject> flowerAnimalTiles = new(); // 🟢 Visible in Inspector

[Header("Flower/Animal Base Positions Per Player")]

public List<Transform> flowerAnimalStartPoints = new(); // Size = 4 (Player 1 to 4)

IEnumerator HandleInitialFlowerAnimalDraw()

{

Debug.Log("🟢 Starting HandleInitialFlowerAnimalDraw()");

yield return new WaitForSeconds(1);

flowerAnimalTiles.Clear();

int actorID = PhotonNetwork.LocalPlayer.ActorNumber;

List<GameObject> toRemove = new();

// Identify flower/animal tiles in hand

foreach (GameObject tile in myTiles)

{

string name = tile.name.ToUpper().Replace("(CLONE)", "").Trim();

if (name.StartsWith("A") || name.StartsWith("F"))

{

toRemove.Add(tile);

}

}

// Move them to flower/animal visual zone

foreach (GameObject tile in toRemove)

{

myTiles.Remove(tile);

flowerAnimalTiles.Add(tile);

Transform flowerStartPoint = flowerAnimalStartPoints[actorID - 1];

Vector3 flowerBasePos = flowerStartPoint.position;

Quaternion flowerRotation = flowerStartPoint.rotation;

Vector3 offset = flowerStartPoint.right * (flowerAnimalTiles.Count - 1) * 0.1f;

Vector3 targetPos = flowerBasePos + offset;

/* tile.transform.DOKill(true); // kills any existing tweens */

tile.transform.position = targetPos; // optional snap before DOTween

/* tile.transform.DOMove(targetPos, 0.2f).SetEase(Ease.OutQuad); */

tile.transform.DORotateQuaternion(flowerRotation, 0.4f);

tile.transform.position = targetPos; // optional snap before DOTween

int viewID = tile.GetComponent<PhotonView>().ViewID;

photonView.RPC(nameof(RPC_MoveFlowerTile), RpcTarget.All, viewID, targetPos, flowerRotation);

}

// 🟡 Draw until hand is complete (13 tiles)

while (myTiles.Count < 13)

{

yield return StartCoroutine(DrawUntilNotFlowerOrAnimal());

yield return new WaitForSeconds(0.1f);

}

yield return new WaitForSeconds(0.5f);

for (int i = 0; i < 2; i++)

{

RearrangeMyTiles();

yield return new WaitForSeconds(0.25f);

}

photonView.RPC(nameof(RPC_NotifyFlowerDrawDone), RpcTarget.MasterClient, PhotonNetwork.LocalPlayer.ActorNumber);

}

private HashSet<int> playerReadyFlags = new();

IEnumerator DrawUntilNotFlowerOrAnimal()

{

int actorID = PhotonNetwork.LocalPlayer.ActorNumber;

if (stackTiles.Count == 0)

{

Debug.LogWarning("⚠️ Stack is empty, cannot draw.");

yield break;

}

GameObject tile = stackTiles[0];

int viewID = tile.GetComponent<PhotonView>().ViewID;

stackTiles.RemoveAt(0);

stackTileViewIDs.RemoveAt(0);

photonView.RPC(nameof(UpdateStackTiles), RpcTarget.All, stackTileViewIDs.ToArray());

// Request tile from MasterClient

photonView.RPC(nameof(RPC_RequestTileMoveAndOwnershipTransfer_Stack), RpcTarget.MasterClient, viewID, actorID);

// Wait until tile is in my hand and owned

yield return new WaitUntil(() =>

myTiles.Contains(tile) &&

tile.GetComponent<PhotonView>().OwnerActorNr == actorID

);

string name = tile.name.ToUpper().Replace("(CLONE)", "").Trim();

if (name.StartsWith("A") || name.StartsWith("F"))

{

Debug.Log("🌸 Replacement tile is also flower/animal — showing and retrying.");

myTiles.Remove(tile);

flowerAnimalTiles.Add(tile);

Transform flowerStartPoint = flowerAnimalStartPoints[actorID - 1];

Vector3 flowerBasePos = flowerStartPoint.position;

Quaternion flowerRotation = flowerStartPoint.rotation;

Vector3 offset = flowerStartPoint.right * (flowerAnimalTiles.Count - 1) * 0.1f;

Vector3 targetPos = flowerBasePos + offset;

tile.transform.DOKill(true); // kills any existing tweens

tile.transform.position = targetPos; // optional snap before DOTween

tile.transform.DOMove(targetPos, 0.4f).SetEase(Ease.OutQuad);

tile.transform.DORotateQuaternion(flowerRotation, 0.4f);

int viewID1 = tile.GetComponent<PhotonView>().ViewID;

photonView.RPC(nameof(RPC_MoveFlowerTile), RpcTarget.All, viewID1, targetPos, flowerRotation);

// Try drawing again

yield return new WaitForSeconds(0.3f);

yield return StartCoroutine(DrawUntilNotFlowerOrAnimal());

yield break;

}

else

{

Debug.Log("✅ Replacement tile is regular. Added to hand.");

}

}

[PunRPC]

void RPC_MoveFlowerTile(int viewID, Vector3 position, Quaternion rotation)

{

PhotonView tileView = PhotonView.Find(viewID);

if (tileView != null)

{

GameObject tile = tileView.gameObject;

tile.transform.DOKill(true);

tile.transform.position = position;

tile.transform.DORotateQuaternion(rotation, 0.4f);

}

}

public TextMeshProUGUI stacktilesText;

[PunRPC]

void UpdateStackTiles(int[] updatedStackIDs)

{

stackTiles.Clear();

stackTileViewIDs = new List<int>(updatedStackIDs);

foreach (int id in updatedStackIDs)

{

PhotonView pv = PhotonView.Find(id);

if (pv != null)

stackTiles.Add(pv.gameObject);

}

stacktilesText.text = "Remaining Tiles : " + stackTiles.Count;

if (stackTiles.Count < 15)

{

StartCoroutine(HandleGameOver());

}

}

[PunRPC]

void UpdateDiscardTiles(int[] updatedStackIDs)

{

discardedTiles.Clear();

DiscardTileViewIDs = new List<int>(updatedStackIDs);

foreach (int id in updatedStackIDs)

{

PhotonView pv = PhotonView.Find(id);

if (pv != null)

discardedTiles.Add(pv.gameObject);

}

/* stacktilesText.text = "Remaining Tiles : " + stackTiles.Count; */

/* if (stackTiles.Count < 83)

{

StartCoroutine(HandleGameOver());

} */

}

[SerializeField] private GameObject gameOverPanel;

public void _gotoMAdinMenu()

{

PhotonNetwork.LeaveRoom();

PhotonNetwork.LoadLevel("MainMenu"); // replace "MainMenu" with your scene name

}

IEnumerator HandleGameOver()

{

if (gameOverPanel != null)

gameOverPanel.SetActive(true);

yield return new WaitForSeconds(3f);

PhotonNetwork.LeaveRoom();

PhotonNetwork.LoadLevel("MainMenu"); // replace "MainMenu" with your scene name

}

int[] GetViewIDArray(List<GameObject> tileList)

{

int[] ids = new int[tileList.Count];

for (int i = 0; i < tileList.Count; i++)

ids[i] = tileList[i].GetComponent<PhotonView>().ViewID;

return ids;

}

void Shuffle(List<string> list)

{

for (int i = list.Count - 1; i > 0; i--)

{

int rand = UnityEngine.Random.Range(0, i + 1);

(list[i], list[rand]) = (list[rand], list[i]);

}

}

#region Discard Tile

private bool wasTileDrawnByClick = false;

private Vector3 savedTilePosition;

private Quaternion savedTileRotation;

private Dictionary<int, int> playerDiscardCounts = new Dictionary<int, int>();

public void DiscardTile(GameObject tileToDiscard)

{

ExitGames.Client.Photon.Hashtable props = new ExitGames.Client.Photon.Hashtable();

props["LastDiscarder"] = PhotonNetwork.LocalPlayer.ActorNumber;

PhotonNetwork.CurrentRoom.SetCustomProperties(props);

int actorID = PhotonNetwork.LocalPlayer.ActorNumber;

if (actorID < 1 || actorID > discardPositions.Count)

{

Debug.LogWarning("Invalid actor ID or discard position not assigned.");

return;

}

Transform baseDiscardPoint = discardPositions[actorID - 1];

if (!playerDiscardOffsets.ContainsKey(actorID))

playerDiscardOffsets[actorID] = Vector3.zero;

if (!playerDiscardCounts.ContainsKey(actorID))

playerDiscardCounts[actorID] = 0;

int tileCount = playerDiscardCounts[actorID];

Vector3 offset;

int row = tileCount / 6; // 2 tiles per row

int col = tileCount % 6;

offset = baseDiscardPoint.right * (-0.1f * col) + baseDiscardPoint.forward * (0.15f * row);

Vector3 targetPos = baseDiscardPoint.position + offset;

// 🔁 New logic: if tile being discarded is part of a Combo, clear other Combo tags

if (tileToDiscard.tag == "Combo")

{

foreach (GameObject t in myTiles)

{

if (t != null && t.tag == "Combo")

{

t.tag = "Untagged";

photonView.RPC(nameof(RPC_SetTileTag), RpcTarget.All, t.GetComponent<PhotonView>().ViewID, "Untagged");

}

}

}

tileToDiscard.transform.DOKill(true); // kills any existing tweens

tileToDiscard.transform.position = targetPos; // optional snap before DOTween

tileToDiscard.transform.DOMove(targetPos, 0.4f).SetEase(Ease.OutQuad);

tileToDiscard.transform.DORotateQuaternion(baseDiscardPoint.rotation, 0.4f).OnComplete(() =>

{

tileisDrawn = false;

MydrawnTile = null;

myTiles.Remove(tileToDiscard);

tileToDiscard.tag = "Stack";

int viewID = tileToDiscard.GetComponent<PhotonView>().ViewID;

// Transfer ownership to MasterClient

photonView.RPC(nameof(RequestOwnershipTransfer), RpcTarget.MasterClient, viewID, PhotonNetwork.MasterClient.ActorNumber);

photonView.RPC(nameof(RPC_SetTileTag), RpcTarget.All, viewID, "Stack");

discardedTiles.Add(tileToDiscard);

SetLastDiscardedTile(tileToDiscard); // remove this

DiscardTileViewIDs.Add(viewID);

photonView.RPC(nameof(RPC_AnnounceDiscardedTile), RpcTarget.All, tileToDiscard.name, actorID);

photonView.RPC(nameof(UpdateDiscardTiles), RpcTarget.All, DiscardTileViewIDs.ToArray());

// NEW: Tell MasterClient to force the tile to this position + rotation

photonView.RPC(nameof(RPC_SyncTileTransformWithMaster), RpcTarget.MasterClient, viewID, tileToDiscard.transform.position, tileToDiscard.transform.rotation.eulerAngles);

photonView.RPC(nameof(RPC_DiscardTile), RpcTarget.Others, viewID, actorID, offset);

playerDiscardCounts[actorID]++;

// ⬇️ Ask other players to check win based on this tile

photonView.RPC(nameof(RPC_CheckOthersForWinByID), RpcTarget.Others, viewID);

photonView.RPC(nameof(RPC_CheckAndReduceTiles), RpcTarget.All);

RequestRearrangeTilesOnOthers();

RearrangeMyTiles();

SendMyTilesToMaster();

turnManager.NextTurn();

});

}

[PunRPC]

public void RPC_CheckOthersForWinByID(int discardedTileViewID)

{

// Don't process on the discarding player itself

if (PhotonView.Find(discardedTileViewID) == null)

{

Debug.LogWarning($"ViewID {discardedTileViewID} not found");

return;

}

GameObject discardedTile = PhotonView.Find(discardedTileViewID).gameObject;

// Make a copy of our current hand (myTiles)

List<GameObject> simulatedHand = new List<GameObject>(myTiles);

// Create a dummy copy of the discarded tile (do NOT modify original tile)

/* GameObject cloneTile = Instantiate(discardedTile);

cloneTile.name = discardedTile.name.Replace("(Clone)", "").Trim(); // optional cleanup

cloneTile.tag = "Simulated"; // optional tag

cloneTile.SetActive(false); // invisible if needed */

simulatedHand.Add(discardedTile);

print("check wining after discard");

// Check for win

CheckWin(simulatedHand, true);

}

[PunRPC]

void RPC_AnnounceDiscardedTile(string tileName, int actorNumber)

{

Debug.Log($"Player {actorNumber} discarded tile: {tileName}");

// Optionally show tile visually on table/discard pile

// ShowDiscardedTile(tileName, actorNumber);

}

#region Chi

[Header("Chi Info")]

public List<GameObject> possibleChiTiles = new List<GameObject>();

[PunRPC]

void CallChiPanelHide()

{

turnManager.chiPanel.SetActive(false);

}

public void OnChiButtonClicked()

{

photonView.RPC(nameof(CallChiPanelHide), RpcTarget.All);

Debug.Log("chi button clicked - process last discarded tile");

// 1. Get the last discarded tile

if (!PhotonNetwork.CurrentRoom.CustomProperties.TryGetValue("LastDiscarded", out object viewIdObj))

{

Debug.LogWarning("LastDiscarded property not found.");

return;

}

if (viewIdObj is int viewID && viewID != -999)

{

PhotonView tileView = PhotonView.Find(viewID);

if (tileView != null)

{

GameObject tileObject = tileView.gameObject;

Debug.Log("✅ ganga ues drawing last discarded tile: " + tileObject.name);

/* // 2. Temporarily stop others from drawing

turnManager.DisableAllDrawButtons();

*/

// 3. Transfer ownership and place it visually

GetComponent<GameManagerMahjong>().DrawClickedOne(tileObject);

// 4. Set turn AFTER tile drawn

StartCoroutine(WaitAndGiveTurn(tileObject, 3));

turnManager.chiPanel.gameObject.SetActive(false);

// 5. Reset the discarded tile reference

ExitGames.Client.Photon.Hashtable props = new ExitGames.Client.Photon.Hashtable();

props["LastDiscarded"] = -999;

PhotonNetwork.CurrentRoom.SetCustomProperties(props);

}

else

{

Debug.LogWarning("PhotonView not found for ViewID: " + viewID);

}

}

}

/* public bool CheckChiAction()

{

possibleChiTiles.Clear(); // Clear previous results

if (!PhotonNetwork.CurrentRoom.CustomProperties.ContainsKey("LastDiscarded"))

{

Debug.Log("LastDiscarded key not found");

return false;

}

// Retrieve the ViewID (int)

if (!(PhotonNetwork.CurrentRoom.CustomProperties["LastDiscarded"] is int viewID))

{

Debug.LogWarning("LastDiscarded is not an int ViewID");

return false;

}

// Get the PhotonView

PhotonView view = PhotonView.Find(viewID);

if (view == null)

{

Debug.LogWarning("PhotonView not found with ViewID: " + viewID);

return false;

}

GameObject tile = view.gameObject;

string discardedTileName = tile.name.Replace("(Clone)", "").Trim();

char suit = discardedTileName[0];

if (suit == 'W' || suit == 'R') return false;

Debug.Log("last tile: " + discardedTileName);

possibleChiTiles.Add(tile); // Clear previous results

return IsChiPossible(discardedTileName, myTiles);

}

*/

public bool CheckChiAction()

{

possibleChiTiles.Clear(); // Clear previous results

if (!PhotonNetwork.CurrentRoom.CustomProperties.ContainsKey("LastDiscarded"))

{

Debug.Log("LastDiscarded key not found");

return false;

}

if (!(PhotonNetwork.CurrentRoom.CustomProperties["LastDiscarded"] is int viewID))

{

Debug.LogWarning("LastDiscarded is not an int ViewID");

return false;

}

PhotonView view = PhotonView.Find(viewID);

if (view == null)

{

Debug.LogWarning("PhotonView not found with ViewID: " + viewID);

return false;

}

GameObject tile = view.gameObject;

string discardedTileName = tile.name.Replace("(Clone)", "").Trim();

// ✅ Skip Chi for Wind or Dragon

char suit = discardedTileName[0];

if (suit == 'W' || suit == 'R') return false;

Debug.Log("last tile: " + discardedTileName);

possibleChiTiles.Add(tile); // Include the discarded tile

return IsChiPossible(discardedTileName, myTiles);

}

private bool IsChiPossible(string discardedTileName, List<GameObject> myTiles)

{

if (discardedTileName.Length < 2) return false;

char suit = discardedTileName[0];

if (!int.TryParse(discardedTileName.Substring(1), out int value))

return false;

// ✅ Build tile lookup, SKIP tiles with tag "Combo"

Dictionary<string, GameObject> tileLookup = new Dictionary<string, GameObject>();

foreach (GameObject tile in myTiles)

{

if (tile.CompareTag("Combo")) continue; // ✅ Skip already used tiles

string name = tile.name.Replace("(Clone)", "").Trim();

if (!tileLookup.ContainsKey(name))

tileLookup[name] = tile;

}

// Chi pattern options

string left1 = $"{suit}{value - 2}";

string left2 = $"{suit}{value - 1}";

string right1 = $"{suit}{value + 1}";

string right2 = $"{suit}{value + 2}";

// Check for valid sequences

if (tileLookup.ContainsKey(left1) && tileLookup.ContainsKey(left2))

{

possibleChiTiles.Add(tileLookup[left1]);

possibleChiTiles.Add(tileLookup[left2]);

return true;

}

if (tileLookup.ContainsKey(left2) && tileLookup.ContainsKey(right1))

{

possibleChiTiles.Add(tileLookup[left2]);

possibleChiTiles.Add(tileLookup[right1]);

return true;

}

if (tileLookup.ContainsKey(right1) && tileLookup.ContainsKey(right2))

{

possibleChiTiles.Add(tileLookup[right1]);

possibleChiTiles.Add(tileLookup[right2]);

return true;

}

return false;

}

void SetLastDiscardedTile(GameObject tile)

{

int viewID = tile.GetComponent<PhotonView>().ViewID;

ExitGames.Client.Photon.Hashtable props = new ExitGames.Client.Photon.Hashtable();

props["LastDiscarded"] = viewID;

PhotonNetwork.CurrentRoom.SetCustomProperties(props);

}

#endregion

// Example: Set last discarded tile

#region Pong

[PunRPC]

void CallPongPanelHide()

{

PongPanel.SetActive(false);

dummyreverse();

}

void Pong_No()

{

/* isfromclick = true; */

if (GetComponent<TurnManager>().IsMyTurn())

DrawFromStack();

PongPanel.SetActive(false);

turnManager.RequestPauseTimer(false);

}

void Pong_Yes()

{

SetPlayerClaimedPong(true);

// GetComponent<TaiCalculator>().isConcealedHand = false;

photonView.RPC(nameof(CallPongPanelHide), RpcTarget.All);

Debug.Log("pong button clicked - process last discarded tile");

// 1. Get the last discarded tile

if (!PhotonNetwork.CurrentRoom.CustomProperties.TryGetValue("LastDiscarded", out object viewIdObj))

{

Debug.LogWarning("LastDiscarded property not found.");

return;

}

if (viewIdObj is int viewID && viewID != -999)

{

PhotonView tileView = PhotonView.Find(viewID);

if (tileView != null)

{

GameObject tileObject = tileView.gameObject;

Debug.Log("✅ Pong_Yes drawing last discarded tile: " + tileObject.name);

/* // 2. Temporarily stop others from drawing

turnManager.DisableAllDrawButtons();

*/

// 3. Transfer ownership and place it visually

GetComponent<GameManagerMahjong>().DrawClickedOne(tileObject);

// 4. Set turn AFTER tile drawn

StartCoroutine(WaitAndGiveTurn(tileObject, 3));

PongPanel.gameObject.SetActive(false);

// 5. Reset the discarded tile reference

ExitGames.Client.Photon.Hashtable props = new ExitGames.Client.Photon.Hashtable();

props["LastDiscarded"] = -999;

PhotonNetwork.CurrentRoom.SetCustomProperties(props);

}

else

{

Debug.LogWarning("PhotonView not found for ViewID: " + viewID);

}

}

}

[Header("Matched Chi Tiles")]

public List<GameObject> matching = new List<GameObject>();

[PunRPC]

public void RpcReverse()

{

GetComponent<GameManagerMahjong>().dummyreverse();

}

IEnumerator WaitAndGiveTurn(GameObject tileObject, int count)

{

print("ma coroine ma a gya hu ");

int actorID = PhotonNetwork.LocalPlayer.ActorNumber;

// wait until tile is owned and in hand

yield return new WaitUntil(() =>

tileObject.GetComponent<PhotonView>().OwnerActorNr == actorID &&

myTiles.Contains(tileObject)

);

if (possibleChiTiles.Count > 0 && count == 3)

{

//chi

matching = new List<GameObject>(possibleChiTiles);

}

else

{

//pong gang

print("tile mara pass a gya hu");

Transform startPoint = playerStartPoints[actorID - 1];

string tileName = tileObject.name.Replace("(Clone)", "").Trim();

// Replace `count` with the actual number you need

matching = myTiles

.Where(t => t.name.Replace("(Clone)", "").Trim() == tileName)

.Take(count)

.ToList();

}

//idr chi ka liya alga logic chaheya

if (matching.Count == count)

{

foreach (GameObject item in matching)

{

photonView.RPC(nameof(RPC_SetTileTag), RpcTarget.All, item.GetComponent<PhotonView>().ViewID, "Combo");

}

DisplayComboTilesOut(matching);

}

turnManager.RequestPauseTimer(false);

turnManager.GiveTurnToThisPlayer(actorID - 1); // Set the turn properly

}

public bool RPC_CheckPong()

{

possibleChiTiles.Clear();

if (!PhotonNetwork.CurrentRoom.CustomProperties.ContainsKey("LastDiscarded"))

{

Debug.Log("LastDiscarded key not found");

return false;

}

// Retrieve the ViewID (int)

if (!(PhotonNetwork.CurrentRoom.CustomProperties["LastDiscarded"] is int viewID))

{

Debug.LogWarning("LastDiscarded is not an int ViewID");

return false;

}

// Get the PhotonView

PhotonView view = PhotonView.Find(viewID);

if (view == null)

{

Debug.LogWarning("PhotonView not found with ViewID: " + viewID);

return false;

}

print(viewID + " this is view id");

if (viewID == -999)

{

print("this id is -999");

return false;

}

GameObject tile = view.gameObject;

string discardedTileName = tile.name.Replace("(Clone)", "").Trim();

Debug.Log("last tile: " + discardedTileName);

return IsPongPossible(discardedTileName, this.myTiles);

}

private bool IsPongPossible(string discardedTileName, List<GameObject> myTiles)

{

int matchCount = 0;

foreach (GameObject tile in myTiles)

{

string name = tile.name.Replace("(Clone)", "").Trim();

if (name == discardedTileName)

{

matchCount++;

}

}

return matchCount == 2; // Need 2 to form a Pong with the third

}

#endregion

#region Gang

[PunRPC]

void CallGangPanelHide()

{

GangPanel.SetActive(false);

}

[PunRPC]

void CallwincanHide()

{

youWonButton.SetActive(false);

}

void Gang_No()

{

/* isfromclick = true; */

if (GetComponent<TurnManager>().IsMyTurn())

DrawFromStack();

GangPanel.SetActive(false);

turnManager.RequestPauseTimer(false);

}

void Gang_Yes()

{

// GetComponent<TaiCalculator>().isConcealedHand = false;

photonView.RPC(nameof(CallGangPanelHide), RpcTarget.All);

Debug.Log("gang button clicked - process last discarded tile");

// 1. Get the last discarded tile

if (!PhotonNetwork.CurrentRoom.CustomProperties.TryGetValue("LastDiscarded", out object viewIdObj))

{

Debug.LogWarning("LastDiscarded property not found.");

return;

}

if (viewIdObj is int viewID && viewID != -999)

{

PhotonView tileView = PhotonView.Find(viewID);

if (tileView != null)

{

GameObject tileObject = tileView.gameObject;

Debug.Log("✅ ganga ues drawing last discarded tile: " + tileObject.name);

/* // 2. Temporarily stop others from drawing

turnManager.DisableAllDrawButtons();

*/

// 3. Transfer ownership and place it visually

GetComponent<GameManagerMahjong>().DrawClickedOne(tileObject);

// 4. Set turn AFTER tile drawn

StartCoroutine(WaitAndGiveTurn(tileObject, 4));

GangPanel.gameObject.SetActive(false);

// 5. Reset the discarded tile reference

ExitGames.Client.Photon.Hashtable props = new ExitGames.Client.Photon.Hashtable();

props["LastDiscarded"] = -999;

PhotonNetwork.CurrentRoom.SetCustomProperties(props);

}

else

{

Debug.LogWarning("PhotonView not found for ViewID: " + viewID);

}

}

}

public bool RPC_CheckGang()

{

possibleChiTiles.Clear();

if (!PhotonNetwork.CurrentRoom.CustomProperties.ContainsKey("LastDiscarded"))

{

Debug.Log("LastDiscarded key not found");

return false;

}

// Retrieve the ViewID (int)

if (!(PhotonNetwork.CurrentRoom.CustomProperties["LastDiscarded"] is int viewID))

{

Debug.LogWarning("LastDiscarded is not an int ViewID");

return false;

}

print(viewID + " this is view id");

if (viewID == -999)

return false;

// Get the PhotonView

PhotonView view = PhotonView.Find(viewID);

if (view == null)

{

Debug.LogWarning("PhotonView not found with ViewID: " + viewID);

return false;

}

GameObject tile = view.gameObject;

string discardedTileName = tile.name.Replace("(Clone)", "").Trim();

Debug.Log("last tile: " + discardedTileName);

return IsGangPossible(discardedTileName, this.myTiles);

}

private bool IsGangPossible(string discardedTileName, List<GameObject> myTiles)

{

int matchCount = 0;

foreach (GameObject tile in myTiles)

{

string name = tile.name.Replace("(Clone)", "").Trim();

if (name == discardedTileName)

{

matchCount++;

}

}

return matchCount == 3; // Need 2 to form a gang with the third

}

#endregion

[PunRPC]

void RPC_SyncTileTransformWithMaster(int viewID, Vector3 position, Vector3 eulerRotation)

{

GameObject tile = PhotonView.Find(viewID)?.gameObject;

if (tile == null) return;

tile.transform.position = position;

tile.transform.rotation = Quaternion.Euler(eulerRotation);

}

[PunRPC]

void RPC_DiscardTile(int tileViewID, int actorID, Vector3 offset)

{

GameObject tile = PhotonView.Find(tileViewID).gameObject;

if (tile == null || actorID < 1 || actorID > discardPositions.Count) return;

Transform baseDiscardPoint = discardPositions[actorID - 1];

Vector3 targetPos = baseDiscardPoint.position + offset;

tile.transform.DOKill(true); // kills any existing tweens

tile.transform.position = targetPos; // optional snap before DOTween

tile.transform.DOMove(targetPos, 0.4f).SetEase(Ease.OutQuad);

tile.transform.DORotateQuaternion(baseDiscardPoint.rotation, 0.4f);

tile.tag = "Stack";/*

discardedTiles.Add(tile); */

/* stackTiles.Add(tile); */

}

[PunRPC]

public void RPC_CheckAndReduceTiles()

{

if (myTiles.Count >= 14)

{

GameObject lastTile = myTiles[myTiles.Count - 1];

if (lastTile != null && lastTile.GetComponent<PhotonView>() != null)

{

int viewID = lastTile.GetComponent<PhotonView>().ViewID;

myTiles.RemoveAt(myTiles.Count - 1);

Debug.Log($"🚮 Removed excess tile {lastTile.name} (ViewID {viewID}) from myTiles on player {PhotonNetwork.LocalPlayer.ActorNumber}.");

ReverseDueToUnplayedTile(viewID);

}

else

{

Debug.LogWarning("⚠️ Tried to remove last tile but it was null or missing PhotonView.");

}

}

else

{

Debug.Log($"✅ myTiles count is {myTiles.Count}, no need to remove.");

}

}

[PunRPC]

public void RPC_RearrangeMyTiles()

{

RearrangeMyTiles();

}

public void RequestRearrangeTilesOnOthers()

{

photonView.RPC(nameof(RPC_RearrangeMyTiles), RpcTarget.Others);

}

public void RearrangeMyTiles()

{

int actorID = PhotonNetwork.LocalPlayer.ActorNumber;

if (actorID < 1 || actorID > playerStartPoints.Length) return;

Transform startPoint = playerStartPoints[actorID - 1];

// Ensure clean list, no nulls or dups

myTiles = myTiles.Where(t => t != null).Distinct().ToList();

// Separate Combo and Non-Combo tiles

var comboTiles = myTiles.Where(t => t.CompareTag("Combo")).ToList();

var normalTiles = myTiles.Except(comboTiles).OrderBy(t => GetTileSortKey(t.name)).ToList();

// Prepare sorted result

List<GameObject> sortedTiles = new List<GameObject>();

// Step 1: Gangs (4 of same name)

var gangGroups = comboTiles.GroupBy(t => t.name).Where(g => g.Count() == 4);

foreach (var group in gangGroups)

sortedTiles.AddRange(group);

// Step 2: Pongs (3 of same name)

var pongGroups = comboTiles.GroupBy(t => t.name).Where(g => g.Count() == 3);

foreach (var group in pongGroups)

sortedTiles.AddRange(group);

// Step 3: Chi (3 in sequence, same suit)

var usedForChi = new HashSet<GameObject>();

var chiCandidates = comboTiles

.Except(gangGroups.SelectMany(g => g))

.Except(pongGroups.SelectMany(g => g))

.OrderBy(t => GetTileSortKey(t.name))

.ToList();

for (int i = 0; i < chiCandidates.Count - 2;)

{

var t1 = chiCandidates[i];

var t2 = chiCandidates[i + 1];

var t3 = chiCandidates[i + 2];

if (IsChiSequence(t1.name, t2.name, t3.name))

{

sortedTiles.Add(t1);

sortedTiles.Add(t2);

sortedTiles.Add(t3);

usedForChi.Add(t1);

usedForChi.Add(t2);

usedForChi.Add(t3);

i += 3;

}

else

{

i++;

}

}

// Step 4: Remaining Combo tiles (not used in any group)

foreach (var tile in comboTiles)

if (!sortedTiles.Contains(tile))

sortedTiles.Add(tile);

// Step 5: Remaining normal tiles

sortedTiles.AddRange(normalTiles);

// Final assignment

myTiles = sortedTiles;

// Animate all tiles to new positions

for (int i = 0; i < myTiles.Count; i++)

{

int tileIndex = i;

Vector3 targetPos = startPoint.position + startPoint.right * tileIndex * 0.1f;

Quaternion targetRot = startPoint.rotation;

myTiles[tileIndex].transform.DOKill(true); // kills any existing tweens

myTiles[tileIndex].transform.position = targetPos; // optional snap before DOTween

myTiles[tileIndex].transform.DOMove(targetPos, 0.4f).SetEase(Ease.OutQuad);

//myTiles[tileIndex].transform.DOMove(targetPos, 0.4f).SetEase(Ease.OutQuad);

// Only rotate if not combo

if (!myTiles[tileIndex].CompareTag("Combo"))

{

myTiles[tileIndex].transform.DORotateQuaternion(targetRot, 0.4f);

}

}

List<GameObject> tilesToKeep = new List<GameObject>();

foreach (var tile in myTiles)

{

if (tile == null || tile.GetComponent<PhotonView>() == null) continue;

int ownerActor = tile.GetComponent<PhotonView>().OwnerActorNr;

if (ownerActor == actorID)

{

tilesToKeep.Add(tile);

}

else

{

// 🚀 Ask the owner to add this tile to their myTiles list

photonView.RPC(nameof(RPC_AddTileToMyTiles), PhotonNetwork.CurrentRoom.GetPlayer(ownerActor), tile.GetComponent<PhotonView>().ViewID);

}

}

myTiles = tilesToKeep.Distinct().ToList();

// Debug check

if (myTiles.Count < 13)

Debug.LogWarning($"❌ [TileLoss] Player {actorID} has only {myTiles.Count} tiles after rearrange!");

}

[PunRPC]

public void RPC_AddTileToMyTiles(int tileViewID)

{

GameObject tile = PhotonView.Find(tileViewID)?.gameObject;

if (tile == null)

{

Debug.LogWarning($"⚠️ Tile with ViewID {tileViewID} not found.");

return;

}

bool alreadyHasTile = myTiles.Any(t =>

t != null && t.GetComponent<PhotonView>() != null &&

t.GetComponent<PhotonView>().ViewID == tileViewID

);

if (!alreadyHasTile)

{

if (myTiles.Count < 13)

{

myTiles.Add(tile);

Debug.Log($"✅ [TileTransfer] Added tile {tile.name} (ViewID {tileViewID}) to myTiles on player {PhotonNetwork.LocalPlayer.ActorNumber}. Now has {myTiles.Count} tiles.");

}

else

{

Debug.LogWarning($"🚫 [TileTransfer] Not adding tile {tile.name} (ViewID {tileViewID}). myTiles already has {myTiles.Count} tiles.");

}

}

else

{

Debug.Log($"🔄 [TileTransfer] Player {PhotonNetwork.LocalPlayer.ActorNumber} already has tile {tile.name} (ViewID {tileViewID}).");

}

RearrangeMyTiles();

}

private bool IsChiSequence(string n1, string n2, string n3)

{

// Check if all same suit: first letter must match

if (n1[0] != n2[0] || n2[0] != n3[0])

return false;

// Suit must be one of the valid ones

if (!(n1.StartsWith("C") || n1.StartsWith("D") || n1.StartsWith("S")))

return false;

try

{

int i1 = int.Parse(n1.Substring(1));

int i2 = int.Parse(n2.Substring(1));

int i3 = int.Parse(n3.Substring(1));

var nums = new[] { i1, i2, i3 };

Array.Sort(nums);

return nums[1] == nums[0] + 1 && nums[2] == nums[1] + 1;

}

catch

{

return false;

}

}

public void DrawFromStack()

{

SendMyTilesToMaster();

if (!turnManager.IsMyTurn())

{

Debug.Log("❌ Not your turn!");

return;

}

if (tileisDrawn)

{

Debug.Log("❌ You already drew a tile. Discard it first.");

return;

}

if (stackTiles.Count == 0) return;

RequestRearrangeTilesOnOthers();

StartCoroutine(DrawTileAndCheckForFlower());

}

IEnumerator DrawTileAndCheckForFlower()

{

int actorID = PhotonNetwork.LocalPlayer.ActorNumber;

GameObject tile = stackTiles[0];

int viewID = tile.GetComponent<PhotonView>().ViewID;

stackTiles.RemoveAt(0);

stackTileViewIDs.RemoveAt(0);

photonView.RPC(nameof(UpdateStackTiles), RpcTarget.All, stackTileViewIDs.ToArray());

// Request tile move + ownership

photonView.RPC(nameof(RPC_RequestTileMoveAndOwnershipTransfer_Stack), RpcTarget.MasterClient, viewID, actorID);

// Wait until tile is owned + added to hand

yield return new WaitUntil(() =>

myTiles.Contains(tile) &&

tile.GetComponent<PhotonView>().OwnerActorNr == actorID

);

string name = tile.name.ToUpper().Replace("(CLONE)", "").Trim();

if (name.StartsWith("A") || name.StartsWith("F"))

{

Debug.Log("🌸 Drew flower/animal tile, displaying and drawing again");

myTiles.Remove(tile);

flowerAnimalTiles.Add(tile);

Transform flowerStartPoint = flowerAnimalStartPoints[actorID - 1];

Vector3 flowerBasePos = flowerStartPoint.position;

Quaternion flowerRotation = flowerStartPoint.rotation;

Vector3 offset = flowerStartPoint.right * 0.1f * (flowerAnimalTiles.Count - 1);

Vector3 targetPos = flowerBasePos + offset;

tile.transform.DOKill(true); // kills any existing tweens

tile.transform.position = targetPos; // optional snap before DOTween

tile.transform.DOMove(targetPos, 0.4f).SetEase(Ease.OutQuad);

tile.transform.DORotateQuaternion(flowerRotation, 0.4f);

// Wait a moment and draw again

yield return new WaitForSeconds(0.3f);

StartCoroutine(DrawTileAndCheckForFlower());

yield break;

}

else

{

Debug.Log("✅ Drew regular tile.");

tileisDrawn = true;

MydrawnTile = tile;

/* RearrangeMyTiles(); */

}

}

[PunRPC]

void RPC_RequestTileMoveAndOwnershipTransfer_Stack(int viewID, int targetActorID)

{

GameObject tile = PhotonView.Find(viewID).gameObject;

Transform startPoint = playerStartPoints[targetActorID - 1];

Vector3 targetPos = startPoint.position + (startPoint.right * 0.1f * 13);

tile.transform.position = startPoint.position + (startPoint.right * 0.1f * 13);

tile.transform.rotation = startPoint.rotation;

// Transfer ownership to target player

tile.GetComponent<PhotonView>().TransferOwnership(targetActorID);

Player target = PhotonNetwork.PlayerList.FirstOrDefault(p => p.ActorNumber == targetActorID);

if (target != null)

photonView.RPC(nameof(RPC_FinalizeTileOnClientstack), target, viewID);

else

Debug.Log("❌ Player not found for actorID: " + targetActorID);

}

[PunRPC]

void RPC_FinalizeTileOnClientstack(int viewID)

{

GameObject tile = PhotonView.Find(viewID).gameObject;

int actorID = PhotonNetwork.LocalPlayer.ActorNumber;

Transform startPoint = playerStartPoints[actorID - 1];

if (!myTiles.Contains(tile)) // avoid duplicates

myTiles.Add(tile);

// Finalize locally (for safety)

tile.transform.position = startPoint.position + (startPoint.right * 0.1f * 13);

tile.transform.rotation = startPoint.rotation;

tile.gameObject.tag = "Untagged";

photonView.RPC(nameof(RPC_SetTileUntagged), RpcTarget.Others, viewID);

StartCoroutine(DiscardTimer(tile));

CheckWin(myTiles, false);

}

[PunRPC]

public void RequestOwnershipTransfer(int viewID, int targetActorID)

{

PhotonView pv = PhotonView.Find(viewID);

if (pv != null && PhotonNetwork.IsMasterClient)

{

print("trnasering ownership");

pv.TransferOwnership(targetActorID);

}

}

public void DrawClickedOne(GameObject tile)

{

SendMyTilesToMaster();

wasTileDrawnByClick = true;

savedTilePosition = tile.transform.position;

savedTileRotation = tile.transform.rotation;

int actorID = PhotonNetwork.LocalPlayer.ActorNumber;

Transform startPoint = playerStartPoints[actorID - 1];

/* if (!turnManager.IsMyTurn())

{

Debug.Log("Not your turn!");

return;

} */

MydrawnTile = tile;

if (tileisDrawn)

{

Debug.Log("You already drew a tile. Discard it first.");

return;

}

if (discardedTiles.Count == 0) return;

tileisDrawn = true;

GameObject drawn = tile;

myTiles.Add(drawn);

int viewID = drawn.GetComponent<PhotonView>().ViewID;

photonView.RPC(nameof(RequestOwnershipTransfer), RpcTarget.MasterClient, viewID, PhotonNetwork.MasterClient.ActorNumber);

// Ask the MasterClient to move the tile and transfer ownership to us

photonView.RPC(nameof(RPC_RequestTileMoveAndOwnershipTransfer), RpcTarget.MasterClient, viewID, actorID);

// Update local game state but delay applying local transform changes until ownership is transferred

discardedTiles.Remove(drawn);

DiscardTileViewIDs.Remove(viewID);

photonView.RPC(nameof(UpdateDiscardTiles), RpcTarget.All, DiscardTileViewIDs.ToArray());

StartCoroutine(DiscardTimer(tile));

CheckWin(myTiles, false);

}

[PunRPC]

void RPC_RequestTileMoveAndOwnershipTransfer(int viewID, int targetActorID)

{

GameObject tile = PhotonView.Find(viewID).gameObject;

if (myTiles.Contains(tile) == false)

myTiles.Add(tile);

// Find the target player's start point

Transform startPoint = playerStartPoints[targetActorID - 1];

tile.transform.position = startPoint.position + (startPoint.right * 0.1f * 13);

/* tile.transform.rotation = startPoint.rotation; */

// Transfer ownership once movement is complete

tile.GetComponent<PhotonView>().TransferOwnership(targetActorID);

// Notify the player who requested it to finish tagging and rotation locally

/* photonView.RPC(nameof(RPC_FinalizeTileOnClient), PhotonNetwork.CurrentRoom.GetPlayer(targetActorID), viewID); */

/* // Move the tile on the MasterClient

tile.transform.DOMove(startPoint.position + (startPoint.right * 0.1f * 13), 0.4f).SetEase(Ease.OutQuad);

tile.transform.DORotateQuaternion(startPoint.rotation, 0.4f).SetEase(Ease.OutQuad).OnComplete(() =>

{

}); */

}

[PunRPC]

void RPC_FinalizeTileOnClient(int viewID)

{

GameObject tile = PhotonView.Find(viewID).gameObject;

// Set tag and finalize position/rotation (just in case)

tile.gameObject.tag = "Untagged";

photonView.RPC(nameof(RPC_SetTileUntagged), RpcTarget.Others, viewID);

// Optional safety re-alignment

int actorID = PhotonNetwork.LocalPlayer.ActorNumber;

Transform startPoint = playerStartPoints[actorID - 1];

if (tile.GetComponent<PhotonView>().IsMine)

{

if (!myTiles.Contains(tile)) // avoid duplicates

myTiles.Add(tile);

tile.transform.position = startPoint.position + (startPoint.right * 0.1f * 13);

tile.transform.rotation = startPoint.rotation;

}

StartCoroutine(DiscardTimer(tile));

CheckWin(myTiles, false);

}

// public void DrawClickedOne(GameObject tile)

// {

// // Save data before changing it

// wasTileDrawnByClick = true;

// savedTilePosition = tile.transform.position;

// savedTileRotation = tile.transform.rotation;

// int actorID = PhotonNetwork.LocalPlayer.ActorNumber;

// Transform startPoint = playerStartPoints[actorID - 1];

// if (!turnManager.IsMyTurn())

// {

// Debug.Log("Not your turn!");

// return;

// }

// if (tileisDrawn)

// {

// Debug.Log("You already drew a tile. Discard it first.");

// return;

// }

// if (discardedTiles.Count == 0) return;

// tileisDrawn = true;

// GameObject drawn = tile;

// int viewID = drawn.GetComponent<PhotonView>().ViewID;

// // ✅ Ask MasterClient to transfer ownership to us

// photonView.RPC(nameof(RequestOwnershipTransfer), RpcTarget.MasterClient, viewID, actorID);

// discardedTiles.Remove(drawn);

// DiscardTileViewIDs.Remove(drawn.GetComponent<PhotonView>().ViewID);

// photonView.RPC(nameof(UpdateDiscardTiles), RpcTarget.All, DiscardTileViewIDs.ToArray());

// myTiles.Add(drawn);

// MydrawnTile = drawn;

// drawn.transform.position = startPoint.position + (startPoint.right * 0.1f * 13);

// drawn.transform.DOMove(startPoint.position + (startPoint.right * 0.1f * 13), 0.4f).SetEase(Ease.OutQuad);

// drawn.gameObject.tag = "Untagged";

// /* // Set local tag

// drawn.gameObject.tag = "Untagged";

// */

// // Inform others to do the same

// photonView.RPC(nameof(RPC_SetTileUntagged), RpcTarget.Others, drawn.GetComponent<PhotonView>().ViewID);

// drawn.transform.rotation = startPoint.rotation;

// //drawn.transform.DOLocalRotate(startPoint.rotation.eulerAngles, 0.4f).SetEase(Ease.OutQuad);

// drawn.transform.DORotateQuaternion(startPoint.rotation, 0.4f);

// /* photonView.RPC(nameof(UpdateStackTiles), RpcTarget.All, stackTileViewIDs.ToArray()); */

// StartCoroutine(DiscardTimer(drawn));

// turnManager.hidestackbtn();

// CheckWin();

// }

[PunRPC]

public void RPC_SetTileUntagged(int viewID)

{

PhotonView pv = PhotonView.Find(viewID);

if (pv == null)

{

Debug.LogWarning($"Tile with ViewID {viewID} not found.");

return;

}

pv.gameObject.tag = "Untagged";

// Debug.Log($"[RPC] Tag set to Untagged for tile: {pv.gameObject.name}");

}

public void NewRPCForClickedDrawnTile(int tileViewID)

{

// Call the RPC on all clients (including this one)

photonView.RPC(nameof(RPC_HandleClickedDrawnTile), RpcTarget.All, tileViewID);

}

// The actual RPC method that runs on all clients

[PunRPC]

private void RPC_HandleClickedDrawnTile(int tileViewID)

{

PhotonView tileView = PhotonView.Find(tileViewID);

if (tileView == null)

{

Debug.LogWarning("Tile not found for viewID: " + tileViewID);

return;

}

GameObject tile = tileView.gameObject;

Debug.Log("RPC_HandleClickedDrawnTile executed for tile: " + tile.name);

// Example: Do something with the clicked drawn tile here

// For example, call your existing method to process drawn tile click

DrawClickedOne(tile);

}

private IEnumerator DiscardTimer(GameObject drawnTile)

{

float waitTime = turnManager.timer; // adjust time as needed

yield return new WaitForSeconds(waitTime);

if (tileisDrawn && MydrawnTile == drawnTile)

{

ReverseDueToUnplayedTile();

}

}

public void dummyreverse()

{

if (tileisDrawn)

{

ReverseDueToUnplayedTile();

}

}

/* public void ReverseDueToUnplayedTile()

{

if (tileisDrawn && MydrawnTile != null)

{

GameObject tile = MydrawnTile;

tileisDrawn = false;

MydrawnTile = null;

if (myTiles.Contains(tile))

myTiles.Remove(tile);

if (wasTileDrawnByClick)

{

tile.transform.DOMove(savedTilePosition, 0.4f).SetEase(Ease.OutQuad);

tile.transform.DORotateQuaternion(savedTileRotation, 0.4f);

}

else

{

tile.transform.position = Vector3.zero; // or stack area

}

wasTileDrawnByClick = false; // reset

tileisDrawn = false;

MydrawnTile = null;

tile.gameObject.tag = "Stack";

stackTiles.Add(tile);

stackTileViewIDs.Add(tile.GetComponent<PhotonView>().ViewID);

RearrangeMyTiles();

photonView.RPC(nameof(UpdateStackTiles), RpcTarget.All, stackTileViewIDs.ToArray());

// 🔁 Tell all clients to set the tag

photonView.RPC(nameof(RPC_SetTileTag), RpcTarget.All, tile.GetComponent<PhotonView>().ViewID, "Stack");

}

} */

public void ReverseDueToUnplayedTile()

{

if (tileisDrawn && MydrawnTile != null)

{

GameObject tile = MydrawnTile;

tileisDrawn = false;

MydrawnTile = null;

// 🔁 Remove tile from hand if it exists

if (myTiles.Contains(tile))

myTiles.Remove(tile);

// 🔁 Animate or reposition

if (wasTileDrawnByClick)

{

tile.transform.DOKill(true); // kills any existing tweens

tile.transform.position = savedTilePosition; // optional snap before DOTween

tile.transform.DOMove(savedTilePosition, 0.4f).SetEase(Ease.OutQuad);

tile.transform.DORotateQuaternion(savedTileRotation, 0.4f);

}

else

{

tile.transform.position = Vector3.zero;

}

// 🔁 Reset click flag

wasTileDrawnByClick = false;

tileisDrawn = false;

MydrawnTile = null;

// 🔁 If the drawn tile had a "Combo" tag, untag others

if (tile.tag == "Combo")

{

foreach (GameObject t in myTiles)

{

if (t != null && t.tag == "Combo")

{

t.tag = "Untagged";

photonView.RPC(nameof(RPC_SetTileTag), RpcTarget.All, t.GetComponent<PhotonView>().ViewID, "Untagged");

}

}

}

// 🔁 Move the tile to the stack

tile.tag = "Stack";

stackTiles.Add(tile);

stackTileViewIDs.Add(tile.GetComponent<PhotonView>().ViewID);

// 🔁 Rearrange and sync

RearrangeMyTiles();

photonView.RPC(nameof(UpdateStackTiles), RpcTarget.All, stackTileViewIDs.ToArray());

// 🔁 Sync tag change

photonView.RPC(nameof(RPC_SetTileTag), RpcTarget.All, tile.GetComponent<PhotonView>().ViewID, "Stack");

}

}

public void ReverseDueToUnplayedTile(int viewID)

{

GameObject tile = PhotonView.Find(viewID)?.gameObject;

if (tile == null)

{

Debug.LogWarning($"⚠️ ReverseDueToUnplayedTile: Tile with ViewID {viewID} not found.");

return;

}

// 🔁 Remove tile from hand if it exists

if (myTiles.Contains(tile))

myTiles.Remove(tile);

tileisDrawn = false;

MydrawnTile = null;

// 🔁 Animate or reposition

if (wasTileDrawnByClick)

{

tile.transform.DOKill(true);

tile.transform.position = savedTilePosition;

tile.transform.DOMove(savedTilePosition, 0.4f).SetEase(Ease.OutQuad);

tile.transform.DORotateQuaternion(savedTileRotation, 0.4f);

}

else

{

tile.transform.position = Vector3.zero;

}

// 🔁 Reset click flag

wasTileDrawnByClick = false;

tileisDrawn = false;

MydrawnTile = null;

// 🔁 If tile was a combo, untag others

if (tile.tag == "Combo")

{

foreach (GameObject t in myTiles)

{

if (t != null && t.tag == "Combo")

{

t.tag = "Untagged";

photonView.RPC(nameof(RPC_SetTileTag), RpcTarget.All, t.GetComponent<PhotonView>().ViewID, "Untagged");

}

}

}

// 🔁 Move tile back to stack

tile.tag = "Stack";

stackTiles.Add(tile);

stackTileViewIDs.Add(tile.GetComponent<PhotonView>().ViewID);

RearrangeMyTiles();

photonView.RPC(nameof(UpdateStackTiles), RpcTarget.All, stackTileViewIDs.ToArray());

// 🔁 Sync tag

photonView.RPC(nameof(RPC_SetTileTag), RpcTarget.All, viewID, "Stack");

}

[PunRPC]

public void RPC_SetTileTag(int tileViewID, string newTag)

{

PhotonView tileView = PhotonView.Find(tileViewID);

if (tileView != null)

{

tileView.gameObject.tag = newTag;

// Debug.Log("Tag set via RPC: " + newTag);

}

else

{

Debug.LogWarning("Tile view not found for RPC_SetTileTag with ID: " + tileViewID);

}

}

#endregion

#region StackButton

#endregion

#region CheckWin

/* public bool isoptionwin = false; */

public void CheckWin(List<GameObject> myTiles, bool isoption = false)

{

/* isoptionwin = false; */

Debug.Log($"myTiles Count = {myTiles.Count}, Tiles: {string.Join(",", myTiles.Select(t => t ? t.name : "NULL"))}");

if (myTiles.Count != 14)

{

Debug.Log("You must assign exactly 14 tile GameObjects to test.");

return;

}

List<string> cleanedTiles = myTiles.Select(tile => NormalizeTile(tile.name)).ToList();

if (cleanedTiles.Any(t => t == null))

{

Debug.Log("One or more tiles have invalid format. Use names like: C1, D3, S9, R1");

return;

}

cleanedTiles.Sort((a, b) => GetTileSortKey(a).CompareTo(GetTileSortKey(b)));

bool isWin = TryFormWinningHand(cleanedTiles);

/* if (isoption)

isWin = true;//testing */

if (isWin)

{

if (isoption)

{

Debug.Log($"[WinOption] {PhotonNetwork.LocalPlayer.NickName} is eligible to win.");

youWonButton.SetActive(true);

timerWin.gameObject.SetActive(true);

/* photonView.RPC("RPC_SetOptionWin", RpcTarget.All, true);

*/

StartCoroutine(WinCountdownCoroutine(10));

}

else

{

youWonButton.SetActive(false);

photonView.RPC("RPC_ShowResults", RpcTarget.All, PhotonNetwork.LocalPlayer.ActorNumber);

}

}

else

{

//not winning hand yet

}

Debug.Log(isWin ? "🎉 You WIN!" : "❌ Not a winning hand yet.");

}

/* [PunRPC]

public void RPC_SetOptionWin(bool value)

{

isoptionwin = value;

Debug.Log($"[RPC] isoptionwin set to {value} for {PhotonNetwork.LocalPlayer.NickName}");

} */

private IEnumerator WinCountdownCoroutine(int seconds)

{

float remaining = seconds;

while (remaining > 0f)

{

timerWin.text = $" {Mathf.CeilToInt(remaining)}s to win";

yield return new WaitForSeconds(1f);

remaining -= 1f;

// // If the player clicked "Win", break early

// if (!isoptionwin)

// {

// /* timerWin.text = "";

// timerWin.gameObject.SetActive(false);

// photonView.RPC("RPC_SetOptionWin", RpcTarget.All, false);

// yield break; */

// }

}

// Timer expired — auto-cancel win

Debug.Log("⏲️ Win timer expired, hiding button.");

youWonButton.SetActive(false);

photonView.RPC(nameof(CallwincanHide), RpcTarget.All);

timerWin.text = "";

timerWin.gameObject.SetActive(false);

/* isoptionwin = false; */

}

[Header("you won")]

public TextMeshProUGUI timerWin;

public GameObject youWonButton;

public void _youwonNO()

{

youWonButton.SetActive(false);

turnManager.RequestPauseTimer(false);

photonView.RPC(nameof(CallwincanHide), RpcTarget.All);

}

public void _youwon()

{

youWonButton.SetActive(false);

photonView.RPC("RPC_ShowResults", RpcTarget.All, PhotonNetwork.LocalPlayer.ActorNumber);

}

[PunRPC]

void RPC_ShowResults(int winnerActorNumber)

{

completePanel.SetActive(true);

if (PhotonNetwork.LocalPlayer.ActorNumber == winnerActorNumber)

{

winResultText.text = "You Win!";

}

else

{

winResultText.text = "You Lose!";

}

}

private string NormalizeTile(string name)

{

name = name.Replace("(Clone)", "").Trim().ToUpper();

if (string.IsNullOrEmpty(name) || name.Length < 2)

return null;

string suit = name.Substring(0, 1);

string numberPart = name.Substring(1);

if (!int.TryParse(numberPart, out int number))

return null;

if (!new[] { "A", "C", "D", "F", "R", "S", "W" }.Contains(suit))

return null;

return $"{suit}{number}";

}

private bool TryFormWinningHand(List<string> tiles)

{

for (int i = 0; i < tiles.Count - 1; i++)

{

if (tiles[i] == tiles[i + 1])

{

var remaining = new List<string>(tiles);

remaining.RemoveAt(i + 1);

remaining.RemoveAt(i);

if (CanBeDividedIntoSets(remaining))

return true;

}

}

return false;

}

private bool CanBeDividedIntoSets(List<string> tiles)

{

if (tiles.Count == 0)

return true;

for (int i = 0; i < tiles.Count - 2; i++)

{

if (tiles[i] == tiles[i + 1] && tiles[i] == tiles[i + 2])

{

var copy = new List<string>(tiles);

copy.RemoveRange(i, 3);

if (CanBeDividedIntoSets(copy))

return true;

}

}

for (int i = 0; i < tiles.Count; i++)

{

string a = tiles[i];

for (int j = 0; j < tiles.Count; j++)

{

if (i == j) continue;

string b = tiles[j];

for (int k = 0; k < tiles.Count; k++)

{

if (k == i || k == j) continue;

string c = tiles[k];

if (IsSequential(a, b, c))

{

var copy = new List<string>(tiles);

RemoveOne(copy, a);

RemoveOne(copy, b);

RemoveOne(copy, c);

if (CanBeDividedIntoSets(copy))

return true;

}

}

}

}

return false;

}

private void RemoveOne(List<string> list, string value)

{

list.Remove(value);

}

private bool IsSequential(string a, string b, string c)

{

if (a[0] != b[0] || a[0] != c[0])

return false;

var nums = new List<int>

{

int.Parse(a.Substring(1)),

int.Parse(b.Substring(1)),

int.Parse(c.Substring(1))

};

nums.Sort();

return nums[1] == nums[0] + 1 && nums[2] == nums[1] + 1;

}

private string GetTileSortKey(string tile)

{

tile = tile.Replace("(Clone)", "").Trim().ToUpper();

if (tile.Length < 2)

return "Z_00"; // fallback

string suit = tile.Substring(0, 1);

string numPart = tile.Substring(1);

if (!int.TryParse(numPart, out int number))

number = 0;

int suitOrder = suit switch

{

"C" => 0,

"D" => 1,

"S" => 2,

"W" => 3,

"R" => 4,

"F" => 5,

"A" => 6,

_ => 7

};

return $"{suitOrder:D1}_{number:D2}";

}

public void OnOwnershipRequest(PhotonView targetView, Player requestingPlayer)

{

throw new System.NotImplementedException();

}

public void OnOwnershipTransferFailed(PhotonView targetView, Player senderOfFailedRequest)

{

throw new System.NotImplementedException();

}

#endregion

#region point 3

void DisplayComboTilesOut(List<GameObject> tiles)

{

int actorID = PhotonNetwork.LocalPlayer.ActorNumber;

Transform basePoint = flowerAnimalStartPoints[actorID - 1];

Quaternion rot = basePoint.rotation;

for (int i = 0; i < tiles.Count; i++)

{

GameObject tile = tiles[i];

tile.transform.DORotateQuaternion(rot, 0.3f);

photonView.RPC(nameof(RPC_SetTileTag), RpcTarget.All, tile.GetComponent<PhotonView>().ViewID, "Combo");

}

RearrangeMyTiles();

}

public void callwaitchicoritne(GameObject tileobject)

{

print("ma is func ma a gya hu ");

StartCoroutine(WaitAndGiveTurn(tileobject, 3));

}

#endregion

// 🚀 Get who discarded last tile

public int GetLastDiscarder()

{

if (PhotonNetwork.CurrentRoom.CustomProperties.TryGetValue("LastDiscarder", out object last))

return (int)last;

return -1;

}

// 🚀 Check if local player can Pong (not on their own discard)

public bool CanLocalPlayerPong()

{

return RPC_CheckPong();

}

// 🚀 Flag to know if anyone claimed Pong

private bool playerClaimedPong = false;

public bool HasPlayerClaimedPong()

{

return playerClaimedPong;

}

public void SetPlayerClaimedPong(bool claimed)

{

playerClaimedPong = claimed;

}

public GameObject GetLastDiscardedTile()

{

if (PhotonNetwork.CurrentRoom.CustomProperties.TryGetValue("LastDiscarded", out object last))

{

int lastID = (int)last;

PhotonView pv = PhotonView.Find(lastID);

if (pv != null)

return pv.gameObject;

}

return null;

}

public List<GameObject> player1Tiles = new();

public List<GameObject> player2Tiles = new();

public List<GameObject> player3Tiles = new();

public List<GameObject> player4Tiles = new();

// 🚀 Call this from local player to send their tile data

public void SendMyTilesToMaster()

{

int[] viewIDs = myTiles

.Where(tile => tile != null && tile.GetComponent<PhotonView>() != null)

.Select(tile => tile.GetComponent<PhotonView>().ViewID)

.ToArray();

photonView.RPC(nameof(RPC_SendMyTilesToMaster), RpcTarget.MasterClient, PhotonNetwork.LocalPlayer.ActorNumber, viewIDs);

}

[PunRPC]

void RPC_SendMyTilesToMaster(int actorNumber, int[] viewIDs)

{

List<GameObject> tileList = viewIDs

.Select(id => PhotonView.Find(id)?.gameObject)

.Where(go => go != null)

.ToList();

switch (actorNumber)

{

case 1:

player1Tiles = tileList;

break;

case 2:

player2Tiles = tileList;

break;

case 3:

player3Tiles = tileList;

break;

case 4:

player4Tiles = tileList;

break;

default:

Debug.LogWarning($"⚠️ Unknown actorNumber {actorNumber} tried to send tiles.");

break;

}

Debug.Log($"✅ Received tiles for player {actorNumber}, count: {tileList.Count}");

}

} 

No comments:

Post a Comment