AX

電霊ログ

私のやってるサイト、電子の言霊の活動ログ

カテゴリークラウド

タグリスト

最近のトラックバック

月別アーカイブ(タブ)

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。
  • [HSP3]再帰モジュール(マクロ)

去年の末辺りから作ってて1月の末には出来てたモジュールですが、サンプルもうちょっとあった方が良いかなとか思いつつそのまま放置してました。色々考えすぎたらいつまでも公開できないので、そろそろ公開してみます。
まだ出来そうな感もあるけど色々試してこの形に落ち着きまして取り敢えずこんな感じかなと。次のアイデアも少しだけありもしますが今はまだ様子見中。


2015/04/13 追記:
HSPプログラムコンテスト2014 にて当モジュールの更新版を投稿しています、そちらではローカル変数もどきも使えるようになっていたり、ファイルをアーカイブにまとめてあるため より扱いやすくなっています。興味のある方はこちらからどうぞ→http://dev.onionsoft.net/seed/info.ax?id=896


これは HSP3 で再帰処理構文を提供するモジュールです、repeat & loop と同じ感覚で再帰処理が出来ないかと思い作ってみました。昔よりは緩和されましたが現在のHSPのバージョンでは510回までしかサブルーチンを重ねて呼び出すことが出来ません(それでも十分な処理は多いですが)。このモジュールではHSPのサブレベルの上限に左右されないのでより深いレベルが必要な処理にどこかしら使えるかもしれません。標準命令のみで記述されているので色々と使いやすいとかなんとか思ったり思わなかったり。

モジュールと書いてますが機能のほとんどはマクロとして書かれています。内部で使用している変数名をモジュール空間内に閉じ込めてるのと、取り外し可能な構成単位と言う意味で一応モジュールとしています(他にもっと分かりやすい云い方が思いつかなかっただけってのもありますが)。
処理や名称等々おかしい所がありましたらなんでもご指摘や提案いただけるとありがたいです。

使用だけでなく改変(可読性皆無ですが)や再配布も自由にどうぞ!
その際作者名や入手元などの情報を記載してくれたら嬉しいですが強制では無いのでこれもご自由にどうぞ!


再帰構文を提供するモジュール(マクロ)

通常版

2014/03/31 追記:一部記述ミスでネスト出来なかったのを修正しました。

#ifndef m_rec
#module m_rec

// 初期化命令

#define global rec_init(%1=1,%2=64) _rec_init %1, %2
#deffunc _rec_init int _len, int _siz
len = _len
siz = _siz
/* 未初期化変数報告対策 */
_p1 = 0 :_p2 = 0 :_p3 = 0 :_p4 = 0
return


// 再帰マクロ内で使う専用の stack マクロと命令

#deffunc local _rec_stack_new array arr, int type
on type = 2 goto *ON_DIMTYPE, *ON_SDIM
*ON_DIMTYPE
dimtype arr, type, len + 1
return
*ON_SDIM
sdim arr, siz, len + 1
return

#define rec_stack_new(%1,%2,%3,%4,%5)\
%1_arr0@m_rec = 0 :%1_arr1@m_rec = 0 :%1_arr2@m_rec = 0 :%1_arr3@m_rec = 0 :/* 未初期化変数報告対策 */\
_rec_stack_new@m_rec %1_arr0@m_rec, vartype(%2) :_rec_stack_new@m_rec %1_arr1@m_rec, vartype(%3) :\
_rec_stack_new@m_rec %1_arr2@m_rec, vartype(%4) :_rec_stack_new@m_rec %1_arr3@m_rec, vartype(%5) :\
dim %1_idx@m_rec :ldim %1_lba@m_rec, len@m_rec :\
%1_arr0@m_rec = %2 :%1_arr1@m_rec = %3 :%1_arr2@m_rec = %4 :%1_arr3@m_rec = %5

#define rec_stack_winding(%1,%2,%3,%4,%5,%6)\
%1_lba@m_rec(%1_idx@m_rec) = %2 :%1_idx@m_rec ++ :\
%1_arr0@m_rec(%1_idx@m_rec) = %3 :%1_arr1@m_rec(%1_idx@m_rec) = %4 :\
%1_arr2@m_rec(%1_idx@m_rec) = %5 :%1_arr3@m_rec(%1_idx@m_rec) = %6

#define rec_stack_unwinding_and_goto(%1,%2,%3,%4,%5)\
%1_idx@m_rec -- :rec_stack_peek@m_rec %1, %2, %3, %4, %5 :goto %1_lba@m_rec(%1_idx@m_rec)

#define rec_stack_peek(%1,%2,%3,%4,%5)\
%2 = %1_arr0@m_rec(%1_idx@m_rec) :%3 = %1_arr1@m_rec(%1_idx@m_rec) :\
%4 = %1_arr2@m_rec(%1_idx@m_rec) :%5 = %1_arr3@m_rec(%1_idx@m_rec)

// _REC_NOTRELEASE_ 定数を定義すると、rec_end 終了時に内部変数を開放しないようになる。若干の高速化。
;#define _REC_NOTRELEASE_@ // 有効化でオフになる。ここで有効にするか、#include より前に置く。
#ifndef _REC_NOTRELEASE_@
#define rec_stack_del(%1)\
dim %1_idx@m_rec :dim %1_lba@m_rec :\
dim %1_arr0@m_rec :dim %1_arr1@m_rec :dim %1_arr2@m_rec :dim %1_arr3@m_rec
#else
#define rec_stack_del(%1) /* 使用しない */
#endif


// 再帰マクロ内で使う 戻り値設定マクロ

// _REC_NOTUSESETREF_ 定数を定義すると、戻り値マクロを使用しないようになる。若干の高速化。
;#define _REC_NOTUSESETREF_@ // 有効化でオフになる。ここで有効にするか、#include より前に置く。
#ifndef _REC_NOTUSESETREF_@
#define global setref(%1) goto *%tlbl_ref1%i :*%tlbl_ref2%i :return %1 :*%tlbl_ref1%o :gosub *%tlbl_ref2%o
#else
#define global setref(%1) /* 使用しない */
#endif


// 再帰マクロ

#define global rec_prm1 %tprm%p0
#define global rec_prm2 %tprm%p1
#define global rec_prm3 %tprm%p2
#define global rec_prm4 %tprm%p3
#define global rec_re1 re1@m_rec
#define global rec_re2 re2@m_rec
#define global rec_re3 re3@m_rec
#define global rec_re4 re4@m_rec
#define global rec_brcode code@m_rec

#define global rec_begin(%1=_p1@m_rec,%2=_p2@m_rec,%3=_p3@m_rec,%4=_p4@m_rec)\
%tprm%s4%s3%s2%s1 %trec_lbl_rtn%i0 %trec_lbl_end%i0 \
rec_stack_new@m_rec %trec_var%i, %tprm%p0, %p1, %p2, %p3 :\
rec_call %tprm%p0, %p1, %p2, %p3 :\
rec_break :\
*%trec_lbl_begin%i :\
rec_stack_peek@m_rec %trec_var%p, %tprm%p0, %p1, %p2, %p3

#define global rec_call(%1=rec_prm1,%2=rec_prm2,%3=rec_prm3,%4=rec_prm4)\
rec_stack_winding@m_rec %trec_var%p, *%trec_lbl_back%i, %1, %2, %3, %4 :\
goto *%trec_lbl_begin%p :\
*%trec_lbl_back%o

#define global rec_return(%1=rec_re1,%2=rec_re2,%3=rec_re3,%4=rec_re4)\
rec_re1 = %1 :rec_re2 = %2 :rec_re3 = %3 :rec_re4 = %4 :goto *%trec_lbl_rtn%p

#define global rec_break(%1=0) rec_brcode = %1 :goto *%trec_lbl_end%p
#define global rec_level (%trec_var%p _idx@m_rec)

#define global rec_end(%1=rec_re1,%2=rec_re2,%3=rec_re3,%4=rec_re4)\
rec_return %1, %2, %3, %4 :\
*%trec_lbl_rtn%p :\
setref rec_re1 :\
rec_stack_unwinding_and_goto@m_rec %trec_var%p, %tprm%p0, %p1, %p2, %p3 :\
*%trec_lbl_end%p :\
rec_stack_del@m_rec %trec_var%p :\
rec_release_mcrtag

#define global rec_release_mcrtag %trec_var%o0 %tprm%o0%o0%o0%o0 %trec_lbl_begin%o0 %trec_lbl_end%o0 %trec_lbl_rtn%o0

#global
rec_init
#endif


ライト版

一部命令のパラメータ数が少ないだけでそれ以外は同じ、少ない分通常版より若干速い。

#ifndef m_rec
#module m_rec

// 初期化命令

#define global rec_init(%1=1,%2=64) _rec_init %1, %2
#deffunc _rec_init int _len, int _siz
len = _len
siz = _siz
/* 未初期化変数報告対策 */
_p1 = 0 :_p2 = 0 :_p3 = 0 :_p4 = 0
return


// 再帰マクロ内で使う専用の stack マクロと命令

#deffunc local _rec_stack_new array arr, int type
on type = 2 goto *ON_DIMTYPE, *ON_SDIM
*ON_DIMTYPE
dimtype arr, type, len + 1
return
*ON_SDIM
sdim arr, siz, len + 1
return

