HSP
構造体

HSPにおける構造体機能の代替


概要

当記事はHSP掲示板で返信したモジュール型変数のモジュール変数へ簡単にアクセスする方法に書いた内容の焼き直しとなる。最近新たなネタも知ったので書き直すこととした。

HSPではC言語の構造体のように変数をまとめて手軽に扱う機能が標準には存在しない。そこで、この記事では考えられる構造体の代替策について紹介する。なお、構造体を使用する場合は同じ構成のオブジェクトを多数必要とするケースが多いため、構造体を配列内で扱うことを想定した。

まずは想定するプログラムをC++で書いたものを記載する。


C++

#include <stdio.h>

#include <string>

struct Player{
std::string name;
int hp;
int mp;
int atk;
int def;
int mgc;
int spd;
};

void profile(Player* self){
printf("【%s】\n",self->name.c_str());
printf("HP:%3d ATK:%3d MGC:%3d\n",self->hp,self->atk,self->mgc);
printf("MP:%3d DEF:%3d SPD:%3d\n",self->mp,self->def,self->spd);
printf("\n");
}

int main(){
Player pl[]={
{"勇者" ,100,100,100,100,100,100},
{"魔法使い", 70,200, 50, 70,150, 90},
{"メイン盾",200,150, 70,180, 10,100},
{"ビースト",120, 30,150,120, 10,150}
};

for(auto v:pl){
profile(&v);
}
return 0;
}



HSPの実装


1.モジュール変数(アクセサ)

#runtime "hsp3cl"


#module Player __name,__hp,__mp,__atk,__def,__mgc,__spd
#modcfunc name
return __name
#modcfunc hp
return __hp
#modcfunc mp
return __mp
#modcfunc atk
return __atk
#modcfunc def
return __def
#modcfunc mgc
return __mgc
#modcfunc spd
return __spd

#modinit str _name,int _hp,int _mp,int _atk,int _def,int _mgc,int _spd
__name=_name
__hp=_hp
__mp=_mp
__atk=_atk
__def=_def
__mgc=_mgc
__spd=_spd
return
#global

#module
#deffunc profile var self
mes "【"+name(self)+"】"
mes strf("HP:%3d ATK:%3d MGC:%3d",hp(self),atk(self),mgc(self))
mes strf("MP:%3d DEF:%3d SPD:%3d",mp(self),def(self),spd(self))
mes ""
return
#global

dimtype pl,5,4

newmod pl,Player,"勇者" ,100,100,100,100,100,100
newmod pl,Player,"魔法使い", 70,200, 50, 70,150, 90
newmod pl,Player,"メイン盾",200,150, 70,180, 10,100
newmod pl,Player,"ビースト",120, 30,150,120, 10,150

foreach pl
profile pl.cnt
loop

モジュール変数機能は構造体の代わりとして扱うにはHSPの中では最も一般的なものだと思う。ただし、変数アクセスにメンバ関数が必須になることなど、機能面としてはクラスの方が近い。モジュール変数へのアクセスは直接行えない(多言語でいうprivate)ため、外部からあれこれするにはアクセサが必要である。モジュール型変数の定義命令であるnewmodは同じ変数名で定義するたびに配列に積み上げられていく形式であるため、複数のオブジェクトの管理を行いたい場合はとても便利だと思う。欠点としてはモジュール変数を外部に公開する際、関数形式とならざるを得ないため関数名の衝突が起きやすくなることか。

参考:HSPでモジュール型変数入門


2.モジュール変数(ラベルジャンプ)

#runtime "hsp3cl"


#module Player name,hp,mp,atk,def,mgc,spd
#modinit str _name,int _hp,int _mp,int _atk,int _def,int _mgc,int _spd
name=_name
hp=_hp
mp=_mp
atk=_atk
def=_def
mgc=_mgc
spd=_spd
return

#modfunc with label l
gosub l:return
#global

#module
#deffunc profile var self
if 0{*with_l
mes "【"+name@Player+"】"
mes strf("HP:%3d ATK:%3d MGC:%3d",hp@Player,atk@Player,mgc@Player)
mes strf("MP:%3d DEF:%3d SPD:%3d",mp@Player,def@Player,spd@Player)
mes ""
return}
with self,*with_l
return
#global

