Steamに登録されている大量のゲームから埋もれている名作を見つけ出す

この記事は[mohikanz Advent Calendar 2018 #2](https://adventar.org/calendars/3057)の18日目です。

# [Steam](https://store.steampowered.com/)とは

Wikipediaより
> Steam（スチーム）は、PCゲーム、PCソフトウェアおよびストリーミングビデオのダウンロード販売とハードウェアの通信販売、デジタル著作権管理、マルチプレイヤーゲームのサポート、ユーザの交流補助を目的としたプラットフォームである。

現在Steamで販売されているゲームは３万本以上存在し、Steam Direct(Steam Greenlight)の導入で販売するハードルも以前と比べて下がっており、販売数は年々増加しています。

プレイ可能なゲームが増えて嬉しい反面、Unityのアセットがそのまま売られるなど、いわゆる糞ゲーも多く登録されることとなっています。

今回はそれらのゲームに埋もれてしまって日の目を見ていない良ゲーを探したいと思います。

# Steam APIを使用してユーザーによるレビュー情報を抜き出す

**2018年12月時点でのやり方です。今後同じ方法は使えなくなる可能性があります。**

## 概要

次の方法を用います。

1. SteamのWebAPIで全ゲームのユーザーのレビューを取得してDBに格納
    - ユーザーはレビュー時にPositive or Negativeを付けることができるので、その情報を使います。
2. 独自の計算式を定義して各ゲームにポイントを付けてランキング化する

### 環境

個人的に使い慣れているという理由で次の環境を使用します。

- C#
- SQLServer


## 全ゲームの取得

[GetAppList](https://partner.steamgames.com/doc/webapi/ISteamApps#GetAppList) を使用します。
これで全てのゲームのIDとタイトルがJSON形式で取得できます。

```:GetAppList
https://api.steampowered.com/ISteamApps/GetAppList/v2/
```

```:結果例
{  
   "applist":{  
      "apps":[  
         {  
            "appid":767330,
            "name":"Indoor Rock Climbing VR"
         },
         {  
            "appid":767340,
            "name":"Minion Forest"
         },
         {  
            "appid":767380,
            "name":"theHunter™: Call of the Wild - New Species 2018"
         }
         ...
      ]
   }
}
```

## 各ゲームの評価の取得

### レビュー取得用APIを使用する

詳細は [ユーザーレビュー - リストの取得](https://partner.steamgames.com/doc/store/getreviews)

例えば次のURLのappidに10を入れれば`Counter-Strike`のレビューをいくつか取得できます。

```:URL例
https://store.steampowered.com/appreviews/{appid}?json=1&start_offset=0
```

```:結果例
{
	"success": 1,
	"query_summary": {
		"num_reviews": 1,
		"review_score": 8,
		"review_score_desc": "非常に好評",
		"total_positive": 64,
		"total_negative": 4,
		"total_reviews": 68
	},
	"reviews": [
		{
			"recommendationid": "xxxx",
			"author": {
				"steamid": "xxxxxx",
				"num_games_owned": 91,
				"num_reviews": 51,
				"playtime_forever": 25,
				"playtime_last_two_weeks": 0,
				"last_played": 1543494362
			},
			"language": "japanese",
			"review": "最高のゲームだ",
			"timestamp_created": 1537804715,
			"timestamp_updated": 1537804715,
			"voted_up": true,
			"votes_up": 1,
			"votes_funny": 0,
			"weighted_vote_score": "0.523809552192687988",
			"comment_count": 0,
			"steam_purchase": true,
			"received_for_free": false,
			"written_during_early_access": false
		}
	]
}
```

デフォルトだと言語や期間によって取得情報が制限されてしまうので、全て取得するには `language=all` や `day_range=9223372036854775807` のオプションが必要になります。
(参考：[Steam API User Reviews Query](https://www.reddit.com/r/devsteam/comments/7qxjho/steam_api_user_reviews_query/))

ユーザーの評価であるPositive と Negative の総数が登録されているので目的は果たせそうですが、
目的を達成するのにより使い勝手が良さそうな後述のAPIが調査過程で見つかりましたので、今回はそちらを使用することとしました。

### ヒストグラム取得用のAPI

[Steamが「レビュー荒らし」に対策、ユーザーレビューにヒストグラム表示が追加](https://www.gamespark.jp/article/2017/09/20/75883.html)

上記ページで紹介されていますが、2017年9月ごろからストアページでヒストグラムが表示されるようになりました。
![CounterStrike_Graph.PNG](https://qiita-image-store.s3.amazonaws.com/0/299916/a909714b-a05c-3085-6eab-135ef3461678.png)

このヒストグラムを表示するためのAPIです。
以下URLでデータが取得できます。

```:取得用URL
https://store.steampowered.com/appreviewhistogram/{appid}
```

```:結果例
{
  "success": 1,
  "results": {
    "start_date": 1288569600,
    "end_date": 1544918400,
    "weeks": [],
    "rollups": [
      {
        "date": 1288569600,
        "recommendations_up": 21,
        "recommendations_down": 0
      },
     ...
    ],
    "rollup_type": "month",
    "recent": [
      {
        "date": 1542412800,
        "recommendations_up": 21,
        "recommendations_down": 1
      },
     ...
    ]
  },
  "count_all_reviews": false
}
```

必要な情報が含まれており、また期間別に分かれていてデータの取捨選択をしながら集計できるため、こちらのAPIを利用しました。

# ランク付けのための計算をする

`GetAppList` で取得した全IDを回して `appreviewhistogram` にアクセスし、全ゲームのPositive, Negative情報を取得・DBに格納できました。
この情報を基に、良ゲーを探します。

**なお、DBを作成したのは12月初旬であり、最近の情報は含まれていません。**

## 試しにPositive,Negativeの総数を利用してみる

単純に考えると、「PositiveがNegativeよりも圧倒的に多いゲーム」は面白いゲームであり、「NegativeがPositiveより圧倒的に多いゲーム」はつまらないゲームと言えそうです。
ということで上記2つのゲームを試しに算出してみました。

- PositiveがNegativeよりも圧倒的に多いゲーム
    - [Counter-Strike: Global Offensive](https://store.steampowered.com/app/730/CounterStrike_Global_Offensive/)
    - <img width="715" alt="Counter-Strike_Reviews.PNG" src="https://qiita-image-store.s3.amazonaws.com/0/299916/0696136c-7dd3-5778-c206-fe601d4fd8c4.png">

- NegativeがPositiveよりも圧倒的に多いゲーム
    - [No Man's Sky](https://store.steampowered.com/app/275850/No_Mans_Sky/)
    - <img width="718" alt="NoMansSky.PNG" src="https://qiita-image-store.s3.amazonaws.com/0/299916/ce998599-e796-97aa-5a78-c85967a1a9fd.png">

`Counter-Strike: Global Offensive` は最近のアップデートで不評がかつてないほど増えていますが、リリースしてから全体的に好評が付いています。
逆に`No Man's Sky`は最近は好評ですが、リリース当時かなり不評であり、それが尾を引いているようです。

上記から、アップデートによって大きく評価は変わるので最近のデータのみを使用したほうが信憑性が上がりそうではあるのですが、
今回の目的は埋もれているゲーム探しであり、最新のレビューがほとんどないものも含めて探したいため、突出したデータの除去のみを検討材料に加えます。

## 計算式を定義する

以下の条件を採用することとしました。

- 基本的な計算式は `Positiveの合計値` - `Negativeの合計値` とする
- 埋もれているゲームを探したいのでレビューが `100件以下` のゲームを対象とする
- 特定期間のみに集中したレビューを弾くため、 `Positive`・`Negative`の最大値はそれぞれ除外する

上記情報を統括したクエリが次のクエリとなります。

```sql:計算用クエリ
SELECT
        r.[AppId]
        , SUM(r.[RecommendationsUp]) - MAX(r.RecommendationsUp) - (SUM(r.RecommendationsDown) - MAX(r.RecommendationsDown)) AS point
        , a.[Name]
    FROM
        [dbo].[ReviewHistograms] AS r
        INNER JOIN [dbo].[AllGames] AS a
            ON a.[AppId] = r.[AppId]
    GROUP BY
        r.[AppId]
        , a.[Name]
    HAVING
        SUM(r.RecommendationsUp) + SUM(r.RecommendationsDown) < 100
        AND COUNT(r.[AppId]) > 10
    ORDER BY
        point DESC
;
```

# 結果

上の計算式で算出したポイントを基にランク付けしました。(DLC等は除外)

| Rank | タイトル | ポイント | 参考画像 |リリース日|
|:-----------:|:------------:|:------------:|:------------:|:------------:|
|1|[Corinne Cross's Dead & Breakfast](https://store.steampowered.com/app/473920)|78|![CorinneCross.jpg](https://qiita-image-store.s3.amazonaws.com/0/299916/830fbe84-5ee5-bc4c-aa8c-f66321973984.jpeg)|2016年8月19日|
|2|[Void Pyramid](https://store.steampowered.com/app/563430)|77|![VoidPyramid.jpg](https://qiita-image-store.s3.amazonaws.com/0/299916/a9bb253e-9e51-1931-dbbe-4775bb678c9e.jpeg)|2016年12月6日|
|3|[Micron](https://store.steampowered.com/app/290380)|76|![Micron.jpg](https://qiita-image-store.s3.amazonaws.com/0/299916/cc276b21-f631-79c5-73f8-67b577b816fc.jpeg)|2014年7月24日|
|3|[Sword of the Samurai](https://store.steampowered.com/app/327950)|76|![SwordOfTheSamurai.jpg](https://qiita-image-store.s3.amazonaws.com/0/299916/0d1582ef-c227-8800-a730-e085f5fc9fa3.jpeg)|1989年1月1日|
|3|[Voidspire Tactics](https://store.steampowered.com/app/415920)|76|![VoidspireTactics.jpg](https://qiita-image-store.s3.amazonaws.com/0/299916/e5a3edc0-376a-2d9b-6c9d-5d6734a5bd0a.jpeg)|2015年11月3日|
|3|[Higurashi When They Cry Hou - Ch.4 Himatsubushi](https://store.steampowered.com/app/526490)|76|![Higurashi.jpg](https://qiita-image-store.s3.amazonaws.com/0/299916/4275142b-e766-58a9-d642-e67260cbcde0.jpeg)|2016年10月14日|
|3|[RetroMaze](https://store.steampowered.com/app/852110)|76|![RetroMaze.jpg](https://qiita-image-store.s3.amazonaws.com/0/299916/11cf88c6-4b4f-08e5-b87b-569ba1bcf25b.jpeg)|2018年5月22日|
|8|[Zombie Bowl-O-Rama](https://store.steampowered.com/app/32160)|74|![ZombieBowl.jpg](https://qiita-image-store.s3.amazonaws.com/0/299916/7d00e3d3-b856-9df6-2860-47deb7df0de9.jpeg)|2009年10月16日|
|8|[Copy Kitty](https://store.steampowered.com/app/349250)|74|![CopyKitty.jpg](https://qiita-image-store.s3.amazonaws.com/0/299916/d493895d-6d1b-5aec-aefc-cc8992add3e0.jpeg)|2018年4月20日|

ひぐらし以外は聞いたことがないゲームばかりですね。
1位のグラフを見ると1箇所が大きく突出しているわけではなく、比較的理想的なグラフとなっています。
![CorinneCross_Graph.PNG](https://qiita-image-store.s3.amazonaws.com/0/299916/a8b6f4f0-6cba-934c-9d95-8414ab22068a.png)

## 終わりに

せっかくなので、1位の`Corinne Cross's Dead & Breakfast` を買ってプレイしてみました。(投稿時点未クリア)

あらすじを簡単に説明すると、旧友が亡くなり、その母親もショックで体調を崩して入院してしまったため、
代わりに1週間家を預かって生活する話です。
お見舞いしたり、お花を育てて売ってお金を稼いだり、料理をしたり、夜には幽霊と交流したりといった感じで、
ほのぼのADV系が好きな人には合いそうな感触です。
日本語訳はされていないですが、簡単な英語が多いので、今のところ英語ハードな私でもプレイはできています。

ウィンターセール(2018年は12/20～)も近いですし、興味のあるものがあったら購入対象として考えてみてはいかがでしょうか。
私的には1位のゲームも良かったですが、8位の`Copy Kitty`が「簡単に言うとカービィ＋ロックマン」(レビューより抜粋)とのことであり、興味をそそられますね。