#define rec_stack_new(%1,%2,%3)\
%1_arr0@m_rec = 0 :%1_arr1@m_rec = 0 :/* 未初期化変数報告対策 */\
_rec_stack_new@m_rec %1_arr0@m_rec, vartype(%2) :_rec_stack_new@m_rec %1_arr1@m_rec, vartype(%3) :\
dim %1_idx@m_rec :ldim %1_lba@m_rec, len@m_rec :\
%1_arr0@m_rec = %2 :%1_arr1@m_rec = %3

#define rec_stack_winding(%1,%2,%3,%4)\
%1_lba@m_rec(%1_idx@m_rec) = %2 :%1_idx@m_rec ++ :\
%1_arr0@m_rec(%1_idx@m_rec) = %3 :%1_arr1@m_rec(%1_idx@m_rec) = %4

#define rec_stack_unwinding_and_goto(%1,%2,%3)\
%1_idx@m_rec -- :rec_stack_peek@m_rec %1, %2, %3 :goto %1_lba@m_rec(%1_idx@m_rec)

#define rec_stack_peek(%1,%2,%3)\
%2 = %1_arr0@m_rec(%1_idx@m_rec) :%3 = %1_arr1@m_rec(%1_idx@m_rec)

// _REC_NOTRELEASE_ 定数を定義すると、rec_end 終了時に内部変数を開放しないようになる。若干の高速化。
;#define _REC_NOTRELEASE_@ // 有効化でオフになる。ここで有効にするか、#include より前に置く。
#ifndef _REC_NOTRELEASE_@
#define rec_stack_del(%1)\
dim %1_idx@m_rec :dim %1_lba@m_rec :dim %1_arr0@m_rec :dim %1_arr1@m_rec
#else
#define rec_stack_del(%1) /* 使用しない */
#endif


// 再帰マクロ内で使う 戻り値設定マクロ

// _REC_NOTUSESETREF_ 定数を定義すると、戻り値マクロを使用しないようになる。若干の高速化。
;#define _REC_NOTUSESETREF_@ // 有効化でオフになる。ここで有効にするか、#include より前に置く。
#ifndef _REC_NOTUSESETREF_@
#define global setref(%1) goto *%tlbl_ref1%i :*%tlbl_ref2%i :return %1 :*%tlbl_ref1%o :gosub *%tlbl_ref2%o
#else
#define global setref(%1) /* 使用しない */
#endif


// 再帰マクロ

#define global rec_prm1 %tprm%p0
#define global rec_prm2 %tprm%p1
#define global rec_re1 re1@m_rec
#define global rec_brcode code@m_rec

#define global rec_begin(%1=_p1@m_rec,%2=_p2@m_rec)\
%tprm%s2%s1 %trec_lbl_rtn%i0 %trec_lbl_end%i0 \
rec_stack_new@m_rec %trec_var%i, %tprm%p0, %p1 :\
rec_call %tprm%p0, %p1 :\
rec_break :\
*%trec_lbl_begin%i :\
rec_stack_peek@m_rec %trec_var%p, %tprm%p0, %p1

#define global rec_call(%1=rec_prm1,%2=rec_prm2)\
rec_stack_winding@m_rec %trec_var%p, *%trec_lbl_back%i, %1, %2 :\
goto *%trec_lbl_begin%p :\
*%trec_lbl_back%o

#define global rec_return(%1=rec_re1)\
rec_re1 = %1 :goto *%trec_lbl_rtn%p

#define global rec_break(%1=0) rec_brcode = %1 :goto *%trec_lbl_end%p
#define global rec_level (%trec_var%p _idx@m_rec)

#define global rec_end(%1=rec_re1)\
rec_return %1 :\
*%trec_lbl_rtn%p :\
setref rec_re1 :\
rec_stack_unwinding_and_goto@m_rec %trec_var%p, %tprm%p0, %p1 :\
*%trec_lbl_end%p :\
rec_stack_del@m_rec %trec_var%p :\
rec_release_mcrtag

#define global rec_release_mcrtag %trec_var%o0 %tprm%o0%o0 %trec_lbl_begin%o0 %trec_lbl_end%o0 %trec_lbl_rtn%o0

#global
rec_init
#endif

使い方

通常のユーザー定義命令を使った再帰と、当モジュールでの再帰の書き方を見比べることで簡単に使い方を見てみます。
処理自体意味は無いですが、数字の 0 から指定した値まで1つずつ表示する処理になっています。

#module
#deffunc func int a
if a = 0 :return
func a - 1
mes a
return
#global

func 25

当モジュールを "m_rec.hsp" として保存してあるとすると
上記の再帰処理は以下のように書き直すことが出来ます。

#include "m_rec.hsp"

a = 25
rec_begin a
if a = 0 :rec_return
rec_call a - 1
mes a
rec_end

これでなんとなく使い方の感じを掴んで頂けるのでは無いかと思います。
もう少し詳しい仕様についてはこの後の「詳細」をご覧ください。

詳細

命令一覧

このモジュールをインクルードすることで以下の命令などが使えるようになります。

rec_init
rec_begin
rec_end
rec_return
rec_break
rec_call
rec_level
rec_re1 ~ rec_re4(ライト版は rec_re1 のみ)
rec_brcode

#それぞれのネーミングは結構悩んで今もまだこれでよいのかと思っている状態だったりします。より良い案があれば是非。

説明

repeat と loop の関係と同じ様に、まず rec_begin と rec_end で再帰処理をしたい範囲を囲みます。そして rec_begin のパラメータには再帰呼び出し毎にパラメータを受け取るための変数を指定します。ここに指定する変数は予め必要な値で初期化しておく必要があります、この時初期化する値の型は dimtype に指定できる型と同じです(未検証の物もあり)。しかし配列変数にする必要はありません。通常版では4つまでライト版では2つまでパラメータを指定できます。

// このスクリプトは何もしません、直ぐに再帰範囲を通り抜けます。
#include "m_rec.hsp"
a = 0
b = 0.0
c = ""
rec_begin a, b, c
rec_end

上のスクリプトでは再帰呼び出しが無いため再帰範囲の中を通っても直ぐに通過してしまっています。再帰呼び出しを実行するには rec_call を使います。
rec_call が実行されると rec_begin のパラメータに指定していた変数に rec_call のパラメータが渡されもう一度 rec_begin の次の位置から処理が再開されます。つまりこの rec_call が再帰で言う自分自身を呼んでいる相当の意味になります(自分自身とは rec_begin から rec_end までの範囲の事)。rec_call に指定するパラメータは rec_begin のパラメータと対応しているため、通常版は4つまで ライト版は2つまでのパラメータを使用できます。

もちろん rec_call だけでは永遠にループし続ける事になるので戻るための手段が必要です、rec_return は再帰モジュールにおいて HSP標準命令の return 相当の処理をし、一番最後に実行された rec_call の次の位置に実行位置を戻します。この時 rec_begin のパラメータに指定した受け取り変数の内容も自動的に1つ前の状態に戻されます。

// 9 からカウントダウンする1
#include "m_rec.hsp"
a = 0
rec_begin a
if a = 10 :rec_return
rec_call a + 1
mes a
rec_return
rec_end

rec_end の内部で rec_return が実行される様になっているので最後の rec_return は省略可能。

// 9 からカウントダウンする2
#include "m_rec.hsp"
a = 0
rec_begin a
if a = 10 :rec_return
rec_call a + 1
mes a
rec_end

極端な話 rec_return を使わなくても良いという。

// 9 からカウントダウンする3
#include "m_rec.hsp"
a = 0
rec_begin a
if a < 9 :rec_call a + 1
mes a
rec_end

rec_return と rec_end のパラメータには再帰モジュール用戻り値を指定することが出来ます。通常版では4つ、ライト版では1つの戻り値が設定可能で、専用の rec_re1~rec_re4 を使う事で設定した戻り値を取得できます。rec_re1~4 は rec_return, rec_end のパラメータと対応しているので、通常版では rec_re1~4 まで ライト版では rec_re1 のみが用意されています。また初期状態では一番目のパラメータは HSP の戻り値用システム変数にも反映される様になっています。パラメータを省略した場合はそれ以前の戻り値が維持されます。

// 戻り値取得1
#include "m_rec.hsp"
rec_begin
rec_end 1
mes rec_re1
mes stat

HSP の戻り値には触らないようにすることも出来ます。
インクルードするより前に _REC_NOTUSESETREF_ という名前で定数を定義することによって HSP の戻り値に関するシステム変数の値を変更しません。その場合 rec_re1~4 を使って取得します。

// 戻り値取得2
#define _REC_NOTUSESETREF_
#include "m_rec.hsp"
rec_begin
rec_end 1
mes rec_re1
mes stat

使う機会は余りなさそうですが再帰の範囲をネスト出来ます。

// マクロをネスト(ネスト出来ることの例で処理に意味はありません)
#include "m_rec.hsp"
a = 0
rec_begin a
mes "" + a
if a >= 4 :rec_return
rec_begin a
mes "." + a
if a >= 3 :rec_return
rec_call a + 1
rec_end
rec_call a + 1
rec_end

rec_break で再帰範囲を一つ分まるっと抜けることが出来ます。再帰の中止などしたい場合にお使いください。パラメータに値(型問わず)を指定することで中断理由をブレーク後 rec_brcode で取得できます。
更に rec_level で再帰レベルを取得出来ます。

// レベルとブレーク
#include "m_rec.hsp"
a = 0
rec_begin a
mes a
if rec_level > 10 :rec_break "深さが 10 を超えた"
rec_call a + 1
mes "ここは実行されない"
rec_end
mes rec_brcode

rec_init は、再帰が深くなる事があらかじめ分かっている場合に、内部で使っている配列変数の確保サイズを前もって指定することでHSPの配列の自動拡張を抑制し速度低下を抑える事ができます。何も指定しない場合 rec_init 1 が指定されています。

