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