はじめに
hsptvでは,HSPプログラミングコンテストを毎年開催しています.
その中には「hsptv部門」がありますが,この部門に投稿するには,「start.ax」を6000byte以下に収めなければなりません.
投稿した際に苦労したので,記事としてまとめたいと思います.
…という記事を書こうとおもったら先客がいらっしゃったのでリンクを貼ります.
HSPTV部門でのstart.ax削減術
https://qiita.com/hato/items/7b720e87a408a6ab147a
start.ax のダイエット
https://sites.google.com/site/hspsource/diet
HSP: オブジェクトファイルのサイズを小さく
http://freeh.minim.ne.jp/minimini/develop/hspcpt.html
実行ファイルのサイズを小さくする
http://lhsp.s206.xrea.com/hsp_tips5.html
なるべく内容が重ならないようにします…
追記:2019年以降からhsptv部門が消えた。辛い。
注意事項
可読性が酷く落ちます
-
共通部分を纏める
共通している部分があったら,変数でまとめてみましょう.以下の例では4byte減ります.
c = cnt*3
noteget rscore, c ;cnt*3
noteget ruser, c+1 ;cnt*3+1
noteget rcomm, c+2 ;cnt*3+2
配列をまとめてセットする
dim v, 4
v.0 = 3
v.1 = 2
v.2 = 1
v.3 = 0
と書くと238byteですが,
dim v, 4
v = 3,2,1,0
と書くと166byteになります.
この例のような,1次元配列の場合はdim
命令も要らないので,154byteまで減らせます.
hsvcolorを思い出す
HSV形式で色を指定できるhsvcolor
という命令があります.
HSV形式では,「色相H」「彩度S」「明度V」で色を表現します.
hsvcolor
でパラメータを省略した場合はcolor
と同様に0が指定されます.
HまたはSまたはV成分が0の時,オブジェクトファイルサイズを短縮出来ます.
例えばグレーRGB=(x,x,x)
の場合,color x,x,x
となりますが,
HSV=(0,0,x)
で表現すると hsvcolor ,,x
と書くことが出来ます.
マクロの挙動を理解する
switch
やfor
等はHSPインタプリタの命令では無くマクロによって記述された命令です.
for i, 1, 10, 1
mes "i="+i
next
例えば,上のコードは,以下のように展開されます.
#cmpopt ppout 1
を加えるとファイルhsptmp.i
が出力されるので,
このファイルの中身からどのように展開されたか確認することが出来ます.
i= 1:*_for_0000:exgoto@hsp i, 1, 10,*_break_0000
mes@hsp "i="+i
*_continue_0000: i+= 1:goto@hsp *_for_0000:*_break_0000
大量の命令が出てきました.
複雑なマクロを多用すると,ファイルサイズ短縮のチャンスを見逃してしまうかもしれません.
私は単純そうなマクロにも気をつけています.
例えば,dir_tv
はdirinfo(5)
に展開されます.命令だけでなくパラメータも1つ隠れていますね.
mes dir_tv+"sozai5.jpg
mes dir_tv+"mini_kei.png"
mes dir_tv+"mini_tamane.png"
mes dir_tv+"tamadot.png"
「共通部分を纏める」と同様に,一時変数_dir_tv
に放り込んでから使うようにすることで,
上の例では294byteから270byteに短縮します.
_dir_tv = dir_tv
mes _dir_tv+"sozai5.jpg
mes _dir_tv+"mini_kei.png"
mes _dir_tv+"mini_tamane.png"
mes _dir_tv+"tamadot.png"
浮動小数点数(double)を極力避ける
整数(int)は4byte,浮動小数点数(double)は8byteです.整数に書き換えると4byte減ります.
例えば,次の「たまねちゃんが横倒しの状態から始まり3/4回転するアニメーション」のコードを見てみます.
celput
の引数の浮動小数点はいくつまで減らせるでしょうか?
M_PI
が円周率(もちろん浮動小数点数)のマクロであることも忘れずに.
# const PATTERN_WID 1
celload dir_tv+"tamadot.png",SOZAI_WID
celdiv PATTERN_WID, 64, 64, 32, 32
repeat 100
redraw 0: boxf
pos 100,100
celput PATTERN_WID, 31, 2,2, M_PI*2.0/4.0 + (M_PI*2.0*(3.0/4.0)/100)*cnt
redraw: wait 1
loop
答えは0個.円周率どこいったのー!
celput PATTERN_WID, 31, 2,2, 573204 + 659213*cnt
以下,求め方(PI*2/4
が何故573204
になるのか)について書きます.
要は,x \ (PI*2)
が目標値に近くなるような整数x
を求めたいです.(\
は剰余)
ラジアン以外の数学の知識は必要ありません.ちょっとだけコーディングするだけです.
- 2つの角度の差を求める関数
diffangle(double ang1, double ang2)
を作る. -
0
から999999
の整数の中で,diffangle(x\(PI*2), 目標値)
が最小となるような値を全探索する.
HSPで書いたコードは以下のようになります.
# module
#defcfunc diffangle double ang1, double ang2
mod = M_PI*2
dim a,3
a.0 = absf((ang1 - ang2 - mod)\mod)
a.1 = absf((ang1 - ang2 )\mod)
a.2 = absf((ang1 - ang2 + mod)\mod)
sortval a
return a.0
# global
target = M_PI/2
bestx = 0
bestdiff = 999
repeat 1000000
x = cnt
d = diffangle(target, x)
if (d < bestdiff){
bestdiff = d
bestx = x
}
loop
mes "answer: "+bestx
mes "diff: "+strf("%e",bestdiff)
先程の573204
が求まります.
モジュール展開
ランキング機能を用いる際,hsptv.as
をインクルードすると思います.
# ifndef __hsptv__
# define __hsptv__
# runtime "hsptv"
# regcmd 18
# cmd hsptv_send $00
# module hsptv
# define global HSPTV_RANK_MAX 30
# deffunc hsptv_up int _p1, str _p2, int _p3
buf=""
hsptv_send buf,_p1,_p2,_p3
return
# deffunc hsptv_getrank var _p1, var _p2, var _p3, int _p4
notesel buf
i=_p4*3
noteget _p2, i
_p1=0+_p2
noteget _p2, i+1
noteget _p3, i+2
noteunsel
return
# global
# endif
本当に必要なのは,次の3行だけです.
それ以外の部分は何のためにあるのかというと,hsptvの機能を分かりやすく安全に使うためのwrapperです.
# runtime "hsptv"
# regcmd 18
# cmd hsptv_send $00
hsptv_up
命令,hsptv_getrank
命令が5回10回現れないならば,#include "hsptv.as"
せずに,直接埋め込めこむことで#deffunc
分のオブジェクトファイルサイズを削減出来ます.
次の sample/hsptv/hsptv_test.hsp
を例にあげてみます.start.ax
のサイズは944byteでした.
# include "hsptv.as"
score=1000
sdim comm,64
mes "HSPTVデータの更新登録テスト"
hsptv_up -1,"" ; 最初に情報を更新しておく
gosub *update ; ランキング情報の表示
pos 500,32:objsize 120,24
mes "スコア"
input score
mes "コメント"
input comm
button "更新",*send
button "終了",*ok
dialog dirinfo(0)
stop
*send
hsptv_up score, comm
gosub *update
stop
*ok
end
*update
color 255,255,255
boxf 0,32,500,480 ; 背景をクリア
color 0,0,0
pos 0,32
repeat 10 ; 上位10位のみ表示
hsptv_getrank rscore,ruser,rcomm,cnt ; 情報を取得する
rank=cnt+1
mes "#"+rank+":"+rscore+"("+ruser+") "
mes " コメント:"+rcomm
loop
return
#include "hsptv.as"
の代わりに先程の3行を埋め込みます.
hsptv_send
にはバッファが必要なので,notesel buf
で宣言します.1
hsptv_up
をhsptv_send
に,hsptv_getrank
をnoteget
に書き換えます.
結果,start.ax
のサイズは228byte減少し,716byteになりました.
# runtime "hsptv" ; hsptv.as
# regcmd 18 ;
# cmd hsptv_send $00 ;
notesel buf ; hsptv_send用バッファ
score=1000
sdim comm,64
mes "HSPTVデータの更新登録テスト"
hsptv_send buf,-1,"" ; 情報更新のhsptv_upをhsptv_sendに書き換え
gosub *update
pos 500,32:objsize 120,24
mes "スコア"
input score
mes "コメント"
input comm
button "更新",*send
button "終了",*ok
stop
*send
hsptv_send buf,score,comm ; 情報更新のhsptv_upをhsptv_sendに書き換え
gosub *update
stop
*ok
end
*update
color 255,255,255
boxf 0,32,500,480
color 0,0,0
pos 0,32
repeat 10
noteget rscore, cnt*3 ; hsptv_getrankをnotegetに書き換え
noteget ruser, cnt*3+1 ;
noteget rcomm, cnt*3+2 ;
rank=cnt+1
mes "#"+rank+":"+rscore+"("+ruser+") "
mes " コメント:"+rcomm
loop
return
おまけ.もう少し頑張ってみました.
サブルーチンの移動やstrf
の導入等の改善を行って614byteです.
スパゲティ感.
# runtime "hsptv" ; hsptv.as
# regcmd 18 ;
# cmd hsptv_send $00 ;
notesel buf
score=1000
comm=""
mes "HSPTVデータの更新登録テスト"
hsptv_send buf,-1
pos 500,32:objsize 120,24
mes "スコア"
input score
mes "コメント"
input comm
button "更新",*send
button "終了",*ok
*update
hsvcolor ,,255
boxf
color
pos 0,32
repeat 10
c3 = cnt*3
noteget rscore, c3
noteget ruser, c3+1
noteget rcomm, c3+2
mes strf("#%d:%s(%s)",cnt+1,rscore, ruser)
mes " コメント:"+rcomm
loop
stop
*send
hsptv_send buf,score,comm
goto *update
*ok
end
-
hsptv_test.hsp
ではnotesel
は使われていないので,buf
の為の1度だけで済みます.別の場所でnotesel
が使われている場合はこの限りではありません. ↩