// 要素数を指定しない時と指定した時の速度差を見る
#uselib "winmm.dll"
#cfunc timeGetTime "timeGetTime"
#include "m_rec.hsp"

time = timeGetTime()
rec_begin a
if a = 9999 :rec_return
rec_call a + 1
rec_end
mes "" + (timeGetTime() - time) + "ms"

time = timeGetTime()
rec_init 10000 ; 内部で使っている配列を rec_begin 時に 10000要素分確保する事を指示する
rec_begin a
if a = 9999 :rec_return
rec_call a + 1
rec_end
mes "" + (timeGetTime() - time) + "ms"

ちなみに rec_end を正規の方法で抜けると内部で使用された変数は再確保され配列のサイズが初期化されます。
その際インクルードより前に _REC_NOTRELEASE_ という名前で定数を定義することによってそれをしないようにすることもできます。ほんの微々たるほど速くなりますがほぼデバッグ用途です。


あと注意というか補足の様な物をいくつか。
  • 基本的にこれらの命令は rec_begin と rec_end の範囲内でのみ使用可(rec_re1~4とrec_brcodeは別)。
  • それぞれの命令へのパラメータは省略できるが、その分速くなったりはしない。
  • 再帰処理中に rec_begin へ指定した変数に値を代入しても、内部の配列の要素は書き換えられることはない。
  • rec_begin のパラメータに指定した変数の型と rec_call に渡すパラメータの型は合わせること。
  • rec_begin, rec_call のパラメータにモジュール変数を使う場合は工夫が必要な事がある。
  • 処理はほぼマクロで書いてあるので沢山使うとオブジェクトファイルのサイズが増える。
  • rec_begin に入った時点の最初の再帰レベルは1。
  • 内部で沢山の変数を使っているのでデバッグウィンドウでモジュールのチェックを入れると一杯出てきて驚くかも。
  • バランスの中でなるべく速くを心がけたものの普通にユーザー定義命令で再帰するのに比べて一桁くらい遅い。

こんなところでしょうか。

散々書き散らかしてきましたが
やっぱりヘルプファイル作った方が良いかな
というかファイルにまとめてダウンロード出来る様にした方が良いかな。

サンプルスクリプト

一応実用的?なサンプルスクリプトを作って見ました。

このページの上の方にある再帰モジュールと、下記のサンプルスクリプトを同じフォルダに保存して実行してください。
再帰モジュールのファイル名は、
通常版が m_rec を、ライト版は m_rec_lite を推奨します。

最大公約数

以下のサイトを参考に当モジュールで書いてみました。
ほぼコピペに近いという・・・。

参考にしたサイト:
http://fs-cgi-basic01.freespace.jp/~hsp/ver3/hsp3.cgi?print+201107/11080014.txt
http://rpen.blogspot.jp/2007/05/blog-post_06.html

#include "m_rec.hsp"

a = 15
b = 6

rec_begin a, b
if b = 0 :rec_return a, rec_level - 1
rec_call b, a \ b
rec_end

mes a
mes b
mes "最大公約数:" + rec_re1
mes "最小公倍数:" + (a * b) / rec_re1 ; おまけ
mes "要したレベル:" + rec_re2


階乗

2014/04/22 追記:
再帰との相性が良いらしいのでサンプルとして階乗を追加。
これもほぼウィキペディアのサンプルのコピペ状態だったり。
参考にしたサイト:
http://ja.wikipedia.org/wiki/%E5%86%8D%E5%B8%B0

#include "m_rec_lite.hsp"

n = 6

rec_begin n
if n = 0 :rec_return 1
rec_call n - 1
rec_end n * stat

mes stat


塗りつぶし

深いレベルの再帰が出来るのを活かして塗りつぶし処理をやってみました。
一応VRAMを使って高速化してたり。
2014/03/31 追記:スクリプトの一部が古いままで実行できなかったのを修正しました。

// m_rec のサンプルスクリプト
// 塗りつぶしモジュール


;#include "m_rec.hsp" // どっち使っても良いけど、
#include "m_rec_lite.hsp" // 今回は lite で十分。
rec_init 1048576 // 予め確保しておく配列変数のサイズ


#ifndef RGB
// 色の並びは vram に合わせて 0x00RRGGBB
#define global ctype RGB(%1=0,%2=0,%3=0) (((%1) & 0xff) << 16) | (((%2) & 0xff) << 8) | (((%3) & 0xff))
#endif


// 塗りつぶしモジュール
#ifndef mod_gfill
#module mod_gfill

#define ctype VRAM_INDEX(%1,%2,%3,%4) ( (( (%4) - 1 - (%2) ) * (%3) ) + ( (%1) * 3 ) ) // VRAM 書き込み用にインデックスを計算 VRAM_INDEX(x,y,w,h)
#define ctype VRAM_WIDTH(%1) ( ( ( (%1) * 3 ) + 3 ) & $fffffffc ) // VRAM 書き込み用に4の倍数になるように切り上げる
#define VRAM_SEL(%1) gsel %1 :mref vram@mod_gfill, 66 // VRAM_PGET, VRAM_PSET で使う VRAM 変数指定
#define VRAM_PGET(%1,%2,%3,%4,%5) memcpy %1, vram@mod_gfill, 3, 0, VRAM_INDEX(%2, %3, %4, %5) // pget の VRAM 版
#define VRAM_PSET(%1,%2,%3,%4,%5) memcpy vram@mod_gfill, %1, 3, VRAM_INDEX(%2, %3, %4, %5), 0 // pset の VRAM 版

#deffunc gfill int _x, int _y
x = _x
y = _y
sx = GINFO_SX
sy = GINFO_SY
sx2 = VRAM_WIDTH(sx)
clr_get = 0
clr_fill = RGB(GINFO_R, GINFO_G, GINFO_B)
VRAM_SEL GINFO_SEL
VRAM_PGET clr_bg, x, y, sx2, sy
if clr_bg = clr_fill :return

rec_begin x, y
if x < 0 | y < 0 | x >= sx | y >= sy :rec_return
VRAM_PGET clr_get, x, y, sx2, sy
if clr_get ! clr_bg :rec_return
VRAM_PSET clr_fill, x, y, sx2, sy

rec_call x, y - 1
rec_call x + 1, y
rec_call x, y + 1
rec_call x - 1, y
rec_end
return

#global
#endif


#if 01
randomize
// 適当な模様を描画
max = 3
xsize = GINFO_WINX / max, GINFO_WINX / (max - 1)
ysize = GINFO_WINY / max, GINFO_WINY / (max - 1)
repeat max
c = cnt
repeat max
line GINFO_WINX, ysize * cnt + ysize / 2, 0, ysize * c + ysize / 2
line xsize * cnt + xsize / 2, GINFO_WINY, xsize * c + xsize / 2, 0
circle xsize(1) * cnt - xsize(1) / 2, 0, xsize(1) * cnt + xsize(1) - xsize(1) / 2, GINFO_WINY, 0
circle 0, ysize(1) * c - ysize(1) / 2, GINFO_WINX, ysize(1) * c + ysize(1) - ysize(1) / 2, 0
loop
loop
// クリックで塗りつぶし
*MAIN
stick key, 256, 1
if (key & 256) ! 0 {
pget mousex, mousey
if RGB(GINFO_R, GINFO_G, GINFO_B) > 0 {
redraw 0
hsvcolor rnd(192), rnd(160) + 96, 255
gfill mousex, mousey
redraw 1
}
}
await 16
goto *MAIN
#endif
スポンサーサイト
  • [HSP3] Unicodeでファイルを開く

前にHSP本家の掲示板で、ファイル名に中国語を含んだファイルを開く事についての質問があり、Unicode を使えば良いんじゃないかって話のやり取りを見て、自分なりに調べて作ってたスクリプトが有ったんですが、質問者が未解決のまま居なくなってしまって、そのまま投稿する機会を失っていました。せっかく書いたのに公開出来ないのも寂しいので、こちらで公開してみます。(いくらか適当なトコもあったりしますが…)


追記(2014/08/20):
久しぶりにスクリプトを見返したら、あまりにもいい加減すぎたので修正しました。

追記(2015/01/18):
ユニコードを手軽に扱う HSP3 用モジュール mod_unicode をサイトにて公開しました。この記事の内容がよく分からなかったり、自分でやるのが面倒くさい人は使ってみてください。


・指定したテキストファイルのファイル名とその内容の一部をUnicodeとして表示
#スクリプトは HSP3.4rc1 で実行確認しています

#include "comdlg32.as"
#include "user32.as"
#include "kernel32.as"
#include "advapi32.as"

#define MAX_PATH                        $00000104
#define OFN_FILEMUSTEXIST               $00001000
#define OFN_ALLOWMULTISELECT            $00000200
#define OFN_EXPLORER                    $00080000
#define OPENFILENAME_SIZE_VERSION_400   $0000004C
#define DT_WORDBREAK                    $00000010
#define DT_EXPANDTABS                   $00000040
#define GENERIC_READ                    $80000000
#define OPEN_EXISTING                   $00000003
#define FILE_ATTRIBUTE_NORMAL           $00000080
#define INVALID_HANDLE_VALUE            $FFFFFFFF
#define FILE_SHARE_READ                 $00000001
#define CP_ACP                          $00000000
#define MB_PRECOMPOSED                  $00000001