dimtype pl,5,4

newmod pl,Player,"勇者" ,100,100,100,100,100,100
newmod pl,Player,"魔法使い", 70,200, 50, 70,150, 90
newmod pl,Player,"メイン盾",200,150, 70,180, 10,100
newmod pl,Player,"ビースト",120, 30,150,120, 10,150

foreach pl
profile pl.cnt
loop

裏技的な手法となるが、モジュール命令/関数内でジャンプ命令(goto/gosub)を実行するとジャンプ先でもモジュール変数の取得及び代入が可能となる。これを利用して手元に用意したラベルをモジュール命令に投げ込んでコールバックしてもらうことで値の読み書きを行う。

参考:HSPでgetter・setterを使わないでモジュール変数にアクセスする


3.文字列配列&列挙体

#runtime "hsp3cl"


#enum global name=0
#enum global hp
#enum global mp
#enum global atk
#enum global def
#enum global mgc
#enum global spd
#enum global PlayerSize

#module
#deffunc profile array self,int i
mes "【"+self(name,i)+"】"
mes strf("HP:%3d ATK:%3d MGC:%3d",self(hp,i),self(atk,i),self(mgc,i))
mes strf("MP:%3d DEF:%3d SPD:%3d",self(mp,i),self(def,i),self(spd,i))
mes ""
return
#global

sdim pl,,PlayerSize,4

pl.0.0="勇者" ,"100","100","100","100","100","100"
pl.0.1="魔法使い", "70","200", "50", "70","150", "90"
pl.0.2="メイン盾","200","150", "70","180", "10","100"
pl.0.3="ビースト","120", "30","150","120", "10","150"

repeat length2(pl)
profile pl,cnt
loop

配列を構造体の代わりに使うというシンプル極まりない方法。0からの連番に割り当てたいメンバー名を割り当てていくことで構造体のように見せかける。配列であるのでなるので型を統一しなければならないのがネックとなる。本例では汎用性の高い文字列配列に必要に応じて型変換することで使用することとしている。


4.型別配列&列挙体

#runtime "hsp3cl"


#enum global name=0
#enum global PlayerSize_s
#enum global hp=0
#enum global mp
#enum global atk
#enum global def
#enum global mgc
#enum global spd
#enum global PlayerSize_i

#define global newPlayer(%1,%2,%3=0,%4,%5,%6,%7,%8,%9,%10) \
%1(name,%3)=%4 :\
%2(hp,%3)=%5 :\
%2(mp,%3)=%6 :\
%2(atk,%3)=%7 :\
%2(def,%3)=%8 :\
%2(mgc,%3)=%9 :\
%2(spd,%3)=%10

#module profilem
#deffunc profile array self_s,array self_i,int i
mes "【"+self_s(name,i)+"】"
mes strf("HP:%3d ATK:%3d MGC:%3d",self_i(hp,i),self_i(atk,i),self_i(mgc,i))
mes strf("MP:%3d DEF:%3d SPD:%3d",self_i(mp,i),self_i(def,i),self_i(spd,i))
mes ""
return
#global

sdim pl_s,,PlayerSize_s,4
dim pl_i,PlayerSize_i,4

newPlayer pl_s,pl_i,0,"勇者" ,100,100,100,100,100,100
newPlayer pl_s,pl_i,1,"魔法使い", 70,200, 50, 70,150, 90
newPlayer pl_s,pl_i,2,"メイン盾",200,150, 70,180, 10,100
newPlayer pl_s,pl_i,3,"ビースト",120, 30,150,120, 10,150

repeat length2(pl_s)
profile pl_s,pl_i,cnt
loop

上記の型を統一する代わりに型ごとに配列を増やす形式。一つにまとまらなくなるので場合によっては引数が酷いことになる。


5.#define(マクロ)

#runtime "hsp3cl"


