# Chokeの実装

* アルゴリズムとして定義することで検証可能にする
* PeerInfoクラスを定義する
* UnchokeしたPeerからChokeするPeerを選択する
* ChokeしたPeerから、UnchokeするPeerを選択する

Chokeを実装してみましょう。Torrentクライアントの通信部分やアプリケーションの部分と、アルゴリズムの部分は分離する事が望ましいです。

このアルゴリズムじたいをテスト可能にするためです。アプリケーション層とまぜると、アプリケーションの操作としてテストしなくては検証できません。 通信部分の一部としてしまっては、通信機能としてテストする事になります。通信部分を利用しなくてはテスト出来ない状態になります。

アルゴリズムとして定義する事で、検証が容易になります。

## PeerInfoクラスを定義する

Chokeアルゴリズムに必要な要素を考えて、PeerInfoというクラスを定義しました。

PeerInfoには、Peerのステータスの一覧が定義しました。

```
abstract class TorrentClientPeerInfo {
  static const int STATE_NONE = 0;
  static const int STATE_ON = 1;
  static const int STATE_OFF = 2;
  String ip = "";
  int port = 0;
  List<int> get peerId;
  int get downloadedBytesFromMe;
  int get uploadedBytesToMe;
  int get chokedFromMe;
  int get chokedToMe;
  int get interestedToMe;
  int get interestedFromMe;
  bool get amI;
  bool get isClose;
  int get uploadSpeedFromUnchokeFromMe;
}
```

```
class TorrentClientPeerInfos {
  List<TorrentClientPeerInfo> _peerInfos = [];
  List<TorrentClientPeerInfo> get rawpeerInfos => _peerInfos;
  int get numOfPeerInfo => _peerInfos.length;

  TorrentClientPeerInfos() {}

  List<TorrentClientPeerInfo> getPeerInfos(Function filter) {
    List<TorrentClientPeerInfo> t = [];
    for (TorrentClientPeerInfo x in _peerInfos) {
      if (filter(x)) {
        t.add(x);
      }
    }
    return t;
  }

  void addPeerInfo(TorrentClientPeerInfo info) {
    _peerInfos.add(info);
  }
}
```

前章で定義したメッセージに対応したものですね。 このListからChokeする、UnchokeするPeerの一覧を返すメソッドを作れば、Choke機能を実装したことになります。

## UnchokeしたPeerからChokeするPeerを選択する

```
List<TorrentClientPeerInfo> extractChokePeerFromUnchoke(TorrentClientPeerInfos infos, int maxOfReplace, int maxOfUnchoke) {
    List<TorrentClientPeerInfo> unchokedPeers = infos.getPeerInfos((TorrentClientPeerInfo info) {
      return (info.isClose == false && info.chokedFromMe == TorrentClientPeerInfo.STATE_OFF && info.amI == false);
    });
    List<TorrentClientPeerInfo> alivePeer = infos.getPeerInfos((TorrentClientPeerInfo info) {
      return (info.isClose == false && info.amI == false);
    });

    List<TorrentClientPeerInfo> ret = [];
    if (alivePeer.length > maxOfUnchoke) {
      unchokedPeers.sort((TorrentClientPeerInfo x, TorrentClientPeerInfo y) {
        return x.uploadSpeedFromUnchokeFromMe - y.uploadSpeedFromUnchokeFromMe;
      });

      int numOfReplace = alivePeer.length - maxOfUnchoke;
      numOfReplace = ((maxOfReplace < numOfReplace) ? maxOfReplace : numOfReplace);
      for (int i = 0; i < numOfReplace && i < unchokedPeers.length; i++) {
        ret.add(unchokedPeers[i]);
      }
    }
    return ret;
  }
```

## ChokeしたPeerから、UnchokeするPeerを選択する

```
  List<TorrentClientPeerInfo> extractUnchokePeerFromChoke(TorrentClientPeerInfos infos, int numOfUnchoke) {
    List<TorrentClientPeerInfo> unchokeInterestedPeers = infos.getPeerInfos((TorrentClientPeerInfo info) {
      return (info.isClose == false && info.interestedToMe == TorrentClientPeerInfo.STATE_ON && info.chokedFromMe == TorrentClientPeerInfo.STATE_ON && info.amI == false);
    });
    List<TorrentClientPeerInfo> unchokeNotInterestedPeers = infos.getPeerInfos((TorrentClientPeerInfo info) {
      return (info.isClose == false && info.interestedToMe != TorrentClientPeerInfo.STATE_ON && info.chokedFromMe == TorrentClientPeerInfo.STATE_ON && info.amI == false);
    });
    unchokeInterestedPeers.shuffle();
    List<TorrentClientPeerInfo> ret = [];
    for (int i = 0; i < unchokeInterestedPeers.length && ret.length < numOfUnchoke; i++) {
      ret.add(unchokeInterestedPeers[i]);
    }
    for (int i = 0; i < unchokeNotInterestedPeers.length && ret.length < numOfUnchoke; i++) {
      ret.add(unchokeNotInterestedPeers[i]);
    }
    return ret;
  }
```

## Choke、UnchokeするPeerを選択する

今までに作成したメソッドを合わせことで実現できます。

```
  TorrentAIChokeTestResult extractChokeAndUnchoke(TorrentClientPeerInfos infos, int maxUnchoke, int maxReplace) {
    List<TorrentClientPeerInfo> unchokeFromMePeers = infos.getPeerInfos((TorrentClientPeerInfo info) {
      return (info.isClose == false && info.chokedFromMe == TorrentClientPeerInfo.STATE_OFF && info.amI == false);
    });
    List<TorrentClientPeerInfo> aliveAndNotChokePeer = infos.getPeerInfos((TorrentClientPeerInfo info) {
      return (info.isClose == false && info.amI == false && info.chokedFromMe != TorrentClientPeerInfo.STATE_OFF);
    });
    List<TorrentClientPeerInfo> chokePeers = extractChokePeerFromUnchoke(infos, maxReplace, maxUnchoke);
    for (TorrentClientPeerInfo info in chokePeers) {
      aliveAndNotChokePeer.remove(info);
    }
    int n = unchokeFromMePeers.length - chokePeers.length;
    List<TorrentClientPeerInfo> unchokePeers = extractUnchokePeerFromChoke(infos, maxUnchoke - n);
    for (TorrentClientPeerInfo info in unchokePeers) {
      aliveAndNotChokePeer.remove(info);
    }

    TorrentAIChokeTestResult ret = new TorrentAIChokeTestResult();
    ret.choke.addAll(chokePeers);
    ret.choke.addAll(aliveAndNotChokePeer);
    ret.unchoke.addAll(unchokePeers);
    return ret;
  }
```


---

# 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://nazenani-torrent.firefirestyle.net/client/chokeimpl.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.