// OPENFILENAME構造体初期化に必要なものを準備

    // フィルター文字列が Unicode で入るだけの大きさを確保
    sdim filter, 512
    // フィルター文字列に置換マーク(0)を埋め込んで Unicode に変換(下で手動で \0 を置換)
    cnvstow filter, "text(*.txt)\0*.txt\0All files(*.*)\0*.*\0\0"           ; HSP に \0 は無いので実際は数字の 0 になる
    // マークを Unicode のヌル文字(2byte の 0x0000)に置換(上書き)
    wpoke filter, 22 :wpoke filter, 34 :wpoke filter, 64 :lpoke filter, 72  ; 終端は並んでるので 4byte 一気に換えてみた
    // ダイアログの選択結果を格納する変数を作成
    sdim filepath, MAX_PATH * 2  ;ここにファイルのパスが入る
    sdim filename, MAX_PATH * 2  ;ここにファイルネームが入る
    cnvstow caption, "開く"

// OPENFILENAME構造体初期化

    // 構造体用の変数を作って適切な値を設定する
#if 01 ; Windows 2000以降ならこっちのサイズを指定するみたい
    dim ofn, 22
    ofn(0) = 88
#else
    dim ofn, OPENFILENAME_SIZE_VERSION_400 / 4
    ofn(0) = OPENFILENAME_SIZE_VERSION_400
#endif
    ofn(1) = hwnd
    ofn(3) = varptr(filter)
    ofn(7) = varptr(filepath), MAX_PATH     ; Unicode 版ではこの MAX_PATH で指定した
    ofn(9) = varptr(filename), MAX_PATH     ; メンバは文字数扱いらしい
    ofn(12) = varptr(caption)
    ofn(13) = OFN_FILEMUSTEXIST

    // ファイルオープンダイアログを表示
    GetOpenFileNameW varptr(ofn)
    if stat = 0 :mes "ダイアログキャンセル" :stop

// 選択したファイルを読み込む

    // 取得したファイルネームを使ってファイルを開きそのハンドルを得る
    CreateFileW varptr(filename), GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0
    hfile = stat
    if hfile = INVALID_HANDLE_VALUE { mes "CreateFileWでエラー" : stop }

    // テキストファイルの内容を変数に読み込む
    GetFileSizeEx hfile, varptr(filesize)                           ; ファイルのハンドルからファイルサイズ取得
    sdim text, filesize + 2                                         ; ファイルの内容を入れる変数を作成(ヌル文字分加算)
    ReadFile hfile, varptr(text), filesize, varptr(readsize), 0     ; ファイルの内容を取得
    CloseHandle hfile                                               ; ハンドルを閉じる

// 読み込んだファイルを表示

    // ファイルのパスを表示
    font MSGOTHIC16
    mes "ファイルパス:"
    rect = 0GINFO_CY640GINFO_CY + 16 : pos , rect(3)
    DrawTextW hdcvarptr(filepath), -1varptr(rect), DT_WORDBREAK|DT_EXPANDTABS

    // ファイルの名前を表示
    mes "\nファイルネーム:"
    rect = 0GINFO_CY640GINFO_CY + 16 : pos , rect(3)
    DrawTextW hdcvarptr(filename), -1varptr(rect), DT_WORDBREAK|DT_EXPANDTABS

    // ファイルの内容が Unicode か判定し Unicode に合わせる
    IsTextUnicode varptr(text), filesize, 0
    if stat {   /* Unicode ならば */
        // 文字数取得
        lstrlenW varptr(text)       ; ここで取得するのは
        unilen = stat               ; 文字サイズでなく文字数(ヌル文字は含まれない)
        mes "\nファイルの内容 (Unicode) : "
    } else {    /* Shift_JIS ならば */
        // 文字数取得
        MultiByteToWideChar CP_ACP, MB_PRECOMPOSED, varptr(text), -100  ; Shift_JIS 文字列を Unicode 文字列にした時の長さを取得
        unilen = stat                                                       ; これも文字数が返る(ヌル文字分含む)
        // Unicode に変換
        text2 = text                ; text 変数へ Unicode 文字列を入れるために Shift_JIS 文字列を別の変数に複製
        sdim text, unilen * 2       ; Unicode 文字列が入るだけの大きさで変数を作り直す
        unilen --                   ; ヌル文字分減らす
        cnvstow text, text2
        mes "\nファイルの内容 (ShiftJISからUnicodeに変換して表示) : "
    }

    // ファイルの内容を一部表示してみる
    rect = 0GINFO_CY640GINFO_CY + 16 * 3
    DrawTextW hdcvarptr(text), -1varptr(rect), DT_WORDBREAK|DT_EXPANDTABS
    pos , rect(3)

    // ファイル情報を表示
    mes "\nファイルサイズ:\n" + filesize + "byte"
    mes "\n文字数:\n" + unilen + "文字"
    mes "\n文字列のサイズ:\n" + (unilen * 2) + "byte"
    redraw

標準のHSPのファイルオープンダイアログ命令 dialog "", 16 で得られるファイルパスの文字コードは Shift_JIS なので、Win32API の GetOpenFileNameW から Unicode 用のファイルオープンダイアログを開いて、Unicode 文字列でファイルパスを取得しています。
ファイルの内容を取得する際も、HSP 標準の bload, noteload 命令には Shift_JIS 文字列のファイルパスしか渡せないため、API の CreateFileW で Unicode 文字列のファイルパスから ファイルを開き、これまた API の ReadFile で読み込んでいます。
テキストファイルの内容を取得した後も、その中身が同じ文字コードで書かれた文字列とは限らないため 中の文字コードによって処理を分けています。処理を分けるためにはそのテキストの文字コードの種類を判定する必要がありますが、それには API の IsTextUnicode 関数で実現できるようなので それを使って見ました(ネット上の C言語で書かれたいくつかのサンプルを見ながら書いて見ましたがいまいち使い方に自信が無い...(^^;))。
このスクリプトでは中のテキストの文字コードが Unicode ならそのまま、Shift_JIS なら Unicode に変換し、API の DrawTextW を使って Unicode 文字列を画面に表示しています。

・デスクトップにあるファイル名の一覧を Unicode で取得し表示する
#スクリプトは HSP3.4rc1 で実行確認しています

#include "user32.as"
#include "kernel32.as"
#include "gdi32.as"

#define MAX_PATH                    $00000104
#define DT_WORDBREAK                $00000010
#define DT_EXPANDTABS               $00000040
#define INVALID_HANDLE_VALUE        $FFFFFFFF
#define ERROR_NO_MORE_FILES         $00000012
#define FILE_ATTRIBUTE_DIRECTORY    $00000010
#define SW_SHOW                     $00000005
#define WS_CHILD                    $40000000
#define WS_VISIBLE                  $10000000
#define WS_BORDER                   $00800000
#define WS_VSCROLL                  $00200000
#define WS_HSCROLL                  $00100000
#define ES_MULTILINE                $00000004
#define ES_READONLY                 $00000800
#define WS_EX_CLIENTEDGE            $00000200
#define FW_NORMAL                   $00000190
#define DEFAULT_CHARSET             $00000001
#define OUT_DEFAULT_PRECIS          $00000000
#define CLIP_DEFAULT_PRECIS         $00000000
#define DEFAULT_QUALITY             $00000000
#define DEFAULT_PITCH               $00000000
#define FF_DONTCARE                 $00000000
#define WM_SETFONT                  $00000030
#define SYSTEM_FONT                 $0000000D

#define dwFileAttributes            0
#define nFileSizeLow                8
#define cFileName                   11


// ファイル列挙の準備
    // FindFirstFileW に必要な物の準備
    // WIN32_FIND_DATA は 320 byte だが、Unicode 版の WIN32_FIND_DATAW は 592 byte 必要
    dim fd, 148                                                 ; WIN32_FIND_DATAW 構造体分の領域確保
    sdim dir, MAX_PATH * 2
    cnvstow dir, DIR_DESKTOP + "\\*"                            ; FindFirstFileW に渡すフォルダ名を Unicode 文字列として用意

// ファイル列挙の開始
    // 検索ハンドルと最初のファイルを取得
    FindFirstFileW varptr(dir), varptr(fd)
    if stat = INVALID_HANDLE_VALUE {
        dialog "ファイル列挙開始のエラー"
        end
    }
    hfind = stat

    // ファイルリスト作成に必要な物の準備
    sdim filelist, (MAX_PATH * 2 + 64) * 256                    ; ファイル一覧の入る変数(予め十分なサイズを取る)
    sdim tmp, 64                                                ; 一時的に使う変数
    sdim ln, 64
    cnvstow ln, "\n"                                            ; 改行を Unicode 文字化する
    num = 0

    // ファイルリストを作る
    repeat
        // 取得ファイル(フォルダ)名を filelist に書き込む
        lstrcatW varptr(filelist), varptr(fd.cFileName)         ; filelist 末尾に fd.cFileName からの文字を書き込む

        // ファイルかフォルダかで処理を分ける
        if fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY {
            cnvstow tmp, "(フォルダ)"                         ; tmp にフォルダ表記を Unicode 文字列として格納
        } else {
            cnvstow tmp, strf("(%dバイト)", fd.nFileSizeLow)  ; tmp にファイルサイズを Unicode 文字列として格納
        }

        // ファイルリスト変数に改行区切りで追加
        // 取得したのがファイルならファイルサイズが、フォルダならフォルダ表記が書き込まれる
        lstrcatW varptr(filelist), varptr(tmp)                  ; filelist 末尾にファイル情報(サイズ、フォルダ)を書き込む
        lstrcatW varptr(filelist), varptr(ln)                   ; filelist 末尾に改行を書き込む

        // 次のファイルを取得
        FindNextFileW hfind, varptr(fd)
        if stat = 0 {                                           ; 処理の終了判定
            GetLastError
            if stat = ERROR_NO_MORE_FILES {                     ; ファイルが尽きたかエラーかを判定
                num = cnt + 1                                   ; 列挙数を格納
            } else {                                            ; エラーが起きたので終了する
                num = -1
            }
            break
        }
    loop

