/*	
 *	cliplotr.c
 *
 *	抽選表ROM形式
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2015-2016 Naoyuki Sawa
 *
 *	* Wed Feb 18 21:49:13 JST 2015 Naoyuki Sawa
 *	- 1st リリース。
 *	- 当モジュールは、cliplot.cモジュールのラッパーとして動作し、以下の機能を追加します。
 *	  1. 抽選表を、アドレスでなく抽選表番号で指定する、関数インターフェイスを追加します。
 *	  2. 各抽選表の各列の各当選値が、何回当選したかをログとして記録する機能を追加します。
 *	     ログ機能は、LotRom_BeginLog()〜LotRom_EndLog()の間のみ有効となります。
 *	     LotRom_BeginLog()を呼び出さなければ、余分なメモリ消費も処理負荷も生じません。
 *	* Mon Mar 02 21:47:20 JST 2015 Naoyuki Sawa
 *	- ST_LotRom構造体のm_LotTblNo_Max,m_TBL_LotTblフィールドを削除しました。LotRom_Init()の_LotTblNo_Max引数を削除しました。
 *	  やや大幅な変更になので、変更前のコードは残していません。変更前のコードを見返したい場合は、前回のアーカイブを参照してください。
 *	  dLotTblC.exeがTBL_LotTblNo[0]に(LotTblNo_Max+1)を格納するようなったので、アプリケーションから明示的に指定する必要が無くなったからです。
 *	  詳細は/clip/tool/dLotTblC/winapp.hの同日のコメントを参照してください。
 *	* Fri Sep 11 21:09:04 JST 2015 Naoyuki Sawa
 *	- LotRom_DumpLog()が標準出力以外にも出力出来るよう、ストリームを指定する引数を追加しました。
 *	* Mon Oct 12 15:21:03 JST 2015 Naoyuki Sawa
 *	- USE_LOTROM_SEHシンボルを定義すると、cliplotr.c内のDIE()を全て、SEH_throw(LotRomException)に置き換えるようにしました。
 *	  使用方法はclipprp.hのUSE_PRP_SEHと同様です。clipprp.hの2015/10/11のコメントを参照してください。
 *	* Mon Oct 12 16:53:24 JST 2015 Naoyuki Sawa
 *	- LOT_draw_table_a(),LOT_draw_table_v()の引数順序を変更した事に伴い、LotRom_draw_v()の実装も変更しました。
 *	  LOT_get_table_weight_a(),LOT_get_table_weight_v()の引数順序を変更した事に伴い、LotRom_get_weight_a(),LotRom_get_weight_v()の引数順序と実装も変更しました。
 *	- LotRom_draw_l(),LotRom_draw_a()をアセンブラ化しました。
 *	  LotRom_get_weight_l(),LotRom_get_weight_a()をアセンブラ化しました。
 *	* Wed Oct 14 21:50:46 JST 2015 Naoyuki Sawa
 *	- clipseh.hにてSEH_info構造体に'msg'フィールドを追加した事に伴い、当モジュールのLotRom_throw()の実装も変更しました。
 *	* Fri Nov 06 21:43:13 JST 2015 Naoyuki Sawa
 *	- '_equally'版の関数を追加しました。
 *	  '_equally'版の関数は、抽選データのテスト用であり、高速化は不要だと思うので、アセンブラ化していません。	⇒{{2016/07/17追記:'_equally'版の関数をアセンブラ化しました。}}
 *	  詳細は、cliplot.hの同日のコメントを参照して下さい。
 *	- cliplot.hのUSE_LOT_EQUALLYシンボル定義の有無を参照して、当モジュールの関数宣言,及び,'_equally'版の関数のビルドを切り替えます。
 *	  Win32プロジェクトは、cliplotr.hよりも先に、cliplot.hをインクルードして下さい。
 *	  P/ECEプロジェクトならば、/clip/clip.h をインクルードするだけなので、インクルードの順序を気にする必要は有りません。
 *	* Sun Jul 17 22:34:56 JST 2016 Naoyuki Sawa
 *	- '_equally'版の関数をアセンブラ化しました。
 *	  今回アセンブラ化した関数は、LotRom_draw_equally_l(),LotRom_draw_equally_a()です。
 *	  LotRom_draw_equally_v()は、USE_LOTROM_LOGの条件コンパイルが有りアセンブラ化しづらいので、今回はアセンブラ化していません。
 *	* Sat Jul 23 21:58:20 JST 2016 Naoyuki Sawa
 *	- 抽選表ログの構造体を廃止し、ワークシート(多階層ハッシュテーブル)を利用するように変更しました。
 *	  この変更によってログ機能の実装が大幅に単純になり、もし今後ログ機能を変更する時も変更し易くなったと思います。
 *	  尚、変更前は抽選列毎の抽選回数を記録していましたが、変更後はその代わりに非当選回数を記録するようにしました。
 *	  特定の抽選列の階層以下に対してWorksheet_sum_l()を行えば、変更前と同様に抽選列毎の抽選回数を得ることも可能です。
 *	- LotRom_DumpLog()の抽選ログ出力形式を変更しました。
 *	  変更前は、抽選表を転置したような表形式で出力していましたが、実際には、あまり使いみちがありませんでした。
 *	  変更後は、「当選回数,抽選表番号(1〜65535),当選値(-1〜65535)[,抽選列id(0〜65535)[,抽選列id(0〜65535)...]」という形式になりました。
 *	  そうした理由は、dLotTblC.exeのクロスリファレンスとの照合を、行い易くするためです。
 *	  両者の違いは、クロスリファレンスは一列目が参照表現であるのに対し、LotRom_DumpLog()の抽選ログは一列目が当選回数である事のみです。
 *	  クロスリファレンスと抽選ログを照合し、クロスリファレンスの全ての参照表現の箇所に、抽選ログの当選回数(一致する抽選ログが無ければ0と見なす)を書き込めば、取り込み元の抽選表をそのまま集計表の雛形として利用出来るはずです。
 *	  2016/07/23現在ではまだ、上記の処理を行うツールを作成していませんが、そのうち作成しようと思っています。	←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←※TODO:
 *	  尚、抽選ログには当選値として非当選(-1)の回数も記録していますが、クロスリファレンスには該当する行は存在しないので、無視して構いません。
 *	* Mon Sep 05 21:24:05 JST 2016 Naoyuki Sawa
 *	- 64ビット版では、例外コードをアドレスでなく、一意なFourCC識別子で表すようにしました。
 *	  setjmp()/longjmp()のパラメータは(int)であり、(void*)や(intptr_t)を使えないからです。
 *	  尚、32ビット版も64ビット版と共通に変更しても良いのですが、万一既存のコードに影響が生じる事を防ぐために、32ビット版はこれまで通りとしました。
 */
