矩形敷き詰め問題

矩形(デフォルトで正方形)をなるべく隙間が出来ないように左上から敷き詰める。
画面をスプレッドシートとして扱い、空き領域を「セル」として管理する。

矩形(デフォルトで正方形)をなるべく隙間が出来ないように左上から敷き詰める。
画面をスプレッドシートとして扱い、空き領域を「セル」として管理する。

  • タグ:
  • タグはありません
/*
	矩形敷き詰め問題
	
	 表計算ソフトのシートの要領で空き領域を管理する。
	爆速だけどメモリ消費が多い(100個の矩形で6MB程)。
	左上から詰めていくから、任意の矩形を1つ置いた時に、必要に応じてその右辺と底辺の延長線でシートを分割すればよい。
	 ある矩形Sを1つ置い後、(必要に応じてシートを分割した後に)、「Sの右隣列の連続した空きセルの最も上のもの」および「Sの1つ下の列の連続した空きセルの最も左のもの」を矩形配置試行セルに加えれば良い。
*/

#cmpopt varinit 1

//#define DEBUG	//コメントアウトでデバッグモード(logmes使うので遅い)
#module
#deffunc debugmes str str_
	#ifdef DEBUG@
		logmes str_
	#endif
	return
#global

#define global TRUE		1
#define global FALSE	0
#define global SIZE_OF_INT	4
#define global INVALID_CELL_ID	-1

#define global SX_FIELD	800	//フィールド(今回は画面全体)のサイズ
#define global SY_FIELD	800

#module Cell sx, sy, used, id_right, id_lower	//セルクラス
	/*
		sx,sy : セルのサイズ
		used : 使用済みor not
		id_right, id_left	: 右隣, 直下のセルへのポインタ(モジュール変数の配列内におけるID。以下「ポインタ」はこれを意味する。)
							  隣がない場合は INVALID_CELL_ID とする
	*/
	id_=0
	#modinit int sx_, int sy_, int used_, int id_right_, int id_lower_
		sx=sx_ : sy=sy_
		used=used_
		id_right=id_right_ : id_lower=id_lower_
		mref id_,2
		return id_
	#modcfunc local get_sx
		return sx
	#modcfunc local get_sy
		return sy
	#modcfunc local get_used
		return used
	#modcfunc local get_id_right
		return id_right
	#modcfunc local get_id_lower
		return id_lower
	#modfunc local set_size int sx_, int sy_
		sx=sx_ : sy=sy_
		return
	#modfunc local set_used int used_
		used=used_
		return
	#modfunc local set_id_right int id_right_
		id_right=id_right_
		return
	#modfunc local set_id_lower int id_lower_
		id_lower=id_lower_
		return
	
	#modcfunc local is_rightEnd
		return id_right==INVALID_CELL_ID
	
	#modcfunc local is_bottom
		return id_lower==INVALID_CELL_ID
#global

#module class_try_cells num_try_cells, array_cell_id, array_dist	//矩形配置試行セルのリストのクラス。インスタンスは1個だけ。
	#modinit int first_cell_id_
		num_try_cells=1	//試行セルの個数
		array_cell_id=first_cell_id_	//試行セルのポインタの配列
		array_dist=0	//試行セルの、原点からの距離(int)の配列。昇順に並んでいる。↑の配列とリンク。
		return
	
	#modcfunc local get_num_try_cells
		return num_try_cells
	
	#modcfunc local get_try_cell int order_id_	//order_id_ 番目の試行セルのポインタを返す
		return array_cell_id(order_id_)
	
	#modcfunc local exists int id_, local return_code	//そのセルが存在するか?。
		/*
			id_ : セルのポインタ
		*/
		return_code=FALSE
		repeat num_try_cells
			if (id_==array_cell_id(cnt)) {return_code=TRUE : break}
		loop
		return return_code
	
	#modfunc local add_try_cell int cell_id_, int dist_	//試行セルの登録
		/*
			cell_id_ : 試行セルへのポインタ
			dist_ : そのセルの原点からの距離
		*/
		if (exists@class_try_cells(thismod, cell_id_)) {return}
		array_cell_id(num_try_cells)=-1 : array_dist(num_try_cells)=0	//ダミー書き込み。配列長が不足の場合に自動確保を促す。
		repeat num_try_cells+1	//挿入
			if ((dist_ < array_dist(cnt))||(cnt==num_try_cells)) {
				//場所を開ける
					memcpy array_cell_id, array_cell_id, SIZE_OF_INT*(num_try_cells-cnt), SIZE_OF_INT*(cnt+1), SIZE_OF_INT*cnt
					memcpy array_dist, array_dist, SIZE_OF_INT*(num_try_cells-cnt), SIZE_OF_INT*(cnt+1), SIZE_OF_INT*cnt
				//追加
					array_cell_id(cnt)=cell_id_ : array_dist(cnt)=dist_
					num_try_cells++
				break
			}
		loop
		return
	
	#modfunc local remove_try_cell int cell_id_	//試行セルを削除
		repeat num_try_cells
			if (array_cell_id(cnt)==cell_id_) {
				//空席を詰める
					memcpy array_cell_id, array_cell_id, SIZE_OF_INT*(num_try_cells-cnt-1), SIZE_OF_INT*cnt, SIZE_OF_INT*(cnt+1)
					memcpy array_dist, array_dist, SIZE_OF_INT*(num_try_cells-cnt-1), SIZE_OF_INT*cnt, SIZE_OF_INT*(cnt+1)
				break
			}
		loop
		num_try_cells--
		return
