# Tutorial 3 - Host/Search MatchMaker

このチュートリアルでは、Diarkis の **Host/Search 方式マッチメイキング**を実装します。一方のクライアントがマッチングルームを「ホスト」し、他のクライアントがサーバーを検索して参加します。Tutorial 2 のチケット方式と対比しながら読むと理解が深まります。

終了時には以下のことが身についています:

* `HostMatchMaking()` でマッチングルームをホストする方法
* `Search(joinFlag: true)` でルームを検索して自動参加する方法
* `OnMMHost`、`OnMMResult`、`OnMMJoin` を別々に処理する方法
* ホストとして `SendDisbandMatchmaking()` でルームを解散する方法
* メンバーとして `SendLeaveMatchmaking()` でルームから離脱する方法
* `MatchMakerHostSearchState` に基づいてボタン状態を制御する方法

Tutorial 1 の接続フローが前提です。このシーンは `Start()` で自動接続します。

### チケット方式との違い

| 項目       | Ticket（Tutorial 2）                      | Host/Search（Tutorial 3）     |
| -------- | --------------------------------------- | --------------------------- |
| ホストの概念   | なし（サーバーが管理）                             | あり（クライアントが明示的に選択）           |
| 主なイベント   | `OnMMTicketJoin` / `OnMMTicketComplete` | `OnMMHost` / `OnMMJoin`     |
| 状態の型     | `MatchMakerTicketState`                 | `MatchMakerHostSearchState` |
| ルーム検索    | 不要                                      | `Search()` で検索              |
| 向いているケース | 対等なマッチング                                | ロビー方式・ルームブラウザ               |

### 仕組み

```mermaid
sequenceDiagram
    participant H as Host クライアント
    participant S as Searcher クライアント
    participant SV as Diarkis サーバー

    H->>SV: HostMatchMaking(profileID, maxMembers)
    SV-->>H: OnMMHost (roomID)
    Note over H: ルーム作成完了、参加者を待つ

    S->>SV: Search(profileID, joinFlag=true)
    SV-->>S: OnMMResult（ルーム一覧）
    SV-->>S: OnMMJoin（自動参加完了）
    SV-->>H: OnMMMemberJoin（メンバーが増えた）
```

### 状態遷移

```mermaid
stateDiagram-v2
    [*] --> None
    None --> Host : HostMatchMaking() → OnMMHost
    None --> Search : Search() 発行
    Search --> MatchingRoomJoined : OnMMJoin
    Host --> None : SendDisbandMatchmaking()
    MatchingRoomJoined --> None : SendLeaveMatchmaking()
```

### シーンのセットアップ

`Tutorials/Scenes/Tutorial3-HostSearchMatchMaker.unity` を開き、定数を環境に合わせて変更してください。

```csharp
private const string HOST       = "127.0.0.1:7000";
private const string CLIENT_KEY = "";
private const string UID        = ""; // 空文字の場合はランダム生成
```

マッチング条件も必要に応じて変更できます。

```csharp
private const string PROFILE_ID  = "RankMatch"; // Host 側と Search 側で一致させる
private const int    MAX_MEMBERS = 4;
```

Play モードに入るとシーンが自動接続します。**Network State** ラベルが緑色になったら、一方のクライアントで **Host**、もう一方で **Search** を押してください。

### コードの解説

#### MatchMaker モジュールの取得

```csharp
DiarkisInterface diarkis = DiarkisNetworkManager.GetDiarkisInterface(INTERFACE_NAME);
_matchMaker = diarkis.MatchMaker;
```

Tutorial 2 と同じパターンです。

#### イベントの登録

```csharp
DiarkisEventHandler handler = diarkis.EventHandler;

handler.OnUDPConnect(OnConnect, this);
handler.OnUDPDisconnect(OnDisconnect, this);
handler.OnUDPFail(_ => SetNetworkState("接続失敗", ColorRed), this);
handler.OnHttpError(_ => SetNetworkState("HTTP 認証エラー", ColorRed), this);

handler.OnMMHost(OnMMHost, this);
handler.OnMMResult(OnMMResult, this);
handler.OnMMJoin(OnMMJoin, this);
handler.OnMMMemberJoin(_ => RefreshMemberList(), this);
handler.OnMMMemberLeave(_ => RefreshMemberList(), this);
handler.OnMMDisband(_ => OnMMDisband(), this);
handler.OnMMLeave(_ => OnMMLeave(), this);
```

このチュートリアルで登録するイベントの一覧です。

| イベント              | 発火タイミング                              | コールバックシグネチャ                              |
| ----------------- | ------------------------------------ | ---------------------------------------- |
| `OnMMHost`        | `HostMatchMaking()` が成功してルームが作成された   | `Action<DiarkisMMHostEventArgs>`         |
| `OnMMResult`      | `Search()` の結果が返ってきた                 | `Action<DiarkisMMResultEventArgs>`       |
| `OnMMJoin`        | `Search(joinFlag=true)` による自動参加が完了した | `Action<DiarkisMMJoinResponseEventArgs>` |
| `OnMMMemberJoin`  | 他のメンバーがルームに参加した                      | `Action<DiarkisMMJoinEventArgs>`         |
| `OnMMMemberLeave` | メンバーがルームから離脱した                       | `Action<DiarkisMMLeaveEventArgs>`        |
| `OnMMDisband`     | ホストがルームを解散した                         | `Action<DiarkisMMDisbandEventArgs>`      |
| `OnMMLeave`       | 自分がルームから離脱した                         | `Action<DiarkisMMLeaveEventArgs>`        |

