LoginSignup
610
242

ポケモンの最強タイプを考える【グラフ理論】

Last updated at Posted at 2022-02-06

導入

先日、ポケモンの最新作『Pokémon LEGENDS アルセウス』が発売されました。ポケモン愛好家の中で密かに話題を集めたのが、新たに登場したポケモン「ゾロア(ヒスイのすがた)」と「ゾロアーク(ヒスイの姿)」のタイプです。なんと驚くべきことに、両者のタイプは未だ登場したことのなかった「ノーマル・ゴースト」だったのです。

1634948879820.jpg

ポケモンを知る人には説明不要ですが、これはノーマルタイプの唯一の弱点であるかくとう技をゴーストタイプで無効化しながら、ゴーストタイプの弱点であるゴースト技をノーマルタイプで無効化するという、非常にバランスのとれた、まさに夢のような複合タイプです。一部では、この「ノーマル・ゴースト」こそ最強の組み合わせなのではないかと噂されました。

しかし、果たして本当にそうなのでしょうか?

ポケモンのタイプは全部で18種類あり、一匹のポケモンは二つまでタイプを持つことができます。考えられるポケモンのタイプは合わせて

_{18}C_{1}+_{18}C_2=18+ 153 = 171

通りです。これらの中で最強が「ノーマル・ゴースト」なのか、あるいは別にあるのか、どのようにすれば客観的に決めることができるでしょうか。本記事ではこの問いに対する数学的なアプローチを試みます。

ポケモンのタイプ相性について

先述したように、ポケモンのタイプは全部で18種類あり、一匹のポケモンは二つまでタイプを持つことができます。一方、ポケモンの技にも一つのタイプが割り当てられています。防御側のポケモンが受けるダメージの倍率は、「使用される技のタイプ」と「自らのタイプ」の相性によって決まります。

タイプ間の相性は下図の通りです。

img_01.png

○は「効果はばつぐん!」(ダメージが2倍)、△は「効果はいまひとつ」(ダメージが0.5倍)、×は「効果がない」(ダメージが0倍)を表し、記号の書かれていない箇所は通常の効果(ダメージが1倍)を意味します。二種類のタイプ(複合タイプ)を持つポケモンが受けるダメージは、二つのタイプの相性の掛け算によって決まります。例えば、「みず・じめん」タイプのポケモンに対してくさタイプの技のダメージは2×2=4倍になり、みずタイプの技のダメージは0.5×2=1倍、でんきタイプの技のダメージは2×0=0倍になる、というわけです。

もう一点、タイプにまつわる仕様として重要なのが「タイプ一致」という概念です。これは、ポケモンが自らのタイプと同じタイプの技を使用する場合、その威力が通常の1.5倍になるというものです。ノーマルタイプのポケモンが放つ「10まんボルト」より、でんきタイプのポケモンが放つ「10まんボルト」の方が強いということになります。

ポケモン対戦の知識として必要な事項は以上です。これらのことを踏まえて検討を進めます。逆にいえば、ポケモンに関する知識はこれ以上分析の段階では登場しません。つまり、実際にそのタイプにどんな特性や種族値を持つポケモンが存在するか、どんな技が人気で優秀なのか、といったことは答えを出すまでは一切考慮に入れずに、純粋なタイプの強さだけを考察するということです。

タイプ間相性の定式化

まず、18種類のタイプにそれぞれ1から18までの番号を割り振ることにします。これにより、ポケモンのタイプは(x,y)と二つの数の組み合わせで表現できます。また、ポケモンの持つタイプが一種類である場合は、「無」タイプを表す0を用いて、(0,x)と書くことにします。

ここで、タイプxの技をタイプ(0,y)のポケモンに対して使用したときのダメージ倍率を$M_{xy}$とします。これは、前節に載せた「相性行列」のx行y列成分の倍率に対応します。

このとき、タイプxの技を、タイプ(y,z)のポケモンに対して放ったときのダメージ倍率$f(x,(y,z))$は

f(x,(y,z)) = M_{xy}M_{xz}

となります。整合性をとるために、$M_{x0}=1(x \neq 0)$が要請されます。