// ファイル列挙の終了
    // 検索ハンドルを閉じる
    FindClose hfind

    title "列挙数 : " + num
    if num < 0 {
        mes "次のファイルを取得のエラー"
        stop
    }


// 表示のしかたを切り替え可能
#if 0;1
// ウィンドウに直接ファイルリストを描画
    font MSGOTHIC16
    rect = 00GINFO_WINXGINFO_WINY : pos , rect(3)
    DrawTextW hdcvarptr(filelist), -1varptr(rect), DT_WORDBREAK|DT_EXPANDTABS
    redraw
#else
// HSPで言うところの mesbox を作成して、ファイルリストを表示
    // エディットコントロール作成
    style = WS_CHILD | WS_VISIBLE | ES_MULTILINE | WS_BORDER | WS_VSCROLL |WS_HSCROLL | ES_READONLY
    CreateWindowExW WS_EX_CLIENTEDGE, "EDIT"varptr(filelist), style, 00GINFO_WINXGINFO_WINYhwnd0hinstance0
    hedit = stat                                                ; コントロールのハンドル
    // エディットにフォントを設定
    sdim fontname, MAX_PATH * 2
    cnvstow fontname, MSGOTHIC                                  ; フォント名をユニコードにしておく
    pandf = DEFAULT_PITCH | FF_DONTCARE
    CreateFontW 16000, FW_NORMAL, 000, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, pandf, varptr(fontname)
    hfont = stat                                                ; フォントのハンドル
    SendMessageW hedit, WM_SETFONT, hfont, 1                    ; フォントを反映
// 一応最後にフォントを消去
    onexit *FIN
    stop
*FIN
    DeleteObject hfont
    end
#endif

列挙したデスクトップ上のファイル名の連結には Unicode のため a += b + "\n" とかは出来ないので、初めはサイズをチマチマ計算し memcpy 命令を使っての切り貼りで連結していましたが、lstrcatW と言う便利なAPI関数を発見してだいぶんスッキリ書くことが出来ました。
結果を表示するメッセージボックス(EDIT)も、HSP標準の物では Unicode 文字列を表示できないので、Unicode 対応のオブジェクト(コントロール)を、API の CreateWindowExW から作成しています。

・おまけで Unicode の表を表示。キーボードの上下左右キーでスクロール。
#スクリプトは HSP3.4rc1 で実行確認しています

#include "gdi32.as"

    start = 0
    fsize = 24
    xmax = GINFO_WINX / fsize
    ymax = GINFO_WINY / fsize
    font "メイリオ", fsize
    gosub *SUB_PRINT
    onkey gosub *ON_KEY
    stop

*ON_KEY
    if wparam >= 37 & wparam <= 40  {
        if wparam = 37  :page -= ymax
        if wparam = 39  :page += ymax
        if wparam = 38  :page--
        if wparam = 40  :page++
        gosub *SUB_PRINT
    }
    return

*SUB_PRINT
    redraw 0
    color 255255255  :boxf
    color
    repeat xmax * ymax
        x = cnt \ xmax
        y = cnt / xmax
        wpoke text, 0, start + cnt + page * xmax
        TextOutW hdc, x * fsize, y * fsize, varptr(text), 1
    loop
    redraw 1
    title ""strf("0x%04x ~ 0x%04x", (start + page * xmax) & 0xffff, (start + page * xmax + xmax * ymax) & 0xffff)
    return


ここまで作ってみて、やはり HSP から Unicode を扱うのはかなり面倒だという事が分かりました。HSPの命令は Unicode にはほぼ無力なので、手軽さが売りの HSP でやるには、APIを多用したり定数を定義したり、あまりにも面倒臭すぎます。。。

#調べながらなので間違い等あるかもしれません。
#HSPスクリプトの色分けには主に eller さん作の HTXcnv を使わせて頂きました。
  • [HSP3] つやつやギャラリー

サンプルスクリプト第一弾として、HSPコンテスト2009に応募して最終審査で落ちた(^^;)作品をほんの少し改善したものを掲載します。締め切り時間があと6時間くらいの所で、元々作って有った画像が回るスクリプトをベースに何とか形にした物なので、改善する余地はまだ幾分かありますけど。

「つやつやギャラリーv1.2」
#HSP3.2以降で実行してください
スクリプトのダウンロード


/*・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・
    HSPコンテスト2009応募作品

    つやつやギャラリー Version 1.2
    作者 tyty
・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・*/


    /* 初期化処理 */

    // ウィンドウタイトル設定
    title "つやつやギャラリー"

    // 変数初期化
    bg_r   = 255
    bg_g   = 255
    bg_b   = 255
    maxpix = 128
    ratio  = 0.0
    space  = 8
    max = 128
    x = (GINFO_WINX - maxpix) / 2
    y = (GINFO_WINY - maxpix) / 2

    // 配列変数初期化
    sdim files, 512, max
    dim posx, max
    dim posy, max
    dim order, max
    order = 0123456789
    ddim distance, max
    dim zx, max
    dim zy, max
    dim id, max
    dim margin, max

    // バッファー初期化

    /* 画像読み込み専用バッファー。
       ・サムネイル作成処理では、ここに一旦開いて、サイズ取得&サムネイル用バッファーに縮小コピーに使用
       ・クリック時の画像表示では、選択画像の読み込みに使用 */

    buffer 111

    // クリック時画像フェードに使うバッファーの初期化
    buffer 2640480          ; 暗くする時の色合成用
    buffer 3640480          ; 暗い状態から元に戻す為のメイン画像のコピー用

;・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・

    /* 画像ファイルリストの作成 */

    // 選んだ画像の置いてあるフォルダーを対象に
    dialog "jpg"16
    if stat = 0    :end

    gsel 0
    mes "読み込み中・・・"

    // 改行区切りのファイルリストを取得
    dirlist flist, "*.jpg"1
    fnum  = stat

    // 読み込み数の上限を10個までにする
    if fnum >= 10 { lim = 10   }
    else          { lim = fnum }

    // ファイルリストを改行で切り分けて、配列に格納する
    i = 0
    repeat lim
        getstr file, flist, i
        i += strsize
        files(cnt) = file
    loop

    /* ファイルリストを元に、サムネイル用の画像作成 */

    i = 0
    repeat lim

        // ファイルの存在確認
        exist files(cnt)
        if strsize < 0    :continue

        // 画像ファイルを開いて画像のサイズを取得
        gsel 1
        picload files(cnt)
        gx = GINFO_SX
        gy = GINFO_SY

        // 縦横比の計算
        if gx >= gy {
            ratio = double(gy) / gx
            zx(i) = maxpix
            zy(i) = int(ratio * maxpix)
            margin(i) = maxpix - zy(i)
        } else {
            ratio = double(gx) / gy
            zx(i) = int(ratio * maxpix)
            zy(i) = maxpix
            margin(i) = 0
        }

        // 新しいバッファーを作成
        buffer GINFO_NEWID, zx(i), zy(i)        ; リサイズ後の大きさで未使用バッファーを作成
        id(i) = GINFO_SEL                       ; 0要素目から順番にサムネイル用バッファーIDを入れていく

        // 新しいバッファーに、サイズ取得時に読み込んだ画像を縮小コピー
        gzoom zx(i), zy(i), 100, gx, gy, 1  ; サムネイル用にリサイズする
        i++

    loop

    // 使用済みバッファーを出来るだけ開放
    buffer 111

;・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・

    /* その他の設定 */

    gsel 0                      ; 描画先をメインウィンドウに
    randomize                   ; 乱数を不規則に
    onclick gosub *ON_CLICK     ; クリック時のジャンプを設定(選択画像の拡大)

;・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・