#include "clip.h"
//{{2015/10/12追加:USE_LOTROM_SEHシンボルを定義すると、cliplotr.c内のDIE()を全て、SEH_throw(LotRomException)に置き換えるようにしました。
#ifdef  USE_LOTROM_SEH
//USE_LOTROM_SEHシンボルが定義されたら、cliplotr.c内のDIE()を全て、SEH_throw(LotRomException)に置き換えます。
#undef  DIE
#ifndef PIECE
  #define DIE() SEH_throw(LotRomException)
#else //PIECE
  //P/ECEでも上のように定義しても良いのですが、メモリ節約のため下のように定義する事にしました。結果は同じになります。
  //以下の定義はclipseh.hのSEH_throw()の定義に依存しています。もし今後SEH_throw()を変更する時は以下も変更して下さい。
  #define DIE() do { LotRom_throw(__FILE__, __LINE__); } while(0)
  static void __attribute__((noreturn)) LotRom_throw(const char* file, int line) {
    SEH_info.file = file;
    SEH_info.line = line;
    SEH_info.msg  = NULL;
    SEH_longjmp((int)LotRomException); }
#endif//PIECE
//{{2016/09/05変更:64ビット版では、例外コードをアドレスでなく、一意なFourCC識別子で表すようにしました。
//const char LotRomException[1/*dummy*/];
//↓2016/09/05変更:64ビット版では、例外コードをアドレスでなく、一意なFourCC識別子で表すようにしました。
#ifndef _WIN64
const char LotRomException[1/*dummy*/];	//32ビット版
#endif//_WIN64
//}}2016/09/05変更:64ビット版では、例外コードをアドレスでなく、一意なFourCC識別子で表すようにしました。
#endif//USE_LOTROM_SEH
//}}2015/10/12追加:USE_LOTROM_SEHシンボルを定義すると、cliplotr.c内のDIE()を全て、SEH_throw(LotRomException)に置き換えるようにしました。
/****************************************************************************
 *	ローカル変数定義
 ****************************************************************************/
static ST_LotRom stLotRom;
/****************************************************************************
 *	ローカル関数宣言
 ****************************************************************************/
static const unsigned short* LotRom_GetLotTbl(int LotTblNo);
#ifdef  USE_LOTROM_LOG
static void LotRom_AddLog(int LotTblNo, const int* pid, int hitval);
#endif//USE_LOTROM_LOG
/****************************************************************************
 *	
 ****************************************************************************/