さらに、タイプ(w,x)のポケモンがタイプ(y,z)のポケモンに対してどれだけのダメージ倍率を与えることができるかを考えます。今、タイプ(w,x)のポケモンは、自身のタイプに含まれるタイプの技(つまりタイプ一致で放てる技)のみを使うものとします。相手に対してより有効なタイプの技の方を選ぶとして、そのときのダメージ倍率を複合ダメージ倍率$g((w,x),(y,z))$とすると、

g((w,x),(y,z))=\max{(f(w,(y,z)),f(x,(y,z)))}=\max{(M_{wy}M_{wz},M_{xy}M_{xz})}

と定式化できます。これを、(w,x)の(y,z)に対する複合ダメージ倍率と呼びことにします。
ただし、「無」タイプの技を放つことはできないので、その場合の積が最大値として選ばれないようにするため、$M_{0y}=0$とします。

試しに(みず、でんき)の(じめん、はがね)に対する複合ダメージ倍率を計算してみましょう。みずタイプの技は2×1=2倍、でんきタイプの技は0×1=0倍なので、大きい方をとった「2」がgの値となります。

ところが、これをもって(みず、でんき)が(じめん、はがね)に対して有利であるとは言い切れません。今度は(じめん、はがね)の(みず、でんき)に対する複合ダメージ倍率を考えます。じめんタイプの技は2倍、はがねタイプの技は0.5倍となり、こちらもgの値は2となります。つまり、両者はお互い弱点を突き合える関係にあり、どちらか一方が強いとは言えません。このことを踏まえると、どうやら複合タイプ間の有利・不利の関係は、互いの複合ダメージ倍率の大小関係によって決めることができそうです。

今、複合ダメージ倍率のとりうる値は0,0.25,0.5,1,2,4の6段階です。攻撃と防御を入れ替えたときの複合ダメージ倍率をそれぞれ計算し、それが6段階の中でいくつだけ差があったかによって(w,x)の(y,z)に対する有利さを定めましょう。有利さのとりうる値は-5から5までの11通りであり、これが正の値をとれば有利、負の値をとれば不利というわけです。

有利さ$h((w,x),(y,z))$、複合ダメージ倍率が0のときの扱いに注意すると、以下のようにまとめることができます。

h((w,x),(y,z)) = \log_{2}\frac{\max{(g((w,x),(y,z)),\frac{1}{8})}}{\max{(g((y,z),(w,x)),\frac{1}{8})}}

この有利さは$h((w,x),(y,z))=-h((y,z),(w,x))$という反対称性を満たしています。

今度は、(みず、でんき)の(ほのお、いわ)に対する有利さを考えてみましょう。g((みず、でんき),(ほのお、いわ))はみず技の4倍弱点があるので「4」であるのに対し、g((ほのお、いわ),(みず、でんき))はいわ技による等倍ダメージが最大なので「1」です。よって、hの値は2-0=2となり、(みず、でんき)の(ほのお、いわ)に対する有利さは「2」である(6段階で2つ分離れていることに対応する)という計算になります。

タイプの「強さ」の比較

タイプ間の有利・不利の関係が定量化できたところで、どのようにして最強タイプを求めればいいでしょうか。最もシンプルには、全ての複合タイプに対する有利さを計算し、その合計値(スコア)の大小を見るというものです。手始めにやってみましょう。

「無」タイプを含めた19種類のタイプから相異なる二つを選ぶことによって、全部で$_{19}C_2=171$通りのタイプ(単・複合)を統一的に扱うことができます。

まずは、相性行列Mの作成です。これは手作業で打ち込みました。せっかくなので、みなさん各々のポケモン研究にご活用ください。