*MAIN

    // サムネイル画像の回転処理
    repeat

        redraw 0

        // 背景色を描画
        color bg_r, bg_g, bg_b
        boxf 00GINFO_WINXGINFO_WINY

        // ギャラリーの回転弧度を計算
        rad = 6.283 / 256 * cnt                 ; 一周するのに256ループ(手抜き)

        // 座標計算
        repeat lim
            tmp = rad + M_PI * 2 / lim * cnt    ; radをベースに各画像を等間隔で配置
            posx(cnt) = x + cos( tmp ) * x      ; tmpを元にX座標を計算
            posy(cnt) = y + sin( tmp ) * 32     ; tmpを元にY座標を計算
            distance(cnt) = sin( tmp ) + 1      ; ソート処理用に奥行きを計算
        loop

        // 奥行きを元に表示順をソーティング
        repeat lim
            i = cnt
            // 最大値を捜索
            repeat lim - (1 + cnt), 1 + cnt
                if distance(order(i)) > distance(order(cnt))    :i = cnt
            loop

            // 探した値同士を交換
            if cnt ! i {
                tmp = order(cnt)
                order(cnt) = order(i)
                order(i) = tmp
            }
        loop

        // 画面にサムネイル画像と映り込みを描画
        repeat lim
            // 使用する変数を用意
            i   = order(cnt)        ; 記述の簡略化のためにソート後のインデックスを代入
            ref = 1.75              ; 映り込み反射の長さ、2にすると半分になる(1.0~)

            // 画像本体を表示
            gmode 0
            pos posx(i), margin(i) + posy(i)
            gcopy id(i), 00, zx(i), zy(i)

            // 床の反射を表示
            repeat zy(i)
                gmode 3, zx(i), 1192.0 - (192.0 / zy(i) * (ref * cnt))
                pos posx(i), margin(i) + posy(i) + zy(i) + space + cnt
                gcopy id(i), 0, zy(i) - (ref * cnt)
            loop
        loop

        // どの画像の上にマウスポインタがあるかを調べる(secect変数に選択画像IDが入る)
        select = -1
        repeat lim
            // 奥行きの手前にある物から探す
            i = order(lim - cnt - 1)
            // マウスの位置から選択座標を判断
            if (mousex > posx(i)) & (mousex < posx(i) + zx(i)) {
            if (mousey > posy(i) + margin(i)) & (mousey < posy(i) + zy(i) + margin(i)) {
                    // 選択されていたら、選択画像の周りに枠を表示
                    boxsize = 4                                 ; ボックスの枠サイズ
                    color bg_r ^ 255, bg_g ^ 255, bg_b ^ 255    ; 背景色の補色
                    // 枠描画
                    boxf posx(i), posy(i) + margin(i), posx(i) + zx(i), posy(i) + margin(i) + boxsize
                    boxf posx(i) + zx(i) + 1, posy(i) + margin(i), posx(i) + zx(i) - boxsize, posy(i) + margin(i) + zy(i)
                    boxf posx(i) + zx(i), posy(i) + margin(i) + zy(i) + 1, posx(i), posy(i) + zy(i) + margin(i) - boxsize
                    boxf posx(i), posy(i) + margin(i), posx(i) + boxsize, posy(i) + margin(i) + zy(i)
                    select = i      ; 選択画像のIDを代入
                    break           ; 選択されていたらそこでループを抜ける
                }
            }
        loop

        redraw 1
        await 20

    loop

;・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・

/* クリック時のジャンプ先 */

*ON_CLICK

    // クリック時のズーム処理を何コマでアニメーションするか
    #define ANIME 10

    // 左クリックされた時の処理
    if wparam = 1 {

        // マウスの下に画像がある時だけ実行
        if select > -1 {
    
            /* クリックされた画像を大きく表示 */

            // 画像表示のための準備

            // 一時的にクリック無効化
            onclick 0

            // ファイルの存在確認
            exist files(select)
            if strsize < 0 {
                // エラー。クリックを有効化して、サブルーチンジャンプを抜ける
                dialog "ファイルが読み込めません"
                onclick 1
                return
            }

            // バッファーに画像読み込み、画像サイズの記憶
            gsel 1                              ; 画像読み込み用バッファー
            picload files(select)               ; マウスで選択したサムネイル画像のファイル名で開く
            gx = GINFO_SX
            gy = GINFO_SY

            // 縦横比の計算
            if gx >= gy {
                ratio = double(gy) / gx
                zx2   = 500
                zy2   = int(ratio * 500)
            } else {
                ratio = double(gx) / gy
                zx2   = int(ratio * 420)
                zy2   = 420
            }

            /* 画像を開く際のエフェクト */

            // 暗くする時のベース色
            gsel 2
            boxf                        ; 黒色でバッファーを塗りつぶす

            // 暗い状態から復帰するためのメイン画像のコピー
            gsel 3
            pos 00
            gcopy 000640480     ; サムネイル回転画面を複製

            // 暗くする
            gsel 0
            repeat ANIME1
                redraw 0
                gmode 3640480180 / ANIME
                pos 00
                gcopy 200
                redraw 1
                await 20
            loop

            // 画像ズームフェードから復帰するため、画面が暗くなった状態のメイン画面を複製
            gsel 2
            pos 00
            gcopy 000640480

            // 画像をズームしながら表示
            gsel 0
            gmode 0
            repeat ANIME1
                redraw 0
                pos (640 - zx2 / ANIME * cnt) / 2 , (480 - zy2 / ANIME * cnt) / 2
                gzoom zx2 / ANIME * cnt, zy2 / ANIME * cnt100, gx, gy, 1
                redraw 1
                await 15
            loop

            /* 画像のファイル名を表示 */

            // 文字の描画サイズを得るため、画面外にファイ名を表示
            redraw 0
            pos 6400
            mes files(i)

            // 画面の中央(X座標)に、袋文字の枠用ファイル名を表示
            color 0                 ; 袋文字の枠の色
            repeat 3  :_y = cnt
            repeat 3  :_x = cnt
                pos (640 - GINFO_MESX) / 2 + _x, 480 - GINFO_MESY - 3 + _y
                mes files(i)
            loop
            loop

            // 袋文字の中央に袋文字中身の文字を表示
            color 255255200     ; 袋文字の中の色
            pos (640 - GINFO_MESX) / 2 + 1480 - GINFO_MESY - 3 + 1
            mes files(i)
            redraw 1

            /* クリック待ち待機ループ */

            // 左クリックで復帰(何故か起動直後一回目のクリック判定がおかしいのでフラグで対処)
            flg = 0
            repeat
                await 100
                stick key
                if flg = 0       :key = 0    :flg++
                if key = 256     :break
            loop

            /* 画像を閉じる際のエフェクト */

            // 画像の縮小効果
            repeat ANIME1
                redraw 0
                pos 00
                gcopy 200640480
                pos (640 - zx2 / ANIME * (ANIME - cnt)) / 2 , (480 - zy2 / ANIME * (ANIME - cnt)) / 2
                gzoom zx2 / ANIME * (ANIME - cnt), zy2 / ANIME * (ANIME - cnt), 100, gx, gy, 1
                redraw 1
                await 15
            loop

            // 明るくする
            repeat ANIME1
                redraw 0
                gmode 3640480180 / ANIME * cnt
                pos 00
                gcopy 300
                redraw 1
                await 20
            loop

            /* 処理をメインに戻す前に後片付け */

            // 使用済みバッファーを出来るだけ開放
            buffer 111
            // メイン画面を描画先に
            gsel 0
            // 一時的なクリック無効化の解除
            onclick 1

        }

    }

    // 右クリックされた時の処理
    if wparam = 2 {

        /* 背景色の変更 */

        // 背景色の元をランダムに決定
        tmp = 2
        repeat 3
            r(cnt) = rnd(tmp + 1)       ; 0 ~ 2 の乱数を、赤、青、緑、分生成
        loop

        // 乱数からそれぞれの色成分を計算
        bg_r = r(0) * (256 / tmp) - (r(0) ! 0)      ; 背景色赤 : 0 or 127 or 255
        bg_g = r(1) * (256 / tmp) - (r(1) ! 0)      ; 背景色緑 : 0 or 127 or 255
        bg_b = r(2) * (256 / tmp) - (r(2) ! 0)      ; 背景色青 : 0 or 127 or 255

    }

    return

;・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・
  • HSP超入門の索引を作りました

ここのブログだけだとHSP超入門の記事を ずらりと通して読むのには辛いので、各記事へのインデックスを作って見ました。

ページは→こちら

今後記事を追加した時はそちらも合わせて更新していきます。でもまだ当分昔の記事の見直しに追われそうですけどね。HSPも3.2にヴァージョンアップしたしスクリーンショットも撮り直さないとなー。


今年もHSPコンテストが始まり 私も何かしら作ったりしてるんですが、まだなんにもまとまってません、何とか間に合わせたいところです。
  • [HSP超入門] 始めに

HSP超入門 とだいして簡単なHSP講座を書いて見たいと思います、少しでもこれから HSP を始めようとしている方の参考になれば嬉しい限りです。
一応出来る限り 間違いの無い様に心がけて書いていくつもりですが、私も分かっていない事の方が多いので 正しく無い事を書いているかもしれません、この講座だけ読んで満足してしまわない様ご注意下さい
この講座で分からない事があればコメントとして書き込んでいただければ私の分かる範囲ですが 出来る限り返信して行きたいと思っています。間違い等への突っ込みも大歓迎です(^^)。
なお 今回は単なる能書きですので、読み飛ばして頂いて構いません。


手始めに、プログラムとは一体なんなのか? と言う所から始めて行きましょう。

辞書によるとプログラム(Program)とは、計画, 予定, スケジュール, 日程、番組, 催し などとあります。テレビ番組もプログラムだし、学校の運動会などのイベントも、その出し物の記された一覧表もプログラムだと言う訳です。

つまり一般的には 予定を並べた物や予定そのものがプログラムと言う事になります。
#言葉の語源は ギリシャ語の pro(前もって) gramma(書いた物) から来ているそうです。

それに対して私たちが今から始めようとしている物は コンピュータープログラミングと呼ばれ、コンピューターに対し色々な命令を指示するという物です。なにやら難しそうな感じがしますが、コンピュータープログラムも基本的にはプログラムのはずですから、先ほどの辞書による説明はそのまま当てはまります。コンピューターにさせたい事を あらかじめ整理して書いておき、それを順番に実行させるのです。

ところで皆さんは、料理をした事がありますか?
料理にはレシピと言う物がありますが、材料を買って来て、それを切り 鍋に投入し 調味料を入れ 火が通ったら・・・と料理が完成するまでの過程がすべてレシピに書かれています。料理のレシピも 料理が出来るまでの手順が書かれている物という意味ではプログラムなのです。ここではプログラミングを料理に例えて説明して行きたいと思います。(ここでいう レシピ とは プログラムの設計図 で、料理はそれにって作られるプログラムに当たります。)