`OnDestroy` での `UnregisterCallbacks(this)` はお忘れなく。オーナーパターンの詳細は Tutorial 1 を参照してください。

#### HostMatchMaking()

```csharp
private void OnHostClicked()
{
    _matchMaker.HostMatchMaking(
        maxMembers: (ushort)MAX_MEMBERS,
        ttl: 60,           // ルームの有効時間（秒）
        profileID: PROFILE_ID,
        tag: PROFILE_ID);  // グルーピング用タグ。ここでは profileID と同じ値を使用
}
```

成功すると `OnMMHost` が届き、ルーム ID を取得できます。状態は `MatchMakerHostSearchState.Host` に移行します。

#### Search()

```csharp
private void OnSearchClicked()
{
    var props = new Dictionary<string, uint> { { "rank", 10 } };
    _matchMaker.Search(
        profileID: PROFILE_ID,
        tag: PROFILE_ID,
        props: props,       // サーバー側でフィルタされる数値条件
        joinFlag: true,     // true なら見つかり次第自動参加
        howmany: 1,
        message: "");
}
```

`props` はサーバー側のフィルタ条件です。ホスト側のプロパティが条件を満たすルームだけが返ります。`joinFlag: true` にすると最初のルームに自動参加して `OnMMJoin` が届きます。参加前に一覧を確認したい場合は `joinFlag: false` にしてください。

#### OnMMHost と OnMMJoin

```csharp
private void OnMMHost(DiarkisMMHostEventArgs args)
{
    if (!args.IsSuccess())
    {
        AddChatLine($"[エラー] ホスト失敗 (code: {args.GetErrorCode()})");
        return;
    }

    string roomID = args.GetRoomID();
    if (_statusText != null)   _statusText.text   = roomID;
    if (_ownerUIDText != null) _ownerUIDText.text = /* 自分の UID */;

    RefreshMemberList();
    RefreshButtons();
}

private void OnMMJoin(DiarkisMMJoinResponseEventArgs args)
{
    if (!args.IsSuccess())
    {
        AddChatLine($"[エラー] 参加失敗 (code: {args.GetErrorCode()})");
        return;
    }

    // _matchMaker.GetHostUID() でホストの UID を取得できます
    if (_ownerUIDText != null) _ownerUIDText.text = _matchMaker.GetHostUID();

    RefreshMemberList();
    RefreshButtons();
}
```

#### Disband と Leave の違い

| 操作        | メソッド                       | 呼び出せる人     | 他メンバーに届くイベント      |
| --------- | -------------------------- | ---------- | ----------------- |
| ルームを解散する  | `SendDisbandMatchmaking()` | ホストのみ      | `OnMMDisband`     |
| ルームから離脱する | `SendLeaveMatchmaking()`   | 参加者（サーチャー） | `OnMMMemberLeave` |

ホストが終了したいときは必ず **解散**（Disband）を使います。ホストが離脱すると TTL まで空のルームがサーバーに残ってしまいます。

#### メンバーリストの更新

`GetMembers(DiarkisMatchMakerType.HostSearch)` はサーバーへのリクエストなしでローカルキャッシュからメンバー一覧を返します。

```csharp
private void RefreshMemberList()
{
    List<string> members = _matchMaker?.GetMembers(DiarkisMatchMakerType.HostSearch);
    _memberListText.text = members != null && members.Count > 0
        ? string.Join("\n", members)
        : "";
}
```

`OnMMHost`、`OnMMJoin`、`OnMMMemberJoin`、`OnMMMemberLeave`、`OnMMDisband`、`OnMMLeave` から `RefreshMemberList()` を呼んで表示を最新に保ちます。

#### HostSearchState によるボタン制御

```csharp
private void RefreshButtons()
{
    MatchMakerHostSearchState state = _matchMaker.HostSearchState;

    bool notStarted  = state == MatchMakerHostSearchState.None;
    bool isHost      = state == MatchMakerHostSearchState.Host;
    bool isInRoom    = state == MatchMakerHostSearchState.MatchingRoomJoined;
    bool isSearching = state == MatchMakerHostSearchState.Search;

    if (_hostButton)    _hostButton.interactable    = notStarted && _connected;
    if (_searchButton)  _searchButton.interactable  = notStarted && _connected;
    if (_leaveButton)   _leaveButton.interactable   = isInRoom || isSearching;
    if (_disbandButton) _disbandButton.interactable = isHost;

    bool canMessage = isHost || isInRoom;
    if (_messageButton1) _messageButton1.interactable = canMessage;
}
```

`HostSearchState` の 4 状態（`None` / `Host` / `Search` / `MatchingRoomJoined`）が UI 制御の唯一の基準です。

### 次のステップ

Tutorial 4 では **Room** を学びます — マッチング後にプレイヤーをグループ化してメッセージを交換するセッション空間です。

```mermaid
flowchart LR
    MM["MatchMaker<br/>（Tutorial 2 / 3）"] -->|マッチング成立| RM["Room<br/>（Tutorial 4）"]
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://help.diarkis.io/diarkis-client/chtoriaru/unity-chtoriaru/tutorial-3-host-search-matchmaker.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