#global

#module	sheet_contro	//シート操作モジュール
	#deffunc init_sheet	//シート初期化
		sheet=0 : newmod sheet, Cell, SX_FIELD, SY_FIELD, FALSE, INVALID_CELL_ID, INVALID_CELL_ID	//シート全体を巨大な単セルに
		id_baseCell=stat	//左上セルのポインタ
		num_cell_x=1 : num_cell_y=1	//x,y方向のセルの数(=列数, 行数)
		try_cells=0 : newmod try_cells, class_try_cells, id_baseCell	//矩形配置試行セルリストの唯一インスタンス
		return
	
	#defcfunc get_top_cell int idx_, local id	//その列(idx_)の最上行のセル
		id=id_baseCell
		repeat idx_ : id=get_id_right@Cell(sheet(id)) : assert(id!=INVALID_CELL_ID) : loop
		return id
	
	#defcfunc get_bottom_cell int idx_, local id	//その列の最下行のセル
		id=get_top_cell(idx_)
		repeat num_cell_y-1 : id=get_id_lower@Cell(sheet(id)) : assert(id!=INVALID_CELL_ID) : loop
		return id
	
	#defcfunc get_leftEnd_cell int idy_, local id	//その行(idy_)の最左列のセル
		id=id_baseCell
		repeat idy_ : id=get_id_lower@Cell(sheet(id)) : assert(id!=INVALID_CELL_ID) : loop
		return id
		
	#defcfunc get_rightEnd_cell int idy_, local id	//その行の最右列のセル
		id=get_leftEnd_cell(idy_)
		repeat num_cell_x-1 : id=get_id_right@Cell(sheet(id)) : assert(id!=INVALID_CELL_ID) : loop
		return id
	
	#defcfunc index2id int idx_, int idy_, local id	//(列,行)からセルのポインタへ
		id=id_baseCell
		repeat idx_ : id=get_id_right@Cell(sheet(id)) : assert(id!=INVALID_CELL_ID) : loop
		repeat idy_ : id=get_id_lower@Cell(sheet(id)) : assert(id!=INVALID_CELL_ID) : loop
		return id
	
	#deffunc id2index int id_, var idx_, var idy_, local id, local cx, local cy	//セルのポインタから(列,行)へ
		id=id_ : cx=0 : cy=0
		repeat
			if (is_rightEnd@Cell(sheet(id))) {break}
			id=get_id_right@Cell(sheet(id))
			cx++
		loop
		repeat
			if (is_bottom@Cell(sheet(id))) {break}
			id=get_id_lower@Cell(sheet(id))
			cy++
		loop
		idx_=num_cell_x-cx-1 : idy_=num_cell_y-cy-1
		return
	
	#deffunc index2pos int idx_, int idy_, var x_, var y_, local id	//その(列,行)より左上の領域のサイズ
		id=id_baseCell : x_=0 : y_=0
		repeat idx_ : x_+=get_sx@Cell(sheet(id)) : id=get_id_right@Cell(sheet(id)) : loop
		repeat idy_ : y_+=get_sy@Cell(sheet(id)) : id=get_id_lower@Cell(sheet(id)) : loop
		return
	
	#defcfunc index2distance int idx_, int idy_, local x, local y	//その(列,行)の原点からの距離
		index2pos idx_,idy_, x,y
		return int(sqrt(x*x + y*y))
	
	#defcfunc id2distance int id_, local idx, local idy	//そのセルの原点からの距離
		id2index id_, idx,idy
		return index2distance(idx,idy)
		
	#deffunc div_x int idx_, int sx_new, local id	//x方向にシートを分割
		/*
			idx_ : 分割したい列。この列を縮めて右側に新しい列を挿入
			
			[方針]
				下端から作る
		*/
		//「その右に新しいセルが作られるセル達」のID。上から順に詰める。
			stack_id=get_top_cell(idx_)
			repeat num_cell_y-1, 1
				stack_id(cnt)=get_id_lower@Cell(sheet(stack_id(cnt-1)))
			loop
		
		new_cell_id=INVALID_CELL_ID
		repeat num_cell_y
			id=stack_id(num_cell_y-1-cnt)
			//新しいセル
				sy=get_sy@Cell(sheet(id))
				newmod sheet, Cell, sx_new,sy, get_used@Cell(sheet(id)), get_id_right@Cell(sheet(id)), new_cell_id
				new_cell_id=stat
			//既存のセルの調整
				set_size@Cell sheet(id), get_sx@Cell(sheet(id))-sx_new, sy
				set_id_right@Cell sheet(id), new_cell_id
		loop
		num_cell_x++
		return
	
	#deffunc div_y int idy_, int sy_new, local id, local idx, local idy	//y方向にシートを分割
		/*
			idy_ : 分割したい行。この行を縮めて下側に新しい行を挿入
			
			[方針]
				右端から作る
		*/
		//「その下に新しいセルが作られるセル達」のID。左から順に詰める。
			stack_id=get_leftEnd_cell(idy_)
			repeat num_cell_x-1, 1
				stack_id(cnt)=get_id_right@Cell(sheet(stack_id(cnt-1)))
			loop
			
		new_cell_id=INVALID_CELL_ID
		repeat num_cell_x
			id=stack_id(num_cell_x-1-cnt)
			//新しいセル
				sx=get_sx@Cell(sheet(id))
				newmod sheet, Cell, sx, sy_new, get_used@Cell(sheet(id)), new_cell_id, get_id_lower@Cell(sheet(id))
				new_cell_id=stat
			//既存のセルの調整
				set_size@Cell sheet(id), sx, get_sy@Cell(sheet(id))-sy_new
				set_id_lower@Cell sheet(id), new_cell_id
		loop
		num_cell_y++
		return
	
	#defcfunc top_continuous_free_cell int idx_, int idy_, local id, local id2, local idy	//そのセルを含む列の、そのセルと連続した空きセルのうち最上のもの
		id=index2id(idx_,idy_) : idy=idy_
		repeat idy_
			idy-- : id2=index2id(idx_,idy)
			if (get_used@Cell(sheet(id2))==TRUE) {break}
			id=id2
		loop
		return id
	
	#defcfunc leftEnd_continuous_free_cell int idx_, int idy_, local id, local id2, local idx	//そのセルを含む行の、そのセルと連続した空きセルのうち最左のもの
		id=index2id(idx_,idy_) : idx=idx_
		repeat idx_
			idx-- : id2=index2id(idx,idy_)
			if (get_used@Cell(sheet(id2))==TRUE) {break}
			id=id2
		loop
		return id
	
	#deffunc indicate_cell int id_, local idx, local idy, local x, local y	//デバッグ用。指定のセルを強調表示
		id2index id_, idx,idy
		index2pos idx,idy, x,y
		color 255,0,0
		line x,y, x+get_sx@Cell(sheet(id_))-1,y+get_sy@Cell(sheet(id_))-1
		line x+get_sx@Cell(sheet(id_))-1,y, x,y+get_sy@Cell(sheet(id_))-1
		return
	
	#deffunc draw int idx_, int idy_, int sx_, int sy_, local x, local y	//矩形の描画
		/*
			idx_,idy_ : 列,行
			sx_,sy_ : 矩形のサイズ
		*/
		color rnd(200),rnd(200),rnd(200)
		index2pos idx_,idy_, x,y
		boxf x,y, x+sx_-1, y+sy_-1
		return

	#defcfunc local place_at int id_, int sx_, int sy_, local id, local id2, local idx, local idy	//そのセルを左上として可能ならば矩形を配置する
		/*
			id_ : セルのポインタ
			sx_, sy_ : 矩形サイズ
		*/
		#ifdef DEBUG@
			if (get_used@Cell(sheet(id_))==TRUE) {
				indicate_cell id_
				logmes ""
				repeat get_num_try_cells@class_try_cells(try_cells)
					logmes get_try_cell@class_try_cells(try_cells, cnt)
				loop
			}
			assert(get_used@Cell(sheet(id_))==FALSE)
		#endif
		flg_cannot=FALSE
		//x方向の必要セル数を調べる
			num_cell_x_require=1
			id=id_ : sx_remain=sx_
			repeat
				if (get_used@Cell(sheet(id))) {flg_cannot=TRUE : break}
				sx_remain-=get_sx@Cell(sheet(id))
				if (sx_remain<=0) {break}
				num_cell_x_require++
				if (is_rightEnd@Cell(sheet(id))) {flg_cannot=TRUE : break}
				id=get_id_right@Cell(sheet(id))
			loop
			if (flg_cannot) {return FALSE}
		//y方向の必要セル数を調べる
			num_cell_y_require=1
			id=id_ : sy_remain=sy_
			repeat
				if (get_used@Cell(sheet(id))) {flg_cannot=TRUE : break}
				sy_remain-=get_sy@Cell(sheet(id))
				if (sy_remain<=0) {break}
				num_cell_y_require++
				if (is_bottom@Cell(sheet(id))) {flg_cannot=TRUE : break}
				id=get_id_lower@Cell(sheet(id))
			loop
			if (flg_cannot) {return FALSE}
		//配置
			id2index id_, idx, idy
			idx_used_cell_rightmost=idx+num_cell_x_require-1	//今回消費されるセルの中で最も右側のもののxインデックス
			idy_used_cell_bottom=idy+num_cell_y_require-1
			
			//食べ残しが発生したセルを分割
				if (sx_remain<0) {div_x idx_used_cell_rightmost, -sx_remain}
				if (sy_remain<0) {div_y idy_used_cell_bottom, -sy_remain}
	
			//使用済みセルをマーク
				id=id_
				repeat num_cell_x_require
					id2=id
					repeat num_cell_y_require
						set_used@Cell sheet(id2), TRUE
						id2=get_id_lower@Cell(sheet(id2))
					loop
					id=get_id_right@Cell(sheet(id))
				loop
			
			//新しい矩形配置試行セルの登録
				if (idx_used_cell_rightmost<num_cell_x-1) {
					id=top_continuous_free_cell(idx_used_cell_rightmost+1, idy)
					add_try_cell@class_try_cells try_cells, id, id2distance(id)
				}
				if (idy_used_cell_bottom<num_cell_y-1) {
					id=leftEnd_continuous_free_cell(idx, idy_used_cell_bottom+1)
					add_try_cell@class_try_cells try_cells, id, id2distance(id)
				}
		
			//描画
				draw idx,idy, sx_,sy_
		return TRUE
	
	#defcfunc place int sx_, int sy_, local id	//できるだけ原点に近い場所に矩形を配置する。矩形配置試行セルで片っ端から試す。どうしても無理なら FALSE を返す。
		assert((sx_>=1)&&(sy_>=1))
		debugmes "num_cell_x,num_cell_y = "+num_cell_x+","+num_cell_y
		debugmes "there are "+get_num_try_cells@class_try_cells(try_cells)+" try-cells."
		return_code=FALSE
		repeat get_num_try_cells@class_try_cells(try_cells)
			id=get_try_cell@class_try_cells(try_cells, cnt)
			if (get_used@Cell(sheet(id))) {remove_try_cell@class_try_cells try_cells, id : continue}	//もしも矩形配置試行セルが使用済みになっているのを見つけたら削除しておく。これ重要
			debugmes "trying to use cell "+id
			if (place_at(id, sx_, sy_)) {
				remove_try_cell@class_try_cells try_cells, id
				return_code=TRUE
				debugmes "  ->SUCCEEDED"
				break
			} else {
				debugmes "  ->FAILED"
			}
		loop
		return return_code