//抽選表番号に対応する、抽選表へのポインタを取得する。
static const unsigned short* LotRom_GetLotTbl(int LotTblNo) {
	const int nLotTblNo = stLotRom.m_TBL_LotTblNo[0];				//(抽選表番号の最大値+1)を取得する。
	const unsigned short* pLotTbl;
	if((LotTblNo < 1) || (LotTblNo >= nLotTblNo)) { DIE(); }			//抽選表番号が有効範囲である事を確認する。
	pLotTbl = (const unsigned short*)&stLotRom.m_TBL_LotTblNo[nLotTblNo];		//抽選表データへのポインタを求める。(抽選表アドレステーブルの直後に抽選表データを配置する前提)
	return &pLotTbl[stLotRom.m_TBL_LotTblNo[LotTblNo]];				//抽選表へのポインタを返す。
}
/*--------------------------------------------------------------------------*/
void LotRom_Init(const int* _TBL_LotTblNo/*[LotTblNo_Max+1]*/, int (*_fnRnd)(/*void* fnRndArg*/), void* _fnRndArg) {
	LotRom_Free();									//既に、抽選表ROM管理が開始していたら、一旦、抽選表ROM管理を終了する。
	stLotRom.m_TBL_LotTblNo	= _TBL_LotTblNo;					//抽選表アドレステーブルへのポインタを格納する。
	stLotRom.m_fnRnd	= _fnRnd;						//乱数ジェネレータ関数へのポインタを格納する。
	stLotRom.m_fnRndArg	= _fnRndArg;						//乱数ジェネレータ関数への引数を格納する。
}
/*--------------------------------------------------------------------------*/
//抽選表ROM管理を終了する。
void LotRom_Free() {
#ifdef  USE_LOTROM_LOG
	LotRom_EndLog();								//確実に、ログを終了する。
#endif//USE_LOTROM_LOG
	memset(&stLotRom, 0, sizeof stLotRom);						//抽選表ROM管理をクリアする。
}
/*--------------------------------------------------------------------------*/
//抽選表の、指定された抽選列を用いて、抽選を行います。
#ifndef PIECE
int LotRom_draw_l(int LotTblNo, ...) {
	int retval;
	va_list ap;
	va_start(ap, LotTblNo);
	retval = LotRom_draw_a(LotTblNo, ap);						//ログはこの中で行う。
	va_end(ap);
	return retval;
}
int LotRom_draw_a(int LotTblNo, va_list ap) {
	int id[(7+1)/*調整可*/], i = 0;							//実用上、抽選列のid行数が7以下(終端含め8以下)と考えて充分だろう。不足した場合は増やせ。
	for(;;) {
		if((id[i] = va_arg(ap, int)) == -1) { break; }
		if(++i >= ARRAY_SIZE(id)) { DIE(); }					//ここで停止した場合は、id[]不足か,又は,呼出し側が終端を忘れている。おそらく後者だろう。
	}
	return LotRom_draw_v(LotTblNo, id);						//ログはこの中で行う。
}
#else //PIECE
int LotRom_draw_l(int LotTblNo, ...);
int LotRom_draw_a(int LotTblNo, va_list ap);
asm("
		.code
		.align		1
		.global		LotRom_draw_l
		.global		LotRom_draw_a
LotRom_draw_l:
		;//[%sp+0] := retp
		;//[%sp+4] := LotTblNo
		;//[%sp+8] := ...
		xld.w		%r12, [%sp+4]						;//%r12 := LotTblNo
		xadd		%r13, %sp, 8						;//%r13 := ap = &...
LotRom_draw_a:
		;//%r12 := LotTblNo
		;//%r13 := ap
		xjp		LotRom_draw_v						;//return LotRom_draw_v(LotTblNo, pid)
");
#endif//PIECE
//{{2015/10/12変更:LOT_draw_table_a(),LOT_draw_table_v()の引数順序を変更した事に伴い、LotRom_draw_v()の実装も変更しました。
//int LotRom_draw_v(int LotTblNo, const int* pid) {
//	int retval = LOT_draw_table_v(LotRom_GetLotTbl(LotTblNo), pid, (unsigned short)(*stLotRom.m_fnRnd)(stLotRom.m_fnRndArg));		//乱数ジェネレータ関数は32bit乱数を返しても良い。当関数が16bit乱数に切り詰める。
//#ifdef  USE_LOTROM_LOG
//	LotRom_AddLog(LotTblNo, pid, retval);						//抽選表ROM管理が開始していたら、抽選結果をログに記録する。
//#endif//USE_LOTROM_LOG
//	return retval;
//}
//↓2015/10/12変更:LOT_draw_table_a(),LOT_draw_table_v()の引数順序を変更した事に伴い、LotRom_draw_v()の実装も変更しました。
int LotRom_draw_v(int LotTblNo, const int* pid) {
	int retval = LOT_draw_table_v(LotRom_GetLotTbl(LotTblNo), (unsigned short)(*stLotRom.m_fnRnd)(stLotRom.m_fnRndArg), pid);		//乱数ジェネレータ関数は32bit乱数を返しても良い。当関数が16bit乱数に切り詰める。
#ifdef  USE_LOTROM_LOG
	LotRom_AddLog(LotTblNo, pid, retval);						//抽選表ROM管理が開始していたら、抽選結果をログに記録する。
#endif//USE_LOTROM_LOG
	return retval;
}
//}}2015/10/12変更:LOT_draw_table_a(),LOT_draw_table_v()の引数順序を変更した事に伴い、LotRom_draw_v()の実装も変更しました。
//{{2015/11/06追加:'_equally'版の関数を追加しました。
#ifdef  USE_LOT_EQUALLY
#ifndef PIECE
int LotRom_draw_equally_l(int LotTblNo, ...) {
	int retval;
	va_list ap;
	va_start(ap, LotTblNo);
	retval = LotRom_draw_equally_a(LotTblNo, ap);					//ログはこの中で行う。
	va_end(ap);
	return retval;
}
int LotRom_draw_equally_a(int LotTblNo, va_list ap) {
	int id[(7+1)/*調整可*/], i = 0;							//実用上、抽選列のid行数が7以下(終端含め8以下)と考えて充分だろう。不足した場合は増やせ。
	for(;;) {
		if((id[i] = va_arg(ap, int)) == -1) { break; }
		if(++i >= ARRAY_SIZE(id)) { DIE(); }					//ここで停止した場合は、id[]不足か,又は,呼出し側が終端を忘れている。おそらく後者だろう。
	}
	return LotRom_draw_equally_v(LotTblNo, id);					//ログはこの中で行う。
}
#else //PIECE
int LotRom_draw_equally_l(int LotTblNo, ...);
int LotRom_draw_equally_a(int LotTblNo, va_list ap);
asm("
		.code
		.align		1
		.global		LotRom_draw_equally_l
		.global		LotRom_draw_equally_a
LotRom_draw_equally_l:
		;//[%sp+0] := retp
		;//[%sp+4] := LotTblNo
		;//[%sp+8] := ...
		xld.w		%r12, [%sp+4]						;//%r12 := LotTblNo
		xadd		%r13, %sp, 8						;//%r13 := ap = &...
LotRom_draw_equally_a:
		;//%r12 := LotTblNo
		;//%r13 := ap
		xjp		LotRom_draw_equally_v					;//return LotRom_draw_equally_v(LotTblNo, pid)
");
#endif//PIECE
int LotRom_draw_equally_v(int LotTblNo, const int* pid) {
	int retval = LOT_draw_table_equally_v(LotRom_GetLotTbl(LotTblNo), (unsigned short)(*stLotRom.m_fnRnd)(stLotRom.m_fnRndArg), pid);	//乱数ジェネレータ関数は32bit乱数を返しても良い。当関数が16bit乱数に切り詰める。
#ifdef  USE_LOTROM_LOG
	LotRom_AddLog(LotTblNo, pid, retval);						//抽選表ROM管理が開始していたら、抽選結果をログに記録する。
#endif//USE_LOTROM_LOG
	return retval;
}
#endif//USE_LOT_EQUALLY
//}}2015/11/06追加:'_equally'版の関数を追加しました。
/*--------------------------------------------------------------------------*/
//抽選表の、指定された抽選列の、指定された当選値の、重み値を取得します。
//{{2015/10/12変更:LOT_get_table_weight_a(),LOT_get_table_weight_v()の引数順序を変更した事に伴い、LotRom_get_weight_a(),LotRom_get_weight_v()の引数順序と実装も変更しました。
//int LotRom_get_weight_l(int LotTblNo, int hitval_sel, ...) {
//	int retval;
//	va_list ap;
//	va_start(ap, hitval_sel);
//	retval = LotRom_get_weight_a(LotTblNo, ap, hitval_sel);
//	va_end(ap);
//	return retval;
//}
//int LotRom_get_weight_a(int LotTblNo, va_list ap, int hitval_sel) {
//	return LOT_get_table_weight_a(LotRom_GetLotTbl(LotTblNo), ap, hitval_sel);
//}
//int LotRom_get_weight_v(int LotTblNo, const int* pid, int hitval_sel) {
//	return LOT_get_table_weight_v(LotRom_GetLotTbl(LotTblNo), pid, hitval_sel);
//}
//↓2015/10/12変更:LOT_get_table_weight_a(),LOT_get_table_weight_v()の引数順序を変更した事に伴い、LotRom_get_weight_a(),LotRom_get_weight_v()の引数順序と実装も変更しました。
#ifndef PIECE
int LotRom_get_weight_l(int LotTblNo, int hitval_sel, ...) {
	int retval;
	va_list ap;
	va_start(ap, hitval_sel);
	retval = LotRom_get_weight_a(LotTblNo, hitval_sel, ap);
	va_end(ap);
	return retval;
}
int LotRom_get_weight_a(int LotTblNo, int hitval_sel, va_list ap) {
	return LOT_get_table_weight_a(LotRom_GetLotTbl(LotTblNo), hitval_sel, ap);
}
#else //PIECE
int LotRom_get_weight_l(int LotTblNo, int hitval_sel, ...);
int LotRom_get_weight_a(int LotTblNo, int hitval_sel, va_list ap);
asm("
		.code
		.align		1
		.global		LotRom_get_weight_l
		.global		LotRom_get_weight_a
LotRom_get_weight_l:
		;//%r12    := LotTblNo
		;//[%sp+0] := retp
		;//[%sp+4] := hitval_sel
		;//[%sp+8] := ...
		xld.w		%r13, [%sp+4]						;//%r13 := hitval_sel
		xadd		%r14, %sp, 8						;//%r14 := ap = &...
LotRom_get_weight_a:
		;//%r12    := LotTblNo
		;//%r13    := hitval_sel
		;//%r14    := ap
		xjp		LotRom_get_weight_v					;//return LotRom_get_weight_v(LotTblNo, hitval_sel, pid)	C言語版はLOT_get_table_weight_a()を呼び出していますがアセンブラ版はLotRom_get_weight_v()を呼び出します。結果は同じです。
");
#endif//PIECE
int LotRom_get_weight_v(int LotTblNo, int hitval_sel, const int* pid) {
	return LOT_get_table_weight_v(LotRom_GetLotTbl(LotTblNo), hitval_sel, pid);
}
//}}2015/10/12変更:LOT_get_table_weight_a(),LOT_get_table_weight_v()の引数順序を変更した事に伴い、LotRom_get_weight_a(),LotRom_get_weight_v()の引数順序と実装も変更しました。
/*--------------------------------------------------------------------------*/
//抽選表の、列数を取得します。
int LotRom_get_size(int LotTblNo) {
	return LOT_get_table_size(LotRom_GetLotTbl(LotTblNo));
}
/*--------------------------------------------------------------------------*/
//抽選表の、抽選列のid行数を取得します。
int LotRom_get_id_size(int LotTblNo) {
	return LOT_get_table_id_size(LotRom_GetLotTbl(LotTblNo));
}
/*--------------------------------------------------------------------------*/
//抽選表の、(i_column)番目の抽選列の、idの組合せを取得します。
void LotRom_get_column_id(int LotTblNo, int i_column, int* pid) {
	LOT_get_table_column_id(LotRom_GetLotTbl(LotTblNo), i_column, pid);
}
/****************************************************************************
 *	ログ機能
 ****************************************************************************/
#ifdef  USE_LOTROM_LOG
//{{2016/07/23削除:抽選表ログの構造体を廃止し、ワークシート(多階層ハッシュテーブル)を利用するように変更しました。
////抽選表ログ
//typedef struct _ST_LotRom_TblLog {
//	struct _ST_LotRom_ColLog*	TBL_ColLog/*[抽選表の列数]*/;			//抽選列ログ
//} ST_LotRom_TblLog;
////抽選列ログ
//typedef struct _ST_LotRom_ColLog {
//	unsigned			    LotCnt;					//抽選回数
//	unsigned*			TBL_HitCnt/*[抽選列の要素数]*/;			//当選回数	(LotCnt-SUM(TBL_HitCnt))=非当選回数
//} ST_LotRom_ColLog;
//}}2016/07/23削除:抽選表ログの構造体を廃止し、ワークシート(多階層ハッシュテーブル)を利用するように変更しました。
/*--------------------------------------------------------------------------*/
//ログを開始する。
void LotRom_BeginLog() {
//{{2016/07/23変更:抽選表ログの構造体を廃止し、ワークシート(多階層ハッシュテーブル)を利用するように変更しました。
//	const int nLotTblNo = stLotRom.m_TBL_LotTblNo[0];				//(抽選表番号の最大値+1)を取得する。
//	int LotTblNo, table_size, id_size, i_column, column_size;
//	LotRom_EndLog();								//既に、ログが開始していたら、一旦、ログを終了する。
//	stLotRom.m_TBL_TblLog = calloc(nLotTblNo, sizeof(ST_LotRom_TblLog*));		//抽選表ログのメモリを確保する。
//	if(!stLotRom.m_TBL_TblLog) { DIE(); }						//メモリ不足
//	for(LotTblNo = 1; LotTblNo < nLotTblNo; LotTblNo++) {
//		ST_LotRom_TblLog* pTblLog = &stLotRom.m_TBL_TblLog[LotTblNo];
//		const unsigned short* table = LotRom_GetLotTbl(LotTblNo);
//		table_size = *table++;							//抽選表の列数
//		id_size    = *table++;							//抽選列のid行数
//		pTblLog->TBL_ColLog = calloc(table_size, sizeof(ST_LotRom_ColLog));	//抽選列ログのメモリを確保する。
//		if(!pTblLog->TBL_ColLog) { DIE(); }					//メモリ不足
//		for(i_column = 0; i_column < table_size; i_column++) {
//			ST_LotRom_ColLog* pColLog = &pTblLog->TBL_ColLog[i_column];
//			table += id_size;						//抽選列のid
//			column_size = *table++;						//抽選列の要素数
//			table += column_size * 2/*{hitval,weight}*/;			//抽選列の要素
//			pColLog->TBL_HitCnt = calloc(column_size, sizeof(unsigned));	//当選回数のメモリを確保する。
//			if(!pColLog->TBL_HitCnt) { DIE(); }				//メモリ不足
//		}
//	}
//↓2016/07/23変更:抽選表ログの構造体を廃止し、ワークシート(多階層ハッシュテーブル)を利用するように変更しました。
	LotRom_EndLog();								//既に、ログが開始していたら、一旦、ログを終了する。
	stLotRom.m_wsLog = Worksheet_new();						//ワークシートを作成する。
//}}2016/07/23変更:抽選表ログの構造体を廃止し、ワークシート(多階層ハッシュテーブル)を利用するように変更しました。
}
/*--------------------------------------------------------------------------*/
//ログを出力する。
//{{2016/07/23追加:抽選表ログの構造体を廃止し、ワークシート(多階層ハッシュテーブル)を利用するように変更しました。
static void LotRom_DumpLog_subr(FILE* fp, ST_Worksheet* ws, int id[(2+7)/*調整可*/], int depth) {
	ST_Worksheet_Iterator it;
	int HitCnt, key, i;
	//このノードに当選回数が格納されているか?
	HitCnt = Worksheet_get_l(ws, -1);
	//当選回数が格納されていなければ…
	if(!HitCnt) {
		if(depth >= (2+7)/*調整可*/) { DIE(); }					//もしここで停止したら当モジュールのバグか、抽選表ログのメモリが壊れています。抽選列のid行数制限はLotRom_AddLog()で検査しているので、ここで検出される事はないはずだからです。
		//子階層の各ノードについて…
		for(key = Worksheet_first_l(ws, &it, -1);
		    key != -1;
		    key = Worksheet_next(&it)) {
			ST_Worksheet* pChild = Worksheet_ref_l(ws, key, -1);
			switch(depth) {
			case 0:  if((key < 1) || (key > 65535)) { DIE(); } break;	//抽選表番号(1〜65535)	┐
			case 1:  if((key < 0) || (key > 65536)) { DIE(); } break;	//当選値(0〜65536)	├もしここで停止したら多分LotRom_AddLog()のバグです。
			default: if((key < 0) || (key > 65535)) { DIE(); } break;	//抽選列id(0〜65535)	┘
			}
			//格納されている値が65536ならば、非当選を示す当選値(-1)に戻す。
			if(key == 65536) { key = -1; }					//LotRom_AddLog()において、非当選を示す当選値以外に65536が格納される事は無いはずなので、65536ならば(depth==1)を検査せずに無条件に-1に置き換えて安全である。
			//抽選表番号(1〜65535),又は,当選値(-1〜65535),又は,抽選列id(0〜65535)を格納する。
			id[depth] = key;
			//子階層のノードへ再帰する。
			LotRom_DumpLog_subr(fp, pChild, id, depth + 1);
		}
	//当選回数が格納されていたら…
	} else {
		//当モジュールの使用方法では、ワークシートの途中のノードには、当選回数は格納されないはずである。
		if(Worksheet_first_l(ws, &it, -1) != -1) { DIE(); }			//もしここで停止したらLotRom_AddLog()のバグか、又は、アプリケーションが不正な抽選列idを指定してLotRom_AddLog()を呼び出した可能性が有る。例えば、同じ抽選表の抽選列id=(1,2)と抽選列(1,2,3)に対して、LotRom_AddLog()が呼び出された場合等です。同じ抽選表に対して、抽選列idは即時復号可能であるはず(というか抽選列id行数は等しいはず)なので、左記のような抽選列idを指定してLotRom_AddLog()を呼び出す事は明らかに誤りです。そのような呼び出しが行われると抽選表ログの構造が不正となり、LotRom_DumpLog()が呼び出された時点でここで停止する事になります。
		//当選回数を出力する。
		fprintf(fp, "%d", HitCnt);
		//id[]には少なくとも、抽選表番号と当選値が格納されているはずである。
		if(depth < 2) { DIE(); }						//もしここで停止したら多分LotRom_AddLog()のバグです。
		//抽選表番号(1〜65535),当選値(-1〜65535)[,抽選列id(0〜65535)[,抽選列id(0〜65535)...] を出力する。
		for(i = 0; i < depth; i++) {
			key = id[i];
			fprintf(fp, "\t%d", key);
		}
		//改行を出力する。
		fputc('\n', fp);
	}
}
//}}2016/07/23追加:抽選表ログの構造体を廃止し、ワークシート(多階層ハッシュテーブル)を利用するように変更しました。
void LotRom_DumpLog(FILE* fp) {
//{{2016/07/23変更:抽選表ログの構造体を廃止し、ワークシート(多階層ハッシュテーブル)を利用するように変更しました。
//	const int nLotTblNo = stLotRom.m_TBL_LotTblNo[0];				//(抽選表番号の最大値+1)を取得する。
//	int LotTblNo, table_size, id_size, i_column, column_size, i;
//	if(!stLotRom.m_TBL_TblLog) { DIE(); }						//ログが開始していない時に、当関数を呼び出してはいけない。
//	for(LotTblNo = 1; LotTblNo < nLotTblNo; LotTblNo++) {
//		ST_LotRom_TblLog* pTblLog = &stLotRom.m_TBL_TblLog[LotTblNo];
//		const unsigned short* table = LotRom_GetLotTbl(LotTblNo);
//		fprintf(fp, "%d\n", LotTblNo);						//出力:抽選表番号
//		table_size = *table++;							//抽選表の列数
//		id_size    = *table++;							//抽選列のid行数
//		for(i_column = 0; i_column < table_size; i_column++) {
//			ST_LotRom_ColLog* pColLog = &pTblLog->TBL_ColLog[i_column];
//			table += id_size;						//抽選列のid
//			column_size = *table++;						//抽選列の要素数
//			fprintf(fp, "%d", pColLog->LotCnt);				//出力:抽選回数
//			for(i = 0; i < column_size; i++) {
//				fprintf(fp, "\t%d\t%d", *table, pColLog->TBL_HitCnt[i]);	//出力:(TAB)当選値(TAB)当選回数
//				table += 2/*{hitval,weight}*/;				//要素を飛ばす。
//			}
//			fprintf(fp, "\n");						//出力:抽選列の改行
//		}
//		if(LotTblNo != (nLotTblNo - 1)) { fprintf(fp, "\n"); }			//出力:抽選表と抽選表の間の空行
//	}
//↓2016/07/23変更:抽選表ログの構造体を廃止し、ワークシート(多階層ハッシュテーブル)を利用するように変更しました。
	int id[(2+7)/*調整可*/];							//実用上、抽選列のid行数が7以下(抽選表番号,当選値め9以下)と考えて充分だろう。不足した場合は増やせ。
	if(!stLotRom.m_wsLog) { DIE(); }						//ログが開始していない時に、当関数を呼び出してはいけない。
	LotRom_DumpLog_subr(fp, stLotRom.m_wsLog, id, 0);				//ルートノードから再帰する。
//}}2016/07/23変更:抽選表ログの構造体を廃止し、ワークシート(多階層ハッシュテーブル)を利用するように変更しました。
}
/*--------------------------------------------------------------------------*/
//ログを終了する。
void LotRom_EndLog() {
//{{2016/07/23変更:抽選表ログの構造体を廃止し、ワークシート(多階層ハッシュテーブル)を利用するように変更しました。
//	if(stLotRom.m_TBL_TblLog) {		//ログが開始していたら…							//┐この順序を逆にしないよう注意せよ。逆にすると、最初にLotRom_Init()⇒LotRom_Free()⇒LotRom_EndLog()の
//		const int nLotTblNo = stLotRom.m_TBL_LotTblNo[0];			//(抽選表番号の最大値+1)を取得する。	//┘経路で呼び出された時に、stLotRom.m_TBL_LotTblNoがまだNULLであるため、一般保護例外が発生してしまう。
//		int LotTblNo, table_size, id_size, i_column, column_size;
//		for(LotTblNo = 1; LotTblNo < nLotTblNo; LotTblNo++) {
//			ST_LotRom_TblLog* pTblLog = &stLotRom.m_TBL_TblLog[LotTblNo];
//			const unsigned short* table = LotRom_GetLotTbl(LotTblNo);
//			table_size = *table++;						//抽選表の列数
//			id_size    = *table++;						//抽選列のid行数
//			for(i_column = 0; i_column < table_size; i_column++) {
//				ST_LotRom_ColLog* pColLog = &pTblLog->TBL_ColLog[i_column];
//				table += id_size;					//抽選列のid
//				column_size = *table++;					//抽選列の要素数
//				table += column_size * 2/*{hitval,weight}*/;		//抽選列の要素
//				free(pColLog->TBL_HitCnt);				//当選回数のメモリを開放する。
//			}
//			free(pTblLog->TBL_ColLog);					//抽選列ログのメモリを開放する。
//		}
//		free(stLotRom.m_TBL_TblLog);						//抽選表ログのメモリを開放する。
//		stLotRom.m_TBL_TblLog = NULL;						//ログが開始していない事を示す。
//	}
//↓2016/07/23変更:抽選表ログの構造体を廃止し、ワークシート(多階層ハッシュテーブル)を利用するように変更しました。
	Worksheet_free(stLotRom.m_wsLog/*NULL可*/);					//ワークシートを開放する。		//┬ログが開始していなければダミー処理となる。
	stLotRom.m_wsLog = NULL;							//ログが開始していない事を示す。	//┘
//}}2016/07/23変更:抽選表ログの構造体を廃止し、ワークシート(多階層ハッシュテーブル)を利用するように変更しました。
}
/*--------------------------------------------------------------------------*/
//抽選結果をログに記録する。
static void LotRom_AddLog(int LotTblNo, const int* pid, int hitval) {
//{{2016/07/23変更:抽選表ログの構造体を廃止し、ワークシート(多階層ハッシュテーブル)を利用するように変更しました。
//	if(stLotRom.m_TBL_TblLog) {		//ログが開始していたら…
//		ST_LotRom_TblLog* pTblLog = &stLotRom.m_TBL_TblLog[LotTblNo];		//抽選表ログへのポインタを取得する。
//		ST_LotRom_ColLog* pColLog = pTblLog->TBL_ColLog;			//最初の列の抽選列ログへのポインタを取得する。
//		const unsigned short* table = LotRom_GetLotTbl(LotTblNo);		//抽選表番号に対応する、抽選表へのポインタを取得する。
//	    //{{この範囲はcliplot.cのLOT_seek_and_call()とほぼ同じアルゴリズムです。処理内容のコメントはLOT_seek_and_call()を参照してください。
//		int table_size, id_size, column_size, n_id, diff;
//		table_size = *table++;							//抽選表の列数
//		id_size    = *table++;							//抽選列のid行数
//		for(;;) {
//			if(--table_size < 0) { DIE(); }					//指定されたidの組合せを持つ抽選列が見つからない。	LOT_draw_table_v()が成功したのにここで失敗する事は無いはず。もしここで停止したらバグです。(データバグではなく当モジュールのPGバグである可能性が高い)
//			n_id = id_size;							//抽選列のidを比較する。
//			diff = 0;
//			while(--n_id >= 0) { diff |= (*table++ - *pid++); }
//			column_size = *table++;						//抽選列の要素数
//			if(!diff) { break; }						//指定されたidの組合せを持つ抽選列が見つかったら、抜ける。
//			pid -= id_size;							//指定されたidの組合せのポインタを戻す。
//			table += column_size * 2/*{hitval,weight}*/;			//抽選列を飛ばす。
//			pColLog++;							//抽選列ログへのポインタを次の列へ進める。
//		}
//		if(*pid != -1) { DIE(); }						//指定された抽選列のidの組合せの、終端を確認する。	LOT_draw_table_v()が成功したのにここで失敗する事は無いはず。もしここで停止したらバグです。(データバグではなく当モジュールのPGバグである可能性が高い)
//	    //}}この範囲はcliplot.cのLOT_seek_and_call()とほぼ同じアルゴリズムです。処理内容のコメントはLOT_seek_and_call()を参照してください。
//		pColLog->LotCnt++;							//抽選回数を増やす。
//		if(hitval != -1) {							//非当選でなければ…
//			unsigned* pHitCnt = pColLog->TBL_HitCnt;			//最初の要素の当選回数へのポインタを取得する。
//			for(;;) {
//				if(--column_size < 0) { DIE(); }			//指定された当選値を持つ要素が見つからない。		LOT_draw_table_v()が当選したのにここで失敗する事は無いはず。もしここで停止したらバグです。(データバグではなく当モジュールのPGバグである可能性が高い)
//				if(*table == hitval) { break; }				//指定された当選値を持つ要素が見つかったら、抜ける。
//				table += 2/*{hitval,weight}*/;				//要素を飛ばす。
//				pHitCnt++;						//当選回数へのポインタを次の要素へ進める。
//			}
//			(*pHitCnt)++;							//当選回数を増やす。
//		}
//	}
//↓2016/07/23変更:抽選表ログの構造体を廃止し、ワークシート(多階層ハッシュテーブル)を利用するように変更しました。
	if(stLotRom.m_wsLog) {		//ログが開始していたら…
		int id[(2+7+1)/*調整可*/], i = 0;					//実用上、抽選列のid行数が7以下(抽選表番号,当選値,終端含め10以下)と考えて充分だろう。不足した場合は増やせ。
		//当選回数を増やす。
		// - 有効な当選値(0〜65535)が指定された場合は、id[]={抽選表番号(1〜65535),当選値(0〜65535)[,抽選列id(0〜65535)[,抽選列id(0〜65535)...],-1} とする。
		// - 非当選を示す当選値(-1)が指定された場合は、id[]={抽選表番号(1〜65535),          65536 [,抽選列id(0〜65535)[,抽選列id(0〜65535)...],-1} とする。
		if((LotTblNo <  1) || (LotTblNo > 65535)) { DIE(); }			//ここで停止した場合は、抽選表番号が不正である。呼び出し側のバグだろう。
		if((hitval   < -1) || (hitval   > 65535)) { DIE(); }			//ここで停止した場合は、当選値が不正である。呼び出し側のバグだろう。
		if( hitval  == -1) {   hitval   = 65536; }				//非当選を示す当選値(-1)が指定された場合は、65536に置き換える。
		id[i++] = LotTblNo;	//id[0]=抽選表番号
		id[i++] = hitval;	//id[1]=当選値
		for(;;) {		//id[2〜]=抽選列id
			if((id[i] = *pid++) == -1) { break; }
			if((unsigned)id[i] > 65535) { DIE(); }				//ここで停止した場合は、抽選列idが不正である。呼び出し側のバグだろう。
			if(++i >= ARRAY_SIZE(id)) { DIE(); }				//ここで停止した場合は、id[]不足か,又は,呼出し側が終端を忘れている。おそらく後者だろう。
		}
		if(!Worksheet_incr_v(stLotRom.m_wsLog, id)) {				//当選回数を増やす。
			Worksheet_set_v(stLotRom.m_wsLog, -1, id);			//当選回数がUINT_MAX⇒0に折り返したら、UINT_MAXに飽和させる事にした。当選回数がUINT_MAXとなった場合、実際にはUINT_MAX以上である事に注意せよ。
		}
	}
//}}2016/07/23変更:抽選表ログの構造体を廃止し、ワークシート(多階層ハッシュテーブル)を利用するように変更しました。
}
#endif//USE_LOTROM_LOG