プログラムは単なる手順で連続した指示なのですが、指示は時と場合によって 大きかったり小さかったり色んな細かさの単位で考える事が出来ます。例えば料理でいうと 大まかにただ「カレーを作る」で説明が十分の時もあれば、どこの店でどの材料を買って どうやって料理を作り どのように皿に盛るのか まで説明しないといけない時もあります。
もっと細かくすると どの道からどのように どの方法で店へ行き 何の材料をいくら以内でどの位買い どうやって帰ってきて 何からどのように下ごしらえをして ・・・などといくらでも続ける事が出来てしまいます。どこまで細かく割り振るかは目的や状況によって違ってくる物です。

HSPでは これらの手順の大きさが ある程度大きくなっていて、どこにどうやって買い物に行くとか、どのように皮をむくかなどを それほど考えなくても良いため、比較的大きな単位でプログラムを書く事が出来る様になっています。野菜を洗って皮をむいて・・・と1から自分で作るのと、あらかじめ食材が下ごしらえされた状態から始めるのとでは どちらが容易いか、それは明らかですね。 もちろんある程度細かい作業を要求される事も有あれば、そこに楽しみを見出す事もあります。(料理の世界では包丁、火加減、味付け、盛り付けなど、一通りこなせて始めて一人前と呼ばれますが、プログラムではどうなんでしょうね)

コンピュータープログラミングの世界では バグ と言いう物がよく問題になります、バグとはプログラムに入り込んだ 間違い で、どれだけ気を付けていても プログラムを作っているのは人間なので必ずミスをしてしまいます。計算する数値を間違えたという単純なバグから、さまざまな要因が重なった根深いバグまで色々あります。

バグはレシピで言うところの誤表記に当たり、食材や調味料に量や名前の間違いがあるとか、火加減や加熱時間に誤りがあるとか、調理の手順が抜けているとか、野菜に味付けしてから洗うなどと順番が入れ替わっていたりと、本来やりたい事とは、違って書かれてしまった意図しない指示です。これではどんなにレシピ通りに正しく作っていても目的の物は出来ませんね


もしレシピに間違いが無いのに 勝手に間違った処理や違う順番で実行される事があれば、コンピューターかそれを動かすプログラムにバグがあると思っていいでしょう。いつだって正確に命令を実行するそれがコンピューターです。しかしそのコンピューターやそれを動かすプログラムに誤りがあれば、コンピューターはまったくその通り 正確 に間違えを実行して見せます。

しかし、そういう事になってしまっては大変な事になるので、そういう物は一般的にかなり注意深く時間と手間とお金を掛けて作られているため、そう多い物ではありません。
ですので プログラムを始めたばかりで、もし自分の作ったプログラムが思った通りに動かない時は、まず自分のプログラムを先に疑う様にする方が良いでしょう。

とはいえ実はこの講座で使用する HSP にもいくつかバグが含まれていて、正しくプログラムを書いてもバグのせいで正しく実行されないと言う事もたまにはあるので いつまでも自分を疑ってかかる必要もありません。
そのHSPのバグも 注意や工夫さえすれば回避できる物も多く、バグはこれからも修正されていくので、HSPをやるにあたって特に心配する必要はありません。
  • [HSP超入門] 実行ファイル作成1

今まで私達が作ってきたプログラムはスクリプトエディターからでないと実行させる事が出来ませんでした、でも普段みんなが使っている他のソフト(アプリケーション)はプログラムのアイコンをダブルクリックするだけで実行出来ますね、
これは実行ファイルと言う 拡張子が exe のファイルで、プログラムを実行するのに必要なデータがたくさん詰まっています、なのでそれだけでプログラムを実行する事が出来きます。

もし自分でもこの実行ファイルを作る事が出来たら、ずいぶんと可能性が広がりそうですね。実はHSPでもこの実行ファイルの作成をサポートしていて簡単に作る事が出来ます。(少しなれが必要かもしれませんが)

HSP3での実行ファイル作成には主に、「自動」と「手動」の2通りの方法があるのですが、今回は古くから行われている「手動」での方法を紹介します。

作成までの手順はこんな感じです。
1 スクリプトを書く
2 AX ファイル作成
3 パックファイル編集
4 実行ファイル作成

まず始めに exe ファイルにしたいスクリプトを書きます、そしてそれを自分の分かる場所に hsp ファイルとして保存しておきます(保存しなくても出来ますがカレントディレクトリがややこしくなるのでここでは保存しています)、もちろん今までに作ってたおいたスクリプトを読み込んでもOKです。

----------------カレントディレクトリ----------------
ディレクトリはフォルダの別名で、カレントディレクトリとはプログラムごとに設定された作業フォルダの事です。と言っても訳が分からないと思うのでもう少し噛み砕いて言うと、例えば何かを探している時私たちは今居る場所からそれを探しますね、台所に居れば台所から、茶の間に居れば茶の間から、友達の家に居れば友達の家からそれを探します。プログラムでもディレクトリの現在地と言うのがあって、プログラムはそこを基準にファイルを探したり作成したりします、要するに今居る場所と言うのがカレントディレクトリの考え方です

スクリプトを保存したり読み込んだりすると、そのスクリプトのある場所がカレントディレクトリに設定されます。新規作成の時(保存も読み込みもしない)のカレントディレクトリはスクリプトエディタのオプションで設定されている場所になります。

(ここで言っているカレントディレクトリはスクリプトエディタにとってのカレントディレクトリです。HSPで作成したプログラムにも別々にカレントディレクトリが存在します、ゴッチャにならないように気を付けてください)
----------------------------------------------------

スクリプトエディターにスクリプトを表示したら、エディターのメニューバーから「HSP(P)」→「START.AXファイル作成(S)」をクリックします。すると 「START.AXを作成しました」というメッセージが表示されるのでOKを押します。
これで保存したスクリプトと同じフォルダに start.ax というファイルが作られます。

20070528220814.png

20070528220822.png

これは一体何をしたのかと言うと、スクリプトをHSPが理解しやすい言葉に変換したのです。hsp のスクリプトは人間が見て分かりやすい言葉として書かれた物なのでそのままだと、HSP にとっては無駄も多く効率が悪い物なのです。そのため一度、HSP に扱いやすい言葉で書かれた、オブジェクトファイル(拡張子 AX)に翻訳してから実行ファイルを作るのです。(ax ファイルはその時エディターに表示されている物を元に作られます、保存時の状態ではないので注意)

実はその AX ファイルもHSPが理解できるだけでそのままではコンピューターに理解する事は出来ません、なのでHSPの実行ファイルは更にそこからパソコンに分かる言葉に変換しながら実行します。
(簡単に説明するために、だいぶん私の主観が入ってます)

さて次はパックファイルの編集です。
パックファイルの編集画面を出すには、エディタのメニューバーから「ツール(T)」->「PACKFILE編集(E)...」をクリックします。

20070707043515.png

20070707050740.png

ここでは実行ファイルに埋め込みたいファイルを指定します。
埋め込みたいファイルとは、プログラムの中で使っている画像ファイルなど、そのプログラムを実行するのに必要なファイルです。
(ファイルを外から見られてよければ、無理にパックする必要はありません。)

さっき作っておいた START.AX はプログラムを実行するのに必要なファイルなので、ここで指定しパックファイルに追加します。(ファイル名を start.ax 以外に変えてしまうと実行ファイルが起動しなくなるので変えてはいけません)

ここでいくつか注意があるのですが、パックするファイルの名前は15文字以内の半角文字じゃないといけないという決まりがあるので、日本語を使う事は出来ません(15文字以内と言うのは、ファイル名と拡張子と その間の . も全て含めて15文字です)。また半角スペースを使う事も出来ません。

あと初心者によくある間違がスクリプトファイル(as ファイル、hsp ファイル)を一緒にパックしてしまう事です。AX ファイル自体に翻訳したスクリプトが全て含まれているので、スクリプトファイルは普通パックしません。(検索で調べたら無駄な物をパックするほどパフォーマンスが悪くなるそうです)

それとパックファイルはフォルダの階層に対応していないので、パックするファイルは全てスクリプトと同じフォルダに入れて置かなくてはいけません。

ではパックファイルの画面を見て行きましょう。
画面左側にはカレントディレクトリの内容が表示れ、画面右側にはパックしたファイル一覧が表示されます。

パックファイルに追加するには左側の画面から追加したいファイルを選んでダブルクリックするか中央にある「追加 >>」ボタンを押すだけです。すると追加したファイルが右画面に表示されるようになります。
逆に追加したファイルを解除したい場合は、解除したいファイルを右画面から選んで画面中央の「削除 ->」ボタンを押せばOKです。

「全追加 >>」ボタンは左画面の内容を全てパックファイルに追加する物で、「全削除 ->」ボタンは逆に右画面に追加しているファイルを全てクリアする物です、名前の通りなので使って見れば分かると思います。

「暗号化」チェックは、暗号をかけて埋め込んだファイルを簡単には解読出来なくする物で、チェックした状態で項目を追加するとファイル名の先頭に + マークが付き、そのファイルは実行ファイル作成時に自動的に暗号化さます。

「キャンセル」ボタンはパックファイル編集画面を「開いてからの変更」を取り消します。

今回は start.ax ファイルだけで良いので start.ax を追加して「閉じる」ボタンを押せばパックファイル作成は完了です。

20070529014453.png

20070529014502.png

これでスクリプトのあるフォルダ(カレントディレクトリ)に packfile が作成されます。