types = ["","ノーマル","ほのお","みず","でんき","くさ","こおり","かくとう","どく",
"じめん","ひこう","エスパー","むし","いわ","ゴースト","ドラゴン","あく","はがね","フェアリー"]
M =[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.5, 0, 1, 1, 0.5, 1],
 [1, 1, 0.5, 0.5, 1, 2, 2, 1, 1, 1, 1, 1, 2, 0.5, 1, 0.5, 1, 2, 1],
 [1, 1, 2, 0.5, 1, 0.5, 1, 1, 1, 2, 1, 1, 1, 2, 1, 0.5, 1, 1, 1],
 [1, 1, 1, 2, 0.5, 0.5, 1, 1, 1, 0, 2, 1, 1, 1, 1, 0.5, 1, 1, 1],
 [1, 1, 0.5, 2, 1, 0.5, 1, 1, 0.5, 2, 0.5, 1, 0.5, 2, 1, 0.5, 1, 0.5, 1],
 [1, 1, 0.5, 0.5, 1, 2, 0.5, 1, 1, 2, 2, 1, 1, 1, 1, 2, 1, 0.5, 1],
 [1, 2, 1, 1, 1, 1, 2, 1, 0.5, 1, 0.5, 0.5, 0.5, 2, 0, 1, 2, 2, 0.5],
 [1, 1, 1, 1, 1, 2, 1, 1, 0.5, 0.5, 1, 1, 1, 0.5, 0.5, 1, 1, 0, 2],
 [1, 1, 2, 1, 2, 0.5, 1, 1, 2, 1, 0, 1, 0.5, 2, 1, 1, 1, 2, 1],
 [1, 1, 1, 1, 0.5, 2, 1, 2, 1, 1, 1, 1, 2, 0.5, 1, 1, 1, 0.5, 1],
 [1, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1, 0.5, 1, 1, 1, 1, 0, 0.5, 1],
 [1, 1, 0.5, 1, 1, 2, 1, 0.5, 0.5, 1, 0.5, 2, 1, 1, 0.5, 1, 2, 0.5, 0.5],
 [1, 1, 2, 1, 1, 1, 2, 0.5, 1, 0.5, 2, 1, 2, 1, 1, 1, 1, 0.5, 1],
 [1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 2, 1, 0.5, 1, 1],
 [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 0.5, 0],
 [1, 1, 1, 1, 1, 1, 1, 0.5, 1, 1, 1, 2, 1, 1, 2, 1, 0.5, 1, 0.5],
 [1, 1, 0.5, 0.5, 0.5, 1, 2, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 0.5, 2],
 [1, 1, 0.5, 1, 1, 1, 1, 2, 0.5, 1, 1, 1, 1, 1, 1, 2, 2, 0.5, 1]]

これをもとに、上で定めた、関数f,g,hを作っていきます。

import math
import numpy as np
def f(x,yz):
  y,z = yz
  return M[x][y]*M[x][z]
def g(wx,yz):
  w, x = wx
  y, z = yz
  return max(f(w,(y,z)),f(x,(y,z)))
def h(wx,yz):
  w, x = wx
  y, z = yz
  return int(np.round(math.log2(max(g((w,x),(y,z)),1/8))-math.log2(max(g((y,z),(w,x)),1/8))))

例えば、(みず、でんき)は (3,4)、(ほのお、いわ)は(2,13)に対応し、

print(h((3,4),(2,13)))
2

とちゃんと計算できていることがわかります。

有利さの合計値を愚直に計算してランキングを出力してみましょう。

result = []
for w in range(18):
  for x in range(w+1,19):
    score = 0
    for y in range(18):
      for z in range(y+1,19):
        score += h((w,x),(y,z))
    result.append(((w,x),score))
result.sort(key=lambda x:x[1],reverse=True)
for i in range(171):
  wx,score =  result[i]
  w,x = wx
  print(types[w],types[x],score)
(上位10タイプを抜粋)
じめん はがね 96
はがね フェアリー 92
ひこう はがね 90
ゴースト はがね 88
ほのお じめん 84
じめん ひこう 83
みず はがね 78
じめん フェアリー 72
かくとう はがね 71
ほのお はがね 67

栄えある一位に輝いたのは「じめん・はがね」でした。5タイプに効果抜群となるじめんタイプの技で広範囲に有利をとりながら、弱点が少なく耐性の多いはがねによって守りも固い。少し意外な結果でしたが、言われてみれば納得できるような気もします。また、上位10タイプのうち7タイプにはがねが入っていることからはがねの優秀さがわかります。また、二位の「はがね・フェアリー」は言わずと知れた使用率最高ポケモンの一匹「ザシアン」のものであり、本手法の有効さを裏打ちしています。