#global

/* main */
	#uselib "winmm"
		#cfunc timeGetTime "timeGetTime"
	
	#define WID_MAIN	0
	#define NUM_SQUARE	100
	
	screen WID_MAIN, SX_FIELD, SY_FIELD

	//よ~い...
		mes str(NUM_SQUARE)+" squares"
		mes "ready..."
		repeat 3 : mes 3-cnt : wait 100 : loop
	
	//どん!
		time_start=timeGetTime()
		randomize
		init_sheet	//シートを初期化
		repeat NUM_SQUARE
			//新たに追加する矩形のサイズ
				sx_new=limit(100-((cnt/10)*10) ,1) : sy_new=limit(100-((cnt/10)*10) ,1)
				//sy_new=int((0.5+0.1*rnd(11))*sx_new)	//縦横比のバリエーションをもたせる場合はここをコメントアウト
				sx_new+=sx_new\2 : sy_new+=sy_new\2//偶数にする
			//追加
				debugmes "square "+(cnt+1)
				if (place(sx_new, sy_new)) {
					debugmes "placement SUCCEEDED"
				} else {
					debugmes "placement FAILED"
				}
				debugmes "------------------------------"
				title str(cnt+1)+" squares"	//コメントアウトすると高速
		loop
	
	//結果発表
		time_end=timeGetTime()
		dialog "required time : "+(time_end-time_start) + " ms"