矩形(デフォルトで正方形)をなるべく隙間が出来ないように左上から敷き詰める。
画面をスプレッドシートとして扱い、空き領域を「セル」として管理する。
矩形(デフォルトで正方形)をなるべく隙間が出来ないように左上から敷き詰める。
画面をスプレッドシートとして扱い、空き領域を「セル」として管理する。
/* 矩形敷き詰め問題 表計算ソフトのシートの要領で空き領域を管理する。 爆速だけどメモリ消費が多い(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"