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] 確保した変数を開放する

HSPでは一度確保した変数の領域はプログラムの終了時にまとめて開放されます。プログラムの実行中にメモリを節約するには、
    sdim buf, 256
    buf = "文字列代入"
    sdim buf
などとして使用した変数を再作成する方法があります。

更にモジュール変数を使う事でプログラム実行中に使用している変数のメモリー領域をまとめて開放することが出来ます。実際にメモリー監視ソフトでメモリーの状態を確認しながら実行すると分かりやすいでしょう。

#スクリプトは HSP3.21 で確認

// モジュール変数を利用することで、
// 確保した変数領域を完全に開放する

#module test buf

    #modinit int mega
        sdim buf, 1024 * 1024 * mega
    return

#global

#const SIZE 100

    button "確保"*GET
    button "開放"*FREE
    stop

*GET
    newmod mv, test, size
    n++
    mes strf("%d MB 確保しました(現在の確保数:%d)"SIZE, n)
    stop

*FREE
    if n > 0 {
        n--
        delmod mv.n
        mes strf("%d MB 開放しました(現在の確保数:%d)"SIZE, n)
    }
    stop
変数を普通に作成する代わりに、モジュール変数内で 使用する変数を確保しています。こうする事によって、モジュール変数を delmod すればいつでもモジュール変数内で確保された変数をまとめて解放する事が出来ます。

ただ、モジュール変数の配列の場合 全ての要素を delmod で削除しても、呼び出し元のモジュール変数の 配列の長さまでは変わらないので、配列の長さも短くしたい場合はモジュール変数を作成し直す必要があります。

#module test buf
    #modinit int mega
        sdim buf, 1024 * 1024 * mega
    return
#global
    // モジュール変数を100個作成
    repeat 100
        newmod mv, test, 1
    loop
    // モジュール変数を全て削除
    foreach mv
        delmod mv.cnt
    loop
    // モジュール変数の配列の拡張は100個のまま
    mes "モジュール変数の配列の数:" + length(mv)
    // モジュール変数を再作成
    dimtype mv, vartype("struct")
    // モジュール変数が作り直され配列が1に
    mes "モジュール変数の配列の数:" + length(mv)

この様にモジュール変数を使用する事で 手軽に変数の領域を解放する事が出来るので、マメに開放をしたいという方には良さそうです。

さらに一時期流行ったメモリ解放ソフトなんかも割と楽に作る事が出来るかもしれませんね(現在のPC環境での必要性や、そもそもの効能の是非に付いては置いといて(笑))。

モジュール変数にする事で直接変数にアクセス出来なくなる不便も有るので、何でもかんでもモジュール変数化すれば良いと言う訳では無いと思いますが、モジュール変数を開放する目的のために使うやりかたは 特質や状況を見極めればどこかしら利用する価値は有るかもしれません。
  • [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コンテストが開催されましたね。もう色々と作品が集まって来ているようです。私も何か送ろうといくつか考えて見ては居るんですが暑さも手伝ってまだまだまとまりません。
HSPコンテスト2007


そうそうTTTの変数リネームもこないだ終わりました。次は処理の見直しかぁ。

あとサイトリニューアルも少しずつやったりしてます。CSS意外と奥が深いのですね。
  • すこしCSSをいじって見た

練習がてらに、このブログのCSSを少しだけ書き換えて見ました。
大雑把に言うと、HSP講座のスクリプト表示部分と全体のリンク色とあと細かいとこ色々です。

特にスクリプト表示部分を変えたおかげで若干見易くなりました。
この調子で tytysite のリニューアルもどんどん!・・・
やっていけたらな~。
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。