それではかんじんの EXEファイルを作成しましょう。
実行ファイルを作成するためのウィンドウを出すには、エディタのメニューバーから「ツール(T)」->「EXEファイル作成(M)...」をクリックします。

20070707074419.png

20070707074425.png


設定画面の一番上の入力ボックスには、作成する実行ファイルの名前を入れます。これは日本語でも構いませんが、文字数は半角で182文字、全角で91文字までです。(182バイト)

「ウィンドウ初期サイズ」はプログラムを起動した時に表示されるウィンドウの大きさです。(これはプログラムから変える事も出来ます)

「特殊な設定」内の「起動時にメインウィンドウを非表示にする」はウィンドウを表示させたくない時にチェックを入れます。(非表示にしてもプログラムから表示する事が出来ます)

「起動時のディレクトリ移動を無効にする」は少し分かり難いですが、HSPはプログラムを実行した時、実行したプログラムのカレントディレクトリを実行ファイルのあるフォルダに設定するのですが、これにチェックが入っているとそれを行いません。
例えばショートカットから起動した時、ショートカットの作業フォルダに指定したパスを、カレントディレクトリにしたい時にチェックを入れます。ショートカットの設定をしないで普通に起動した場合はマイドキュメントがカレントディレクトリになります。(良く分からない人はチェックしないで下さい、不具合の元です)

最後に「OK」ボタンを押せば、この設定と start.ax ファイルと パックファイル の情報を元に exe ファイルが作成されます。カレントディレクトリに実行ファイルが作成されているはずですので正しく作成出来ているかを実行し確認して見ましょう。
  • [HSP超入門] ネストレベル2

前回ネストレベルに付いて書いた時、
repeat の後は必ず continue か break か loop でループを抜ける、
gosub の後は同じように return で戻るらなければいけないと説明しましたが、これらの基本を理解する事で変則的ですが repeat や gosub 中でも goto を使う事が出来る様になります。

どういう事かというとネストは単に深さを知るためだけの物ではないと言う事です。たとえば gosub はどんなに離れた場所へジャンプしようと return を実行すれば必ず元の呼び出された場所に戻って来なければいけません、そのためには元の呼び出し元の位置を記憶しておく必要があります。HSPの内部では単なる重なりだけでなくそれぞれの呼び出し元の位置も一緒に記憶しているのです。
それでなにが分かるかと言うとネスト1にはネスト1の呼び出し元がネスト2にはネスト2の呼びだし元がそれぞれ存在している事になります、そして return を使えばそれに応じた呼び出し元の位置に戻って来る事が出来ます。

と言う事は gosub でサブルーチンジャンプした後に goto でジャンプしたとしても、その goto の先で return を使えば正確に元の位置へ戻してくれると言う事です。
では実際に試してみましょう。
実行するとネストレベルを画面に表示しますが、ずっと1のまま増えていない事が確認出来ます。まさに思った通りの動作で大成功です。

repeat
gosub *label1
wait 50
loop

*label1
goto *label2
stop

*label2
mes sublev
mes looplev
return


このサンプルでは、メインループから gosub で *label1 へサブルーチンジャンプし更にそこから goto で *label2 へジャンプして、その label2 の中から return を実行しています。
さて、この return は一体どこに戻っているのでしょう、return は呼び出し元に戻す命令です、それなら goto のある7行目でしょうか? って違いますね! return のネストに対応した gosub 命令のある2行目に戻って来ています。なので8行目にある stop は実行されません。
(上のサンプルは repeat を使っていますが、もちろん *main ~ goto でも大丈夫です)

repeat 内のからの goto も同じように実行する事が出来ます。

repeat
wait 50
goto *label
loop

repeat
*label
mes sublev
mes looplev
loop


ちょっと見にくいですが、考え方はさっきと同じです。
ただ違うのは *label を repeat loop で囲んでいる事です。repeat loop は単独で存在できない(エラーが出る)でのこれをしないと実行出来ません。もちろん continue や break も出来ます。しかし break の場合は元の場所には戻りません。どうやら自分から見て下方向にある loop を抜けようとするようです。
それともう1つ、今回の場合 wait は goto より上にないと無限ループになりますので注意。

でもちょっとくらい無理が聞くからってこういうのはダメです。

*main
gosub *label
wait 50
goto *main

*label
repeat
if cnt=1 {
mes sublev
mes looplev
return
}
loop


gosub で跳んで repeat に入り適当な条件によって return で元の gosub のあった2行目に戻って来れているので一見これで良い様に見えますが、reoeat を loop,continue,break 以外で離れループから抜け気っていないのでネストレベルが貯まって行きます。

これもダメです。

repeat
wait 50
goto *label1
loop

*label1
gosub *label2

repeat
*label2
mes sublev
mes looplev
loop


1行目の repeat に対しての 13行目の loop は効いていますが、7行目の gosub に対しての return がありませんので sublev のネストレベルが貯まってしまいます。

今回内部の動きを理解するために、こういう無茶で変則的な荒業を紹介しましたが、プログラムが見にくく改良し辛くなる可能性が高くなってしまうので、実行出来るからと言って実験的プログラム以外ではこの様な書き方はなるべくしない方がいいでしょう
  • [HSP超入門] ネストレベル1

ここで、今までに覚えてきた repeat と gosub の注意点に付いて触れておきたいと思います。
それはネストレベルに付いてです。
ネストは前に説明したので分かると思いますが、自分の中に自分と同じ構造の物が入っている状態の事です。
それでネストレベルとは何かと言うと、ネストがいくつ重なっているかを表す物で、ネストの深さとも言われます。
例えば、この下のスクリプトの「ここは?」と書かれた場所のネストレベルは1になります。

repeat 10
mes "ここは?"
loop


そしてこの下のスクリプトの「ここは?」と書かれた場所のネストレベルは2になります。

repeat 10
repeat 10
mes "ここは?"
loop
loop


今やったのは repeat でのネストレベルでしたが、gosub にもネストレベルがあります。
またまた例で見てみましょう。
ここは?と書かれた所のネストレベルは1です。

gosub *label1
stop
*label1
mes "ここは?"
return


そして下のスクリプトでは、ここは?と書かれた所のネストレベルは2になります。

gosub *label1
stop
*label1
gosub *label2
return
*label2
mes "ここは?"
return


もちろん次の様なスクリプトでのネストレベルは0です。

mes "ここは?"


さてここからが本題です、はじめにも言いましたがネストレベルに付いて注意する点があります。
それは何かと言うと、HSPではネストレベルに限界があって、
repeat で31、gosub で126 のネストレベルを超えてはいけないのです。
もしもこれを超えるとエラーが出てしまいます。

前に gosub の説明をした時「gosub は往復切符だから return で戻らないと戻りの切符がどんどんたまって行ってしまう」と書きましたが実はそれがネストレベルだったのです。
gosub命令では gosub が実行された時にネストレベルが1つ上がり、return命令が実行される事で始めてネストレベルが1つ下がります。ですので gosub でジャンプしたにもかかわらず return を使わずに戻ってきてしまうと、ネストレベルが増えたきり下がりません、そしてこれが何回も繰り返されネストレベルの上限を超えてしまった時にHSPが「なんかおかしい事になってるよ」と言う具合でエラーを出します。

repeat命令の時も同じで repeat が実行された時にネストレベルが1つ上がり、loop命令、continue命令、break命令 のどれかで loop を抜けて始めてネストレベルが1つ下がります。ですので repeat を実行したのに loop,continue,break を使わないでループを抜ける様なプログラムを書いてしまうとネストレベルが下がらなくなり、これが繰り返され上限を超えてしまうとHSPがエラーを出します。

これまで「ネストレベル」と一言で片付けて来ましたが、repeat のネストレベルと gosub のネストレベルは別々に管理されていて、お互いのネストレベルが混ざってしまう事はありません。

実際に確かめて見ましょう。
HSPには現在のネストレベルが分かる便利な システム変数が用意されていますので、それを使って見てみる事にします。

まずは looplev です。
looplev は現在の repeat のネストレベルを格納しているシステム変数です。

次に sublev です。
sublev は現在の gosub のネストレベルを格納しているシステム変数です。

(「現在の」と言うのは そのシステム変数が使われた瞬間です)

まず repeat のネストを確認してみます。
ちゃんと loop がありますがその前に goto があって最初の *a に飛ばしているのでこの loop は実行されません。そのためネストレベルが増え続けていきます。

*a
repeat
mes "ループレベル="+looplev+" サブレベル="+sublev
wait 100
goto *a
loop


下のサンプルは gosub のネストを確認します。
4行目の gosub でラベル *b に飛ばした後 return を実行したいところですが、その前にある goto で *a 戻ってしまっているので、永遠に return は実行されません、そのためネストレベルが増え続けます。

*a
mes "ループレベル="+looplev+" サブレベル="+sublev
wait 100
gosub *b
goto *a

*b
goto *a
return


最後に repeat と gosub のネストレベルを同時に確認して見ます。

*a
repeat
mes "ループレベル="+looplev+" サブレベル="+sublev
wait 100
gosub *b
loop

*b
goto *a
return


こうして見ると、repeat のネストレベルと gosub のネストレベルが別の物だという事がはっきり分かりますね、なれない内はこの両方を意識しながらプログラムを書いていくのは大変かもしれませんが「repeat の後は loop か continue か break から抜ける」、「gosub を使ったら return で戻る」という基本的な事をしっかりやっていれば、これらが原因で起こる不具合に悩まされる事は少なくなるでしょう。
と言うよりもこの事実を知っていると言う事が重要なのです。
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。