#define global ctype newPlayer(%1,%2=0,%3,%4,%5,%6,%7,%8,%9) \
%1_name(%2)=%3 :\
%1_hp(%2)=%4 :\
%1_mp(%2)=%5 :\
%1_atk(%2)=%6 :\
%1_def(%2)=%7 :\
%1_mgc(%2)=%8 :\
%1_spd(%2)=%9

#module
#define global ctype profile(%1,%2) \
mes "【"+%1_name.%2+"】" :\
mes strf("HP:%%3d ATK:%%3d MGC:%%3d",%1_hp(%2),%1_atk(%2),%1_mgc(%2)) :\
mes strf("MP:%%3d DEF:%%3d SPD:%%3d",%1_mp(%2),%1_def(%2),%1_spd(%2)) :\
mes ""
#global

newPlayer(pl,0,"勇者" ,100,100,100,100,100,100)
newPlayer(pl,1,"魔法使い", 70,200, 50, 70,150, 90)
newPlayer(pl,2,"メイン盾",200,150, 70,180, 10,100)
newPlayer(pl,3,"ビースト",120, 30,150,120, 10,150)

foreach pl_name
profile(pl,cnt)
loop

マクロ機能で基準の名前にメンバー名を結合して変数定義を増産する。そもそもまとまったオブジェクトなど存在せず、変数名の一部共有のみの関係性しか持たない変数の集まりになるので、まとまって値を扱うためには#defineを使う他ない。その仕組み上#defineマクロを多用することとなるため取り回しはほかのどの方法よりも悪い。なお、名前を結合する#defineを使用するときはctypeにしなくてはならない。

参考:define構造体


6.dupptr

#runtime "hsp3cl"


#const global str_t 2
#const global double_t 3
#const global int_t 4
#define global ctype Player(%1,%2) %tPlayer \
%i=varptr(%1(%2)) :\
dupptr %1_name,%p ,32,str_t :\
dupptr %1_hp ,%p+32, 4,int_t :\
dupptr %1_mp ,%p+36, 4,int_t :\
dupptr %1_atk ,%p+40, 4,int_t :\
dupptr %1_def ,%p+44, 4,int_t :\
dupptr %1_mgc ,%p+48, 4,int_t :\
dupptr %1_spd ,%p+52, 4,int_t :\
%o0
#const global PlayerSize 56
#define global ctype newPlayer(%1,%2=0,%3,%4,%5,%6,%7,%8,%9) \
Player(%1,%2) :\
%1_name=%3 :\
%1_hp=%4 :\
%1_mp=%5 :\
%1_atk=%6 :\
%1_def=%7 :\
%1_mgc=%8 :\
%1_spd=%9

#module
#deffunc profile array self,int i
Player(self,i)
mes "【"+self_name+"】"
mes strf("HP:%3d ATK:%3d MGC:%3d",self_hp,self_atk,self_mgc)
mes strf("MP:%3d DEF:%3d SPD:%3d",self_mp,self_def,self_spd)
mes ""
return
#global

sdim pl,PlayerSize,4

newPlayer(pl,0,"勇者" ,100,100,100,100,100,100)
newPlayer(pl,1,"魔法使い", 70,200, 50, 70,150, 90)
newPlayer(pl,2,"メイン盾",200,150, 70,180, 10,100)
newPlayer(pl,3,"ビースト",120, 30,150,120, 10,150)

foreach pl
profile pl,cnt
loop

他の記事で最近知った新ネタ。文字列変数をサイズ指定してその中に他変数のポインタアドレスを指定して敷き詰めることで一つの変数として扱わせるというもの。ポインタの知識が必要で定義の設計は最も複雑であるが、変数の実態としてはただの文字列となるため関数間の取り回しは最も良い。ただし、異なるスコープで変数を使用する場合はポインタを定義しなおす必要がある。

参考:【上級者向け】プロのHSPerになりたけりゃdupptrを使え

参考:【HSP】dupptrを使った構造体のような何か


あとがき

dupptrが想像以上に使い勝手がいいかなって印象。内部に配列とか複雑な型を持たすのは難しいけど、コンパクトな設計とする時ならモジュール変数の代わりに使うのもありだと感じた。何より仕組みとして何が起きているかモジュール変数よりも明確なのでいくらか高速化にも期待できそうかなと思う。