ところで、肝心の「ノーマル・ゴースト」ですが、スコアは34と伸び悩み、順位は39位にとどまりました。これは、弱点が少なく防御に優れている一方で、ノーマルとゴーストによって突ける弱点がエスパーとゴーストの二つしかなく、攻撃面が物足りないこと(不利にはなりづらいが、有利にもなりづらいこと)に起因しているようです。

ちなみに最弱はくさの単タイプでした。

手法の改良

ここで、一つ疑問が浮かびます。果たして全タイプに対する有利さの合計値を持って最強を決めるのは妥当なのでしょうか。たとえ多くのタイプに有利をとることができていても、それが対戦に現れないような弱いタイプ相手ばかりだったら役に立ちません。逆に、強いとされるタイプに対しても不利にならずに戦えるタイプこそ、真に強いタイプと言えるのではないでしょうか。

つまり、タイプの「強さ」は、相手取るタイプの「強さ」も考慮して決めるべきものであるように思われます。

この再帰的な考え方は、ウェブページの重要度を決めるためのアルゴリズムであるPageRankを想起させます。

PageRank

PageRankはウェブページのリンク関係によってそのページの重要度を決定するアルゴリズムであり、Googleの検索エンジンに用いられている技術です。単に多くのページからリンクされていれば重要度が高いのではなく、リンクされているサイトの重要度の高さおよびリンク数も加味して計算を行うというものです。具体的な内容については、グラフ理論の言葉を頼って説明するのが良いでしょう。

今、各ウェブページを頂点と見なし、あるページから別のページへのリンクを有向辺とした有向グラフを考え、その隣接行列をAとします。また、隣接行列の各行をそれぞれの頂点の出次数で割ったものをSとします。このとき、Sを転置した行列はリンクを辿ってのランダムなウェブサーフィンの動きを表す遷移確率行列となります。

さらにここで、Google行列Gを次のように定めます。

\bf{G} = \alpha \bf{S}^{T}+(1-\alpha)\bf{T}

ただし、$\alpha$はダンピング・ファクターと呼ばれるハイパーパラメータで、被リンクによる重要度の影響の大きさを調整します。通常は0.85に設定されるようです。

また、$\bf{T}$はテレポート行列と呼ばれ、全ての成分が「1/頂点数」である正方行列です。

各ページの重要度を順に並べたベクトルを$\bf{\lambda}$とすると、重要度の再帰的な定義から以下の関係が成り立ちます。

\bf{G}\bf{\lambda} = \bf{\lambda}

ところで、$\bf{G}$もまた(各頂点の持っている重要度をリンク先の頂点にも割り振るような)遷移確率行列になっているので、各列の和は全て1に等しくなります。このような性質を持つ非負行列は「縮退していない固有値1の正の固有ベクトルを持ち、なおかつそれより大きな固有値が存在しない」ことが、ペロン・フロベニウスの定理より従います。

要するに、$\bf{G}$の固有値最大の固有ベクトルこそが、我々の所望していた各ページの重要度を並べたものになっているということです。従って、(固有値が1だとわかっていることも踏まえると)重要度の計算は線型方程式の求解に帰着される、というのがPageRankの簡明にして鮮やかな主張です。

最強タイプ決めへの適用

PageRankでは、ウェブサイト間のリンクを有向辺と見なし、重要度を決める材料としていました。対してポケモンのタイプにおいては、前節までの議論により二つのタイプの間の有利不利の関係がすでに定量的にわかっています。ならば、各タイプをそれぞれ頂点に配置して、不利なタイプから有利なタイプへ重み付きの有向辺を伸ばすことで、「おれから見てこいつは強い」と指差すような(リンクに似た)効果が期待できるのではないでしょうか。

簡単のため、まず「ほのお」「みず」「じめん」「でんき」の4タイプからなるグラフで実験してみます。
「ほのお」の「みず」に対する有利さは-2
「ほのお」の「じめん」に対する有利さは-1
「ほのお」の「でんき」に対する有利さは0
「みず」の「じめん」に対する有利さは1
「みず」の「でんき」に対する有利さは-1
「じめん」の「でんき」に対する有利さは4
なので、これらをもとに、不利な方から有利な方へ「どれだけ不利か」で重み付けをした辺を伸ばします。有利さが0の場合は辺を伸ばしません。

