HSPTV部門でのランキング機能活用術

[今年(2016年)のHSPプログラムコンテスト](http://hsp.tv/contest2016/)も入賞作品が決まり、ひと段落着いたところでHSPTV部門での個人的なテクニックを書きたいと思います。
実際の内容は、投稿したHSPTVプログラムのソースとその説明です。
なお、本記事は[Hot Soup Processor Advent Calendar 2016](http://qiita.com/advent-calendar/2016/hsp)の12日目の記事です。

#HSPTV部門とは？
HSPプログラムコンテストのうち、HSPTVブラウザ上で動作するプログラムを投稿する部門です。
投稿には[制限事項](http://hsp.tv/contest2016/cntst_tvregulation.html)があり、この制限範囲内に収まるように工夫する必要があるわけです。

- start.axのファイルサイズを6000バイト以下にすること。
- 指定以外の素材（画像、音声など）を用意しない。
- OBAQ.DLL以外の拡張プラグインを使わない。など……

また、[hsptv.as](http://www.onionsoft.net/hsp/v33/doclib/hsptv_api.txt)を用いてランキング機能を利用することができます（リンク先は2011年の情報です）。
ランキングに含まれるコメント情報を、本来のコメントとしてではなくデータ領域として利用することで、疑似対戦型ゲームやチャットアプリを実現できます。

#ソースファイル
今年のHSPプログラムコンテストに投稿したプログラムを貼ります。

- 「[雑貨屋競争](http://dev.onionsoft.net/seed/info.ax?id=1216)」（[ソース](http://hato.mydns.jp/hsp2016/zakkaya_src.zip)）
ランキング機能を利用した経営シミュレーション(？)。
「いいね★」によってお店を評価しています。
その他お店の編集・公開機能など。

ソースファイルを見ながら、ランキング機能の一風変わった活用方法について書いていきます。

# ランキング機能の仕様
参考ページ
- [HSPTVにおけるランキング機能の仕様](http://hsp.tv/play/pforum.php?mode=all&num=76792)
- [HSPTVにおけるランキング機能の仕様2](http://hsp.tv/play/pforum.php?mode=all&num=76852)

ランキング機能には、

- ユーザ名
- スコア
- コメント

の情報を30位分記録できます。
スコアが大きい順に順位付けされ、31位以下になったデータは自動的に削除されます。
コメントには半角255文字までの文字列を記録でき、主要なデータ記録源になります。
ただし、それぞれには細かな仕様があります。

## ユーザ名
半角20文字までのユーザ名が自動的に登録されます。
ユーザ名はユニーク・排他的なものではありません。
つまり、**同じ名前のユーザが複数人いる**可能性があります。
ちなみに、HSPTVブラウザでのデフォルト設定は"guest"です。

## スコア
順位付けするための整数値です。
ランキング機能を本来のランキングとして使用せず、変わったことをする場合は、基本的にはスコアの記録領域を使用できません。
また、ランキングのAPIには任意の順位のデータを書き換える機能があります。

## コメント
半角255文字までの記録領域です。
ソース「雑貨屋競争」では、お店の情報を文字列に変換して、コメント領域に記録しています。
ドラクエの「復活の呪文」のような手法ですね。
半角カナなどの一部文字は記録できず、場合によってはランキングデータ全体の整合性を破壊しかねないので注意してください。

また、HSPTVに投稿する際に用いるhsptv.asでは255文字に対応していますが、
**hsptvapp.asでは半角64文字まで**しか対応していません。
hsptvapp.asはサーバーの管理を行うのに必要なモジュールで、変わったことをするには不可欠になってきます。
ちなみに、ドラクエⅡの復活の呪文の最大長さは52文字だそうです。
文字を写し間違えた苦い思い出があります。

## その他
- 通信を頻発させて、ランキングサーバーに高負荷を与えてはいけません。
[HSPTV APIリファレンスマニュアル](http://www.onionsoft.net/hsp/v33/doclib/hsptv_api.txt)によると、**1回のアクセス間隔は5秒以上が目安**です。
リアルタイム通信を行うことは難しく、またユーザインタフェースに配慮が必要になる場合があります。
例えば、チャットアプリでは短時間の連続投稿に制限をかける必要があります。

- HSPTVサーバーによる動作環境と、ローカルなデバッグ環境とでは、厳密には動作が異なる部分があります。

- ランキングデータは、30位分まとめて取得します。自分のデータのみを取得するような、パーミッション構造はありません。

- ランキングデータが恒久的に保持される保証はありません。データが消失する可能性はあります。

- データ送信時に、初回のみ「現在の情報をHSPTVサーバーに反映させていいですか？」と承諾を確認します。**一度承諾を得られると、以降は確認なしでサーバー送信を行います**。

# ランキングデータの設計
雑貨屋競争でのランキングデータの割り当てを次に示します。
**コメント領域を半角64文字として設計**しています。

1~8位: 内部データ
9位: 来客数
10位: いいね★の数
11~14位: 内部データ
15位: 送信承諾確認&ユーザ名取得
16~30位: 各お店データ

## 内部データ
内部データは、ゲームの動作に必要なデータです。
例えば、町内マップのデータや、ダイアログの文字列（「空きテナント　クリックで新しく開店します。」など）が含まれています。
ゲームの動作に必要なデータをランキングサーバー上に持つのはHSPTV部門の[制限事項](http://hsp.tv/contest2016/cntst_tvregulation.html)に違反するかと考えました。
しかし、コンテスト事務局に問い合わせたところ、私の場合は承諾をいただきました。

## 送信承諾確認&ユーザ名取得
「送信承諾確認&ユーザ名取得」は、システムの整合性を保つために必要となります。
ランキングデータ送信時には、初回のみ確認ダイアログが表示されます。
ゲームのプレイ中にデータ送信を断られると、雑貨屋競争のシステム整合性が崩れてしまいます。
しかし、確認ダイアログの「はい」or「いいえ」を検出することはできないようです。

そこで、ゲーム起動時に「送信承諾確認&ユーザ名取得」用のダミーデータを送信します。
ダミーデータのコメントには乱数を埋め込みます。
データの送信後（承諾を得られて送信されるかは分からない）、ランキングデータを更新して**ダミーデータが一致するかをチェック**します。
これによってデータ送信の承諾を確認し、ダミーデータが一致しなかった場合はゲームを終了します。
また、同時にユーザ名を取得することができます。

##各お店データ
15位分をお店データに割り当てました。
お店データのフォーマットは、

- 55文字分: お店の内装
- 1文字分: 模様替え
- 6文字分: 商品

の合計62文字となっています。
10x11マスのお店の情報を詰め込むために、少し入り組んだことをしています。

# データ詰め込み術
お店データを文字列に変換して、コメントに埋め込みます。
64種類の文字を使用し、1文字あたり6ビット、お店データ全体で64文字384ビットの情報を詰め込みます。

```hsp:senddata.hsp
sdim send_comment
	
repeat 11,1	//店編集可能部
	cnt2 = cnt
	repeat 5
		w storemap(cnt2,cnt*2 + 4)*8 + storemap(cnt2,cnt*2 + 5)
	loop
loop

w design

repeat 6
	w object(cnt)
loop

gosub *hsptv_update
if username(store_index) == own_username{
	hsptv_up store_index,send_comment,0x1000
	//dialog strlen(send_comment)
	//dialog send_comment
}
return
//(中略)
#deffunc w int w_data	//send_comment専用
	send_comment += strf("%c",w_data + '0')
	return
```

これは、お店データのコメントを生成してデータ送信するプログラム部分です。
お店の内装1マスあたりには、次の8通りのオブジェクトを配置できます。

- ブランク（何も置かない）
- 壁
- 商品1~6

よって、1マスあたり3ビットで表現でき、1文字で2マス、55文字で10x11=110マスの内装を表現しています。
商品には「食品、ジュース、文房具、…」の32種類を用意しましたが、コメントの64文字制限のため6種類を選択することにしました。
お店自体が小さいコンビニサイズなので、6種類置ければ十分かと思います。
また、選んだ商品の情報を新たに6文字分確保しています。

> 食品、ジュース、文房具、…（32種類）
> ↓
> ブランク、壁、商品1~6(8種類)
> ↓
> お店の内装10x11マス

# オーナーの排他処理
作ったお店は、**基本的には**オーナー（作った本人）しか編集できません。
これは、お店の「ユーザ名」情報と「送信承諾確認&ユーザ名取得」で取得したユーザ名を比較することで実現できます。

「基本的には」を強調したのは、……察してください。
実は、この排他処理には抜け穴があります。
start.axの容量の都合により、セキュリティ対策を行っていません。
抜け穴のヒントになってしまいますが、特別に「guest」ユーザに対しては注意書きのダイアログが表示されます。

# いいね★ランキング
お店にはいいね★の数も記録する必要があるのですが、これは専用のコメント領域に記録します。
各お店データには記録できません。
いいね★はオーナー以外のお客さんがつけるため、各お店データを編集できないからです。
編集したものをデータ送信すると、そのユーザ名が書き換わり、オーナー権限も移動してしまいます。

> 9位: お店1の来客数、お店2の来客数、…、お店15の来客数
> 10位: お店1のいいね★、お店2のいいね★、…、お店15のいいね★

各お店のいいね★数は、4文字で表現しています。
1文字あたり6ビット、4文字で24ビット（0~16,777,215）表現でき、十分な量を確保できています。
全てのお店の来客数といいね★を表現するのは4x2x15=120文字となり、2位分のコメント領域を使用しています。

各お店で共通のコメント領域を使うため、通信の衝突が生じる場合があります。
いいね★の数を更新するには、次の手順で通信します。

(1) ランキングデータを取得
(2) 該当するお店の、来客数といいね★の数をインクリメント（+1）
(2) 来客数といいね★のデータを送信
(2回または3回の通信が発生)

完全な衝突回避はできませんが、衝突が発生してもいいね★が0個に初期化されるわけではありません。
また、通信頻度の都合上、これ以上厳密な通信は難しいです。
ただ、次のようなデータフォーマットにすれば、より少ない通信回数で更新可能でした。

> 9位: {お店1の来客数、お店1のいいね★}、…、{お店8の来客数、お店8のいいね★}
> 10位: {お店9の来客数、お店9のいいね★}、…、{お店15の来客数、お店15のいいね★}
(2回の通信が発生)

# ランキング下位のお店の削除
新しいオーナーを受け入れるために、既存のお店を閉店させなければなりません。
これが「雑貨屋競争」のネーミングの由来なのですが、いいね★によるランキング付けによって閉店する確率が変わります。
ランキング下位ほど閉店してしまう確率を高く設定してありますが、**閉店チェックは町内マップを表示するたび**に行っています。
町内マップを表示するとき、普段よりも長くフリーズすることがありますが、これはお店データを削除（空の文字列で上書き）しているためです。
よって、ゲームを起動してくれるプレイヤーが多くいらっしゃるほど、既存のお店を閉店して新規プレイヤーを受け入れる確率が高くなる仕組みになっています。

#バージョンアップデート
ゲームを公開した後、ランキングデータの仕様をアップデートする可能性があります。
実は、雑貨屋競争の初期バージョンではいいね★を導入しておらず、代わりに「累計収支」を使用していました。
儲かっているお店ほど生存競争に勝てるというシステムだったわけです。
累計収支からいいね★にアップデートするには、既存のお店のデータを書き換える必要があります。
これには、**hsptvapp.asを用いて外部からサーバー管理を行う**必要があります。
ただし、hsptvapp.asは半角64文字までのコメントしか扱えないため、初版公開時からこのことを考慮しておく必要があります。

累計収支からいいね★へのアップデートは、次の手順で行いました。

(1) 新バージョンのstart.axを投稿。
(2) 定期的にランキングデータをバックアップ。
(3) 新バージョンがHSPTVブラウザ上に配信されたら、バックアップデータをもとに**直ちに**書き換え開始。
(4) 累計収支をもとにいいね★の数を割り当て、新フォーマットのランキングデータを作成。
(5) 新フォーマットのランキングデータを送信。

新バージョンで旧フォーマットのランキングデータを操作すると、データが破壊される可能性があったため、バックアップデータをもとに新フォーマットを作成しました。
新フォーマットへの変換は手動でやりましたが、プログラムで自動化すると効率化できます。
また、雑貨屋競争では実装しませんでしたが、**サーバー管理中はゲームを実行できないように指示する**ことは可能だと思われます。
ゲーム停止フラグをランキングデータに埋め込み、ランキングデータを取得するたびにチェックすればよい筈です（確認はしていません）。
また、フラグはhsptvapp.asで操作できます。
なお、**hsptvapp.asでは任意のユーザ名でランキングデータを送信可能**です。

HSPTV部門のみでは問題ないことですが、雑貨屋競争では[EXE版](http://dev.onionsoft.net/seed/info.ax?id=1253)を用意しています。
ファイルサイズなどの制限がないため、ビジュアル面や補助機能などで拡張が行われています。
しかし、HSPTVプログラムとは違って**EXE版は旧バージョンが残ります**。
つまり、旧プログラムで新ランキングデータを操作しないような機構を設ける必要があります。
これは、初版公開時から考えておく必要があります。
雑貨屋競争EXE版では、プログラムとランキングサーバーにバージョン情報（"2.0 "など）を埋め込み、ゲーム起動時のサーバー通信でバージョンが一致するかチェックします。
ランキングデータを新フォーマットにアップデートするたびに、ランキングサーバーのバージョン情報も更新すればよいわけです。

#おわりに
[今年(2016年)のHSPプログラムコンテスト](http://hsp.tv/contest2016/)では、変わった使い方をしてランキング機能を活用してみました。
しかし、その分**HSPTV部門の制限事項、サーバーの仕様、プレイヤーの排他処理、サーバーの負荷、バージョンアップデートの対応**などを念入りにチェックする必要がありました。
変わったことをする分リスクも大きくなるため、注意が必要です。
しかし、限られた機能をいかにフル活用するかが、HSPTV部門の醍醐味の1つだと思います。