/*
 *	clippzlu.c
 *
 *	PuZzLe game UTiLity
 *
 *	* Sat Jul 15 23:41:47 JST 2017 Naoyuki Sawa
 *	- 1st リリース。
 *	  まずは、PzlUtl_GetRgn()だけを作成しました。
 *	  ぷよぷよタイプの落ち物パズルの消滅判定を行う関数です。
 *	* Mon Jul 17 22:58:52 JST 2017 Naoyuki Sawa
 *	- PzlUtl_GetLin()を追加しました。
 *	  コラムスタイプの落ち物パズルの消滅判定を行う関数です。
 */
#include "clip.h"
//*****************************************************************************
//	ローカル関数
//*****************************************************************************
static int PzlRgn_compar(const void* _x, const void* _y) {
	const ST_PzlRgn* x = _x;
	const ST_PzlRgn* y = _y;
	if(x->len  > y->len ) { return -1; }	//┬①セルの数の降順とする。
	if(x->len  < y->len ) { return  1; }	//┘
	if(x->head < y->head) { return -1; }	//┬②セルの数が同じならば、最初のセル番号の昇順とする。
	if(x->head > y->head) { return  1; }	//┘
	DIE();	//一つのセルが、複数の領域に属する事は無いので、少なくとも②の比較で判断出来るはずです。もしここへ到達したら、当モジュール(clippzlu.c)のバグです。
}
//*****************************************************************************
//	グローバル関数
//*****************************************************************************
//フィールドの領域情報を取得する。
//[in]
//	aRgn[w*h]	領域情報を格納するバッファ。
//	aNext[w*h]	領域のチェインを格納するバッファ。
//	w,h		フィールドの列数と行数。			1≦w*h≦65535
//	diag		ラベリングにおける隣接判定の方法。		0=4近傍,1=8近傍			例えば、ぷよぷよならば0、テキパキならば1です。
//	minLen		同色のセルが何個以上隣接した領域を取得するか。					例えば、ぷよぷよならば4、テキパキならば5です。		もし、隣接数に関係無く、全ての領域を取得したい場合は、1以下の値を指定して下さい。(1以下ならばどんな値でも同じです。)
//	fnGetColor	セルの色を返すアプリケーション定義の関数。	0=空セル,1～255=色番号		戻り値のbit31～8は無視されます。戻り値のbit31～8が無視される事を前提として、0～255の範囲外の値を返しても構いません。		実際には空セルでなくても、隣接に影響しないセルに対しては、空セル(0)を返して下さい。例えば、対戦ぱずるだまにおける「こだま」等です。
//	user_data	上記の関数に引き渡す任意のデータ。
//[out]
//	戻り値		領域の数。	0～w*h
//[note]
//	- 領域情報は、下記のフィールドに格納されます。
//	  aRgn[0～領域の数-1].len		各領域の、チェインの長さ(=この領域に属するセルの数)	1～w*hの範囲の値を取ります。チェインを辿って数える事も出来ますが、チェインを辿らずに長さを取得出来る方が便利だと思うので、チェインの長さも明示的に格納しておく事にしました。
//	  aRgn[0～領域の数-1].head		各領域の、最初のセル番号。				セル番号は(w*y+x)で表されます。0～w*h-1の範囲の値を取ります。
//	  aNext[0～w*h-1]			各セルの、同じ領域に属する次のセル番号へのチェイン。	セル番号は(w*y+x)で表されます。1～w*h-1の範囲の値(セル番号)、又は、0で終端します。セル番号0が領域に属する場合はaRgn[?].headに格納され、aNext[?]には格納され得ないからです。
//	- 当関数を作成した目的は、ぷよぷよタイプの落ち物パズルで良く必要になる、同色のセルが隣接した領域の情報を取得する処理を、汎用的に利用出来るようにするためです。
//	  一般的には、再帰処理で作成する事が多いと思うのですが、それでは、偶然たくさんの同色セルが一気に隣接した時に、スタックが溢れる危険が有ります。
//	  そこで、再帰処理は使用せずに、既存の画像のラベリング処理関数(image_labeling())を利用して、領域の情報を作成するようにしました。
//	  領域情報の形式(aRgn,aNext)は、落ち物パズルで使い易いように設計したので、アプリケーションの観点からは、当関数が内部処理としてimage_labeling()を利用している事を、意識する必要は有りません。
int PzlUtl_GetRgn(ST_PzlRgn aRgn[/*(w*h)*/],
                  uint16_t aNext[/*(w*h)*/],
                  int w, int h,
                  int diag,
                  int minLen,
                  int (*fnGetColor)(int x, int y, void* user_data),
                  void* user_data) {
	//────────────────────────────────────────────
	//当関数は、引数で指定されたバッファを、前半処理と後半処理とで、別々の用途で使い回します。
	//引数で指定されたバッファの、各部分の、前半処理と後半処理における用途は、下図の通りです。
	//
	//	前半処理での用途			後半処理での用途
	//	┌──┐				┌──┐
	//	│　　│				│　　│
	//	│　　│				│　　│
	//	│　　│				│　　│
	//	│　　│uint16_t parents[(w*h)]		│　　│
	//	│　　│				│　　│
	//	│　　│				│　　│
	//	│　　│				│　　│
	//	├──┤				│　　│ST_PzlRgn aRgn[(w*h)]
	//	│　　│				│　　│
	//	│　　│uint8_t image[(w*h)]		│　　│
	//	│　　│				│　　│
	//	├──┤				│　　│
	//	│　　│				│　　│
	//	│　　│不使用				│　　│
	//	│　　│				│　　│
	//	└──┘				└──┘
	//	┌──┐				┌──┐
	//	│　　│				│　　│
	//	│　　│				│　　│
	//	│　　│				│　　│
	//	│　　│uint16_t label[(w*h)]		│　　│uint16_t aNext[(w*h)]
	//	│　　│				│　　│
	//	│　　│				│　　│
	//	│　　│				│　　│
	//	└──┘				└──┘
	//────────────────────────────────────────────
	int regions, x, y, n = w * h;
	if((n < 1) || (n > 65535)) { DIE(); }
	//前半処理
	{
		uint16_t* parents = (uint16_t*)aRgn;
		uint8_t*  image   = (uint8_t*)&parents[n];
		for(y = 0; y < h; y++) {
			for(x = 0; x < w; x++) {
				*image++ = (*fnGetColor)(x, y, user_data);	//このセルの色を取得する。(0=空セル,1～255=色番号)
			}
		}
		image -= n;
		regions = image_labeling(image,					//ラベリング処理を行う。
		                         w,
		                         h,
		                         aNext/*label*/,
		                         diag,
		                         parents,
		                         n/*parents_max*/);
		if((regions < 1) || (regions > n)) { DIE(); }			//(parents_max≦w*h)であるはずなので、ワークエリア不足(image_labeling()の戻り値が0)は起こらないはずです。また、領域の数が(w*h)を超える事も有り得ません。万一、ここで停止したら、clipimg.c,又は,当モジュール(clippzlu.c)のバグです。
	}
	//後半処理
	{
		int i, j, t;
		memset(aRgn, 0, sizeof(ST_PzlRgn) * n);				//後半処理でのaRgnは、前半処理でのparents,及び,imageと同じ領域を共用していますが、後半処理ではparentsもimageも参照しないので、クリアして問題有りません。
		for(i = n - 1; i >= 0; i--) {					//全てのセルを、後ろから前へ走査して…			この処理順序は必須です。必須である理由は、この下のコメントを参照して下さい。
			t = aNext[i]/*label[i]*/;				//┐
			    aNext[i] = aRgn[t].head;				//├この領域のチェインの先頭に、このセルを追加する。	逆順で処理し先頭に追加しているので、各チェインのセル番号の並びは必ず昇順になります。セル番号0は最後に処理するので、必ずaRgn[t].headに格納され、aNext[i]には格納されません。従って、aNext[i]における値0は、チェイン終端を意味する値0と見なせます。
			               aRgn[t].head = i;			//┘
			               aRgn[t].len++;				//─この領域のチェインの長さを増やす。	チェインの終端は'!aNext[i]'でも判断出来るので、チェインを辿って長さを求める事も出来ますが、直接、長さを取得出来る方が便利だと思ったので、チェインの長さも明示的に格納しておく事にしました。
		}
		qsort(aRgn, regions, sizeof aRgn[0], PzlRgn_compar);		//領域情報を、セルの数の降順(セルの数が同じならば、最初のセル番号の昇順)でソートする。		最初のセル番号の昇順でソートする事は、必須ではありません。しかし、そうしておく方が安定性が増して、アプリケーションからも使い易いし、デバッグもし易いと思ったので、そうする事にしました。
		j = 0;
		for(i = 0; i < regions; i++) {
			if(aRgn[i].len < minLen) { break; }			//この領域に属するセルの数が、指定された隣接数未満ならば、これ以降の領域も全てそうなので、処理を打ち切って構わない。
			x = aRgn[i].head % w;
			y = aRgn[i].head / w;
			t = (*fnGetColor)(x, y, user_data);			//この領域の色を取得する。(0=空セル,1～255=色番号)
			if(t) {							//空セルの領域でなければ…
				aRgn[j++] = aRgn[i];				//指定された隣接数以上，かつ，空セルでない領域だけを、前詰めで詰め直す。
			}
		}
		regions = j;							//領域の数を、詰め直した領域の数に更新する。
	}
	return regions;								//領域の数を返す。(0～w*h)
}
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#if 0
//□PzlUtl_GetRgn()のテストスイート
#define W	16	//┬変更可
#define H	32	//│
#define COLORS	8	//│
#define MINLEN	4	//┘
uint8_t field[H][W];
ST_PzlRgn aRgn[W*H];
uint16_t aNext[W*H];
static int fnGetColor(int x, int y, void* user_data) {
	if((unsigned)x >= (unsigned)W) { DIE(); }	//┬フィールド外の座標に対して、当関数が呼び出される事は無いはず。
	if((unsigned)y >= (unsigned)H) { DIE(); }	//┘
	return field[y][x];
}
static void test() {
	int x, y, diag, n, i, len, t, c;
	for(y = 0; y < H; y++) {
		for(x = 0; x < W; x++) {
			field[y][x] = rand() % COLORS;	//フィールドの各セルに対して、(0=空セル,1～255=色番号)を格納する。
		}
	}
	for(diag = 0; diag <= 1; diag++) {	//(0=4近傍,1=8近傍)の両方でテストする。
		n = PzlUtl_GetRgn(aRgn, aNext, W, H, diag, MINLEN, fnGetColor, NULL);	//フィールドの領域情報を取得する。
		for(i = 0; i < n; i++) {	//各領域について…
			len = aRgn[i].len;	//この領域に属するセルの数。
			t   = aRgn[i].head;	//この領域に属する最初のセル番号(=x+y*w)。
			if(i) {							//二つ目以降の領域ならば…
				if(len >  aRgn[i - 1].len) { DIE(); }		//セルの数が前の領域と同じ,又は,降順であること。
				if(len == aRgn[i - 1].len) {			//セルの数が前の領域と同じならば…
					if(t <= aRgn[i - 1].head) { DIE(); }	//最初のセル番号が昇順であること。
				}
			}
			x = t % W;
			y = t / W;
			c = fnGetColor(x, y, NULL);		//最初のセルの色(=この領域の色)を取得する。
			if(!c) { DIE(); }			//空セルの領域は格納されていないはず。
			for(;;) {
				if(--len < 0) { DIE(); }	//セルの数を減らして、まだ0未満でないこと。
				printf("%3d ", t);
				x = t % W;
				y = t / W;
				if(fnGetColor(x, y, NULL) != c) { DIE(); }	//このセルの色を取得して、この領域の色と同じであること。
				if(!aNext[t]) { break; }	//チェイン終端ならば、ループを抜ける。
				if(t >= aNext[t]) { DIE(); }	//チェインのセル番号の並びが昇順であること。
				t = aNext[t];
			}
			if(len) { DIE(); }	//領域情報から取得したセルの数と、チェインを辿ったセルの数が、一致していること。
			puts("");
		}
	}
}
int app_main(int argc, char* argv[]) {
	for(;;) { test(); }
}
#endif
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#if 0
//□PzlUtl_GetRgn()の具体的な使用例
//│「対戦ぱずるだま」の消滅ルールのデモです。
//│操作は出来ないので、ブロックが偶然揃って、消滅・落下するのを見るだけです。
#define COLS		6		//フィールドの列数
#define ROWS		12		//フィールドの行数
#define COLORS		3/*5*/		//色数
#define MINLEN		3		//隣接数
#define KODAMA		0x80		//こだまフラグ
#define KODAMA_RATE	40/*50*/	//こだま出現率
uint8_t field[ROWS][COLS];
int fnGetColor(int col, int row, void* user_data) {
	if((unsigned)col >= (unsigned)COLS) { DIE(); }
	if((unsigned)row >= (unsigned)ROWS) { DIE(); }
	return (field[row][col] & KODAMA) ? 0/*こだまは隣接に影響しない*/ : field[row][col];
}
void field_draw() {	//フィールドを描画する。
	int col, row, x, y, c;
	for(row = 0; row < ROWS; row++) {
		for(col = 0; col < COLS; col++) {
			x = ((DISP_X - ((4 + 2) * COLS)) / 2) + ((4 + 2) * col);
			y = ((DISP_Y - ((5 + 2) * ROWS)) / 2) + ((5 + 2) * row);
			render_rectangle(&render, x, y, 4 + 3, 5 + 3, 2);
			c = field[row][col];
			if(c) { render_printf(&render, x + 2, y + 2, 3, (c & KODAMA) ? 1 : 3, "%d", c & ~KODAMA); }
		}
	}
}
int field_gen() {	//フィールドの最上段にセルを生成する。
	int col, c, flag = 0;
	for(col = 0; col < COLS; col++) {
		if(!field[0][col]) {
			c = RND32_RANGE(seed, 1, COLORS + 1);
			if(c && (RND32_RANGE(seed, 0, 100) < KODAMA_RATE)) { c |= KODAMA; }
			field[0][col] = c;
			flag = 1;
		}
	}
	return flag;
}
int field_fall() {	//セルを落下する。
	int col, row, flag = 0;
	for(row = ROWS - 1; row; row--) {
		for(col = 0; col < COLS; col++) {
			if(!field[row][col] && field[row - 1][col]) {
				field[row][col] = field[row - 1][col];
				                  field[row - 1][col] = 0;
				flag = 1;
			}
		}
	}
	return flag;
}
int field_erase() {	//おおだまの消滅とこだまの変化を行う。
	int col, row, n, i, t;
	ST_PzlRgn aRgn[COLS*ROWS];
	uint16_t aNext[COLS*ROWS];
	n = PzlUtl_GetRgn(aRgn, aNext, COLS, ROWS, 0, MINLEN, fnGetColor, NULL);
	for(i = 0; i < n; i++) {
		t = aRgn[i].head;
		do {
			col = t % COLS;
			row = t / COLS;
			field[row][col] = 0;
			if(col >        0) { field[row][col - 1] &= ~KODAMA; }
			if(col < COLS - 1) { field[row][col + 1] &= ~KODAMA; }
			if(row >        0) { field[row - 1][col] &= ~KODAMA; }
			if(row < ROWS - 1) { field[row + 1][col] &= ~KODAMA; }
			t = aNext[t];
		} while(t);
	}
	return n;
}
void wait(int i) {
	while(--i >= 0) {	//少し待つ。
		schedule();
		field_draw();
	}
}
int app_main(int argc, char* argv[]) {
	for(;;) {
		memset(field, 0, sizeof field);
		wait(SEC(0.5));
		for(;;) {
			if(!field_gen()) { break; }	//ブロックを生成出来なければ(埋まったら)最初から。
			wait(SEC(0.5));
			for(;;) {
				for(;;) {
					schedule();
					field_draw();
					if(!field_fall()) { break; }	//落下出来なくなったら消滅判定へ。
					wait(SEC(0.1));
				}
				wait(SEC(0.5));
				if(!field_erase()) { break; }	//消滅が発生しなければ次のブロック生成へ。
				wait(SEC(0.5));
			}
		}
	}
}
#endif
//-----------------------------------------------------------------------------
//フィールドの並び情報を取得する。
//[in]
//	aRgn[w*h]	並び情報を格納するバッファ。
//	w,h		フィールドの列数と行数。			1≦w*h≦65535
//	dir		並び方向。					0=水平,1=垂直,2=降斜,3=昇斜
//	minLen		同色のセルが何個以上隣接した領域を取得するか。					例えば、コラムスならば3です。		もし、隣接数に関係無く、全ての空セル以外を取得したい場合は、1以下の値を指定して下さい。(1以下ならばどんな値でも同じです。)
//	fnGetColor	セルの色を返すアプリケーション定義の関数。	0=空セル,1～255=色番号		戻り値のbit31～8は無視されます。戻り値のbit31～8が無視される事を前提として、0～255の範囲外の値を返しても構いません。		実際には空セルでなくても、隣接に影響しないセルに対しては、空セル(0)を返して下さい。例えば、花組対戦コラムスおける「時宝石」等です。
//	user_data	上記の関数に引き渡す任意のデータ。
//[out]
//	戻り値		領域の数。	0～w*h
//[note]
//	- 領域情報は、下記のフィールドに格納されます。
//	  aRgn[0～領域の数-1].len		各領域の、並びの長さ(=この領域に属するセルの数)		1～w*hの範囲の値を取ります。
//	  aRgn[0～領域の数-1].head		各領域の、最初のセル番号。				セル番号は(w*y+x)で表されます。0～w*h-1の範囲の値を取ります。
//	- 当関数を作成した目的は、コラムスタイプの落ち物パズルで良く必要になる、同色のセルが隣接した領域の情報を取得する処理を、汎用的に利用出来るようにするためです。
//	  ぷよぷよタイプと違って、コラムスタイプの消滅判定は普通に作っても再帰処理にはならず単純なので、アプリケーション毎に個別に作っても構わないのですが、PzlUtl_GetRgn()と似た関数インターフェイスで利用出来ると便利だと思ったので、PzlUtl_GetLin()として実装する事にしました。
//	  PzlUtl_GetRgn()と違って、PzlUtl_GetLin()は画像のラベリング処理関数(image_labeling())を利用しておらず、全ての処理を当モジュール内で実装しています。
//	- PzlUtl_GetRgn()は領域に方向の概念が無いので、一回の呼び出しで全ての領域情報が取得出来ました。
//	  それに対して、PzlUtl_GetLin()は並び方向の概念が有るので、全ての領域情報を取得するには、dirを0～3に変えて四回呼び出す必要が有る事に注意して下さい。
//	  別の案として、PzlUtl_GetLin()内でdir=0～3のループを回して、一気に全方向の領域方向を返すようにする事も考えたのですが、そうはしませんでした。
//	  もしそうすると、アプリケーションが用意すべきバッファの容量が'aRgn[(w*h)*4]'になってしまい、必要なメモリ容量が増えてしまうからです。
//	  一般的には、呼び出し側で個々の並び方向に対して順番に処理しても問題無いと思うので、呼び出し側でdirを0～3に変えて四回呼び出す仕様としました。(ただし実際にクリアするのは全ての方向の消滅判定を行った後で行う必要が有ります。具体的には「PzlUtl_GetLin()の具体的な使用例」内のfield_erase()の処理を参照して下さい。)
//	  ちなみに、縦横にしか並ばないルールならば(そういうゲームの実例が思い浮かびませんでしたが)、dirを0～1に変えて二回呼び出すだけでも構いません。
//	- PzlUtl_GetRgn()と違って、aNext[]引数は有りません。
//	  PzlUtl_GetLin()では、領域の起点(aRgn[?].head)，隣接数(aRgn[?].len)，呼び出し時に指定した並び方向(dir)の情報だけで、その領域に含まれるセルが特定出来るので、チェイン情報は不要だからです。
int PzlUtl_GetLin(ST_PzlRgn aRgn[/*(w*h)*/],
                  int w, int h,
                  int dir,
                  int minLen,
                  int (*fnGetColor)(int x, int y, void* user_data),
                  void* user_data) {
	int n = w * h;
	if(((n < 1) || (n > 65535)) || ((unsigned)dir > 3)) { DIE(); }
	if(minLen < 1) { minLen = 1; }	//(minLen<1)が指定された場合は(minLen=1)に補正する。	PzlUtl_GetRgn()との互換性のためにこうする事にしました。PzlUtl_GetRgn()のアルゴリズムでは(minLen<1)のままでも結果的に(minLen>=1)が指定されたのと同じ結果となるので明示的な補正は不要だったのですが、PzlUtl_GetLin()のアルゴリズムでは(minLen<1)のままだと(ST_PzlRgn.len=0)の無意味な領域も残ってしまうのでそうならないよう明示的な補正が必要です。
	{
		static const int8_t v[/*dir*/][2/*vx,vy*/]={{-1,0},{0,-1},{-1,-1},{1,-1}};	//方向⇒隣接する直前のセルへの座標オフセット
		int x1 = 0;			//走査中の現在のセルの列番号
		int x2 = v[dir][0/*vx*/];	//隣接する直前のセルの列番号
		int y1 = 0;			//走査中の現在のセルの行番号
		int y2 = v[dir][1/*vy*/];	//隣接する直前のセルの行番号
		int i1 = 0;			//走査中の現在のセルのセル番号
		int i2 = ((w * y2) + x2);	//隣接する直前のセルのセル番号
		do {
			do {
				int c1, c2;
				aRgn[i1].len  = 0;							//仮に、現在のセルを起点とする隣接数を0としておく。もし、本当に現在のセルを起点として領域が開始した場合は、この後の処理で+1される。もし、現在のセルが空か,又は,直前のセルを起点とする領域に含まれていた場合は、0のままとなる。
				aRgn[i1].head = i1;							//仮に、現在のセルを含む領域の起点を、現在のセル自身(=現在のセルが領域の起点)としておく。もし、現在のセルが空でなく,かつ,直前(又はそれ以前)のセルを起点とする領域に含まれていた場合は、この後の処理で実際に起点となるセル番号に置き換えられる。
				c1 = (uint8_t)((*fnGetColor)(x1, y1, user_data));			//現在のセルの色を取得する。	戻り値を(uint8_t)にキャストしている訳は、戻り値のbit31～8を無視するためです。アルゴリズム上は必須ではありませんが、判り易さのためにPzlUtl_GetRgn()の仕様と合わせるためにこうする事にしました。
				if(c1) {								//現在のセルが空でなければ…
					if(((unsigned)x2 < (unsigned)w) &&				//┬直前のセルがフィールド内ならば…
					   ((unsigned)y2 < (unsigned)h)) {				//┘
						c2 = (uint8_t)((*fnGetColor)(x2, y2, user_data));	//直前のセルの色を取得する。	戻り値を(uint8_t)にキャストしている訳は、戻り値のbit31～8を無視するためです。アルゴリズム上は必須ではありませんが、判り易さのためにPzlUtl_GetRgn()の仕様と合わせるためにこうする事にしました。
						if(c1 == c2) {						//現在のセルと直前のセルが同じ色ならば…
							aRgn[i1].head = aRgn[i2].head;			//現在のセルを含む領域の起点を、実際に起点となるセル番号に置き換える。	'aRgn[i1].head=i2'としないよう注意せよ。直前のセル自身が起点であるとは限らないからです。
						}
					}
					aRgn[aRgn[i1].head].len++;					//現在のセルを含む領域の起点の、隣接数を+1する。	領域の起点以外のセルの隣接数は0のままとなります。そうすることによって、この後の処理で領域の起点以外のaRgn[]要素は削除出来ます。
				}
				i1++;									//┬セル番号を次へ進める。
				i2++;									//┘
				x1++;									//┬列番号を次へ進める。
				x2++;									//┘
			} while(x1 < w);								//走査中の現在のセルの列番号が右端に到達したら、ループを抜ける。
			x1 -= w;									//┬列番号を巻き戻す。
			x2 -= w;									//┘
			y1++;										//┬行番号を次へ進める。
			y2++;										//┘
		} while(y1 < h);									//走査中の現在のセルの行番号が下端に到達したら、ループを抜ける。
	}
	{
		int i = 0, j = 0;
		do {
			if(aRgn[i].len >= minLen) {		//この領域に属するセルの数が、指定された隣接数以上ならば…
				aRgn[j++] = aRgn[i];		//前詰めで詰め直す。	PzlUtl_GetRgn()のアルゴリズムでは後半処理で空セルの判断を行っていましたが、PzlUtl_GetLin()のアルゴリズムでは前半処理で空セルを判断して除外済みなので、ここで空セルを判断する必要は有りません。
			}
		} while(++i < n);
		qsort(aRgn, j, sizeof aRgn[0], PzlRgn_compar);	//領域情報を、セルの数の降順(セルの数が同じならば、最初のセル番号の昇順)でソートする。		最初のセル番号の昇順でソートする事は、必須ではありません。しかし、そうしておく方が安定性が増して、アプリケーションからも使い易いし、デバッグもし易いと思ったので、そうする事にしました。
		return j;					//領域の数を返す。(0～w*h)
	}
}
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#if 0
//□PzlUtl_GetLin()のテストスイート
#define W	16	//┬変更可
#define H	32	//│
#define COLORS	8	//│
#define MINLEN	3	//┘
uint8_t field[H][W];
ST_PzlRgn aRgn[W*H];
static int fnGetColor(int x, int y, void* user_data) {
	if((unsigned)x >= (unsigned)W) { DIE(); }	//┬フィールド外の座標に対して、当関数が呼び出される事は無いはず。
	if((unsigned)y >= (unsigned)H) { DIE(); }	//┘
	return field[y][x];
}
static void test() {
	int x, y, dir, n, i, j, len, t, c;
	for(y = 0; y < H; y++) {
		for(x = 0; x < W; x++) {
			field[y][x] = rand() % COLORS;	//フィールドの各セルに対して、(0=空セル,1～255=色番号)を格納する。
		}
	}
	for(dir = 0; dir <= 3; dir++) {		//(0=水平,1=垂直,2=降斜,3=昇斜)の並び方向について…
		n = PzlUtl_GetLin(aRgn, W, H, dir, MINLEN, fnGetColor, NULL);	//フィールドの領域情報を取得する。
		for(i = 0; i < n; i++) {	//各領域について…
			len = aRgn[i].len;	//この領域に属するセルの数。
			t   = aRgn[i].head;	//この領域に属する最初のセル番号(=x+y*w)。
			if(i) {							//二つ目以降の領域ならば…
				if(len >  aRgn[i - 1].len) { DIE(); }		//セルの数が前の領域と同じ,又は,降順であること。
				if(len == aRgn[i - 1].len) {			//セルの数が前の領域と同じならば…
					if(t <= aRgn[i - 1].head) { DIE(); }	//最初のセル番号が昇順であること。
				}
			}
			x = t % W;
			y = t / W;
			c = fnGetColor(x, y, NULL);	//最初のセルの色(=この領域の色)を取得する。
			if(!c) { DIE(); }		//空セルの領域は格納されていないはず。
			for(j = 0; j < len; j++) {
				static const int8_t v[][2]={{1,0},{0,1},{1,1},{-1,1}};
				printf("(%2d,%2d) ", x, y);
				if(fnGetColor(x, y, NULL) != c) { DIE(); }	//このセルの色を取得して、この領域の色と同じであること。
				x += v[dir][0];
				y += v[dir][1];
			}
			puts("");
		}
	}
}
int app_main(int argc, char* argv[]) {
	for(;;) { test(); }
}
#endif
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#if 0
//□PzlUtl_GetLin()の具体的な使用例
//│「コラムス」の消滅ルールのデモです。(本物はお邪魔ブロックは有りませんが…)
//│操作は出来ないので、ブロックが偶然揃って、消滅・落下するのを見るだけです。
#define COLS		6		//フィールドの列数
#define ROWS		12		//フィールドの行数
#define COLORS		3/*6*/		//色数
#define MINLEN		3		//隣接数
#define ERASE		0x80		//消滅フラグ
#define OJAMA		0x7F		//お邪魔ブロック
#define OJAMA_RATE	40/*50*/	//お邪魔ブロック出現率
uint8_t field[ROWS][COLS];
int fnGetColor(int col, int row, void* user_data) {
	int c;
	if((unsigned)col >= (unsigned)COLS) { DIE(); }
	if((unsigned)row >= (unsigned)ROWS) { DIE(); }
	c = field[row][col] & ~ERASE;
	return (c == OJAMA) ? 0/*お邪魔ブロックは隣接に影響しない*/ : c;
}
void field_draw() {	//フィールドを描画する。
	int col, row, x, y, c;
	for(row = 0; row < ROWS; row++) {
		for(col = 0; col < COLS; col++) {
			x = ((DISP_X - ((4 + 2) * COLS)) / 2) + ((4 + 2) * col);
			y = ((DISP_Y - ((5 + 2) * ROWS)) / 2) + ((5 + 2) * row);
			render_rectangle(&render, x, y, 4 + 3, 5 + 3, 2);
			c = field[row][col];
			if(c) {
				if(c == OJAMA) {
					render_printf(&render, x + 2, y + 2, 3, 1, "%c", '?');
				} else {
					render_printf(&render, x + 2, y + 2, 3, 3, "%d",  c );
				}
			}
		}
	}
}
int field_gen() {	//フィールドの最上段にセルを生成する。
	int col, c, flag = 0;
	for(col = 0; col < COLS; col++) {
		if(!field[0][col]) {
			c = RND32_RANGE(seed, 1, COLORS + 1);
			if(c && (RND32_RANGE(seed, 0, 100) < OJAMA_RATE)) { c = OJAMA; }
			field[0][col] = c;
			flag = 1;
		}
	}
	return flag;
}
int field_fall() {	//セルを落下する。
	int col, row, flag = 0;
	for(row = ROWS - 1; row; row--) {
		for(col = 0; col < COLS; col++) {
			if(!field[row][col] && field[row - 1][col]) {
				field[row][col] = field[row - 1][col];
				                  field[row - 1][col] = 0;
				flag = 1;
			}
		}
	}
	return flag;
}
int field_erase() {	//ブロックの消滅を行う。
	int dir, len, col, row, n, i, flag = 0;
	ST_PzlRgn aRgn[COLS*ROWS];
	for(dir = 0; dir <= 3; dir++) {
		n = PzlUtl_GetLin(aRgn, COLS, ROWS, dir, MINLEN, fnGetColor, NULL);
		for(i = 0; i < n; i++) {
			len = aRgn[i].len;
			col = aRgn[i].head % COLS;
			row = aRgn[i].head / COLS;
			do {
				static const int8_t v[][2]={{1,0},{0,1},{1,1},{-1,1}};
				field[row][col] |= ERASE;	//他の方向の消滅判定にも使うのでまだクリアしてはいけない。
				if((col >        0) && (field[row][col - 1] == OJAMA)) { field[row][col - 1] |= ERASE; }
				if((col < COLS - 1) && (field[row][col + 1] == OJAMA)) { field[row][col + 1] |= ERASE; }
				if((row >        0) && (field[row - 1][col] == OJAMA)) { field[row - 1][col] |= ERASE; }
				if((row < ROWS - 1) && (field[row + 1][col] == OJAMA)) { field[row + 1][col] |= ERASE; }
				col += v[dir][0];
				row += v[dir][1];
			} while(--len);
		}
	}
	for(row = 0; row < ROWS; row++) {
		for(col = 0; col < COLS; col++) {
			if(field[row][col] & ERASE) {
				field[row][col] = 0;
				flag = 1;
			}
		}
	}
	return flag;
}
void wait(int i) {
	while(--i >= 0) {	//少し待つ。
		schedule();
		field_draw();
	}
}
int app_main(int argc, char* argv[]) {
	for(;;) {
		memset(field, 0, sizeof field);
		wait(SEC(0.5));
		for(;;) {
			if(!field_gen()) { break; }	//ブロックを生成出来なければ(埋まったら)最初から。
			wait(SEC(0.5));
			for(;;) {
				for(;;) {
					schedule();
					field_draw();
					if(!field_fall()) { break; }	//落下出来なくなったら消滅判定へ。
					wait(SEC(0.1));
				}
				wait(SEC(0.5));
				if(!field_erase()) { break; }	//消滅が発生しなければ次のブロック生成へ。
				wait(SEC(0.5));
			}
		}
	}
}
#endif