スクリーンショット 2022-02-06 15.52.59.png

このとき、まず重み付きの隣接行列Aは

 {\bf{A}}= \begin{pmatrix} 0&2&1&0\\ 0&0&0&1\\ 0&1&0&0\\ 0&0&4&0\\ \end{pmatrix} 

となり、遷移確率行列は

 {\bf{S}^{T}} = \begin{pmatrix} 0&0&0&0\\ \frac{2}{3}&0&1&0\\ \frac{1}{3}&0&0&1\\ 0&1&0&0\\ \end{pmatrix} 

となります。よって、

\begin{eqnarray}
{\bf{G}} &=& 0.85 {\bf{S}}^{T}+0.15\bf{T}\\
&=& 0.85 \begin{pmatrix} 0&0&0&0\\ \frac{2}{3}&0&1&0\\ \frac{1}{3}&0&0&1\\ 0&1&0&0\\ \end{pmatrix} +0.15\begin{pmatrix} \frac{1}{4}&\frac{1}{4}&\frac{1}{4}&\frac{1}{4}\\ \frac{1}{4}&\frac{1}{4}&\frac{1}{4}&\frac{1}{4}\\ \frac{1}{4}&\frac{1}{4}&\frac{1}{4}&\frac{1}{4}\\\frac{1}{4}&\frac{1}{4}&\frac{1}{4}&\frac{1}{4}\\ \end{pmatrix} 
\end{eqnarray}

であり、scipyのモジュールを用いて固有値最大の固有ベクトルを計算すると次のようになりました。

from scipy.sparse.linalg import eigs
S = np.array([[0,2/3,1/3,0],[0,0,0,1],[0,1,0,0],[0,0,1,0]])
T = 1/4*np.ones((4,4))
G = 0.85*S.T+0.15*T
value, vector = eigs(G, 1)
print(value, vector)
[1.+0.j] [[-0.06731985+0.j]
 [-0.58967561+0.j]
 [-0.5696563 +0.j]
 [-0.56854412+0.j]]

固有ベクトルの成分が全て負になっているので、正に直した上で大小を比べると、第二成分が最大であり、わずかな差の後にほぼ同じ値である第三成分と第四成分、そして大きく離されて第一成分が続く格好です。これは「みず」>「じめん」≒「でんき」>>「ほのお」を意味し、「みず」「じめん」「でんき」の三竦みの均衡と一歩離された「ほのお」の構図をうまく捉えています。

タイプ数が4つだと重みをつけた旨味が現れていないですが、辺数を増やせば確実に効いてくるはずです。

それでは、全171種のタイプで同じことをやっていきましょう。

d ={} #複合タイプと数字の対応表
d_inv = {}
k = 0
for i in range(18):
  for j in range(i+1,19):
    d[(i,j)] = k
    d_inv[k] = (i,j)
    k += 1
A = [[0 for _ in range(171)] for _ in range(171)]
for w in range(18):
  for x in range(w+1,19):
    k = d[(w,x)] 
    for y in range(18):
      for z in range(y+1,19):
        l = d[(y,z)]
        if h((w,x),(y,z)) < 0:
          A[k][l] = -h((w,x),(y,z))
A = np.array(A,dtype=np.float)
S = np.zeros((171,171))
for i in range(171):
  S[i] = A[i]/sum(A[i])
T = (1/171)*np.ones((171,171))
G = 0.85*S.T+0.15*T
value, vector = eigs(G, 1)
vector = np.abs(vector.real)
result = []
for i in range(171):
  w,x = d_inv[i]
  result.append(((w,x),vector[i][0]))
result.sort(key=lambda x:x[1],reverse=True)
for i in range(171):
  wx,score =  result[i]
  w,x = wx
  print(types[w],types[x],score)
(上位10タイプを抜粋)
みず じめん 0.12901610166092206
じめん ひこう 0.12333336303638766
ほのお じめん 0.1230335213682247
じめん はがね 0.12253193098002743
ほのお はがね 0.11962533181338252
でんき はがね 0.11808330879585195
ひこう はがね 0.11638423434901143
はがね フェアリー 0.11133780309618507
みず フェアリー 0.11083098136702635
くさ はがね 0.11081844379517954

結果はがらりと変わりました。一位の座を射止めたのは(前手法では圏外だった)まさかの「みず・じめん」です。強力だったはがね勢は依然として有力ながらもやや後退し、入れ替わるようにはがねの弱点を突くことができるじめん勢が躍進しています。この傾向の変化を見るに、PageRank風アルゴリズムは予期した通り「強いタイプに対しても戦えるタイプが真に強い」という理念を反映する役割を、見事に果たしているようです。

最新のポケモン使用率ランキング(2022/02/06)と照らし合わせてみましょう。1位の「ザシアン」の「はがね・フェアリー」は8位に、2位の「ランドロス」の「じめん・ひこう」は2位にそれぞれ入っています。本記事の分析において、そのタイプにどんなポケモンがいるのかは全く考慮していないのにも関わらず、このような合致が見られるのはなかなか興味深く、純粋なタイプの「強さ」のみを研究することの有意義さを示唆しています。次ぐ「メタモン」は特殊すぎるので置いておいて、4位の「サンダー」の「でんき・ひこう」はこちらの分析では44位とあまり奮いません。タイプの評価が低いのにも関わらず採用されるポケモンには、タイプ相性ではない別の強さがあるのかと思われます。

参考にために本分析におけるタイプの順位を並べると、次のようになりました。

スクリーンショット 2022-02-06 17.28.55.png

見ての通り、順位が低くとも採用されているポケモンは多いですが、そういったポケモンは種族値の並外れた伝説のポケモン(カイオーガ、ムゲンダイナ、バドレックス、レジエレキ)や、ユニークで大きな持ち味となる特性を持つポケモン(メタモン、エースバーン、ミミッキュ、ヌケニン)であり、タイプとしての強さに勝る長所を持っているようです。

そして特筆すべきは、何と言っても「みず・じめん」勢の存在感です。圏内に全く同じタイプ構成のポケモンはノーマルタイプを除いて珍しい中、「みず・じめん」タイプのポケモンは35位以内に4匹も食い込んでいます。しかも、「トリトドン」「ヌオー」「ガマゲロゲ」「ラグラージ」の四体はいずれも伝説のポケモンではなく、持っている特性も唯一無二というわけではありません。するとこれらのポケモンは、純粋な「タイプの強さ」が大きなアピールとなって選出されたのだと考えても穿ち過ぎではないでしょう。「くさ」技のダメージが4倍になるという欠点を抱えているものの、他に弱点が一つもないこと、天敵の「くさ」タイプ自体がそこまで強いわけではないこと、そして優秀な「はがね」および「じめん」タイプに対して弱点を突けることが、「みず・じめん」が(PageRank上でも実際の対戦でも)選ばれた理由だと考えられます。

したがって、Google行列の固有ベクトルによって定量化したタイプの強さ、名付けて「PokéRank」、および実際の使用率を鑑みて、本記事における最終的な結論はこうなります。

ポケモンの最強タイプは「みず・じめん」である。

感想

最後までお読みいただきありがとうございました。見切り発車&出たとこ勝負で書き始めましたが、無事自分なりの答えに辿り着くことができてホッとしています。ところで、ぼく自身は、実はポケモンの対戦にそこまで詳しいわけではないです(!)。ご指摘等あればお待ちしております。

さらなる改善に向けていくつかメモを残します。本記事の手法では、「タイプ一致の技しか放てない」ということを暗に仮定した上で考察を進めているため、かくとう技でしか弱点をつくことができない「ノーマル」や、冒頭で述べ本記事の軸を担うはずだった「ノーマル・ゴースト」タイプなどの強さが軽視されています。実際は、そういったタイプのポケモンが豊富なタイプな技を覚えることで、防御に長けながら攻撃もできる優秀なポケモンへと変貌を遂げることも考えられます。防御に関する「強さ」により重きを置くのなら、順位はかなり入れ替わるはずです。
また、実際に存在するポケモンのタイプの分布を勘定に入れることで、さらに実用的な分析が可能になるかもしれません。各々の思う「最強タイプ」を議論するのもまた、こうした対戦ゲームの楽しみです。

参考サイト

おすすめ記事

610
242
11

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
610
242