Dripです。
ぜろさん、こんにちは。
syamさんの仰るとおり、確かにご質問の意味がゲームを作りたいのか、
それともクロスワードを自動生成して画像を表示したいだけなのかが釈然としませんが、
今回はクロスワードの自動生成に焦点を絞って解説させていただこうと思います。
まずクロスワードのルールからになりますが、単語がパズル内に存在できる条件として、
・パズルからはみ出ない・全ての単語はどこか別の単語と必ずクロスしている
・単語の頭文字と末尾文字は他の単語に融合してはならない
などがあり、これらを全て満たして作成しなければなりません。
かなり複雑であり、初心者には少々ハードな解説になることを覚悟されてください。
以下にランダムにクロスワードを作成するサンプルを示します。処理の流れとしては、
まずinit命令で単語帳をHSP内の適当なファイルから取得し、画面サイズでパズルフィールドを初期化します。
次に再起のシードとなる最初の単語をパズルフィールド中央付近にmake命令で配置し、
単語の各文字から枝のように別の単語をmake命令内でmake命令を再起的に呼び出して生やしていきます。
この単語を生やすときに、make命令内からchk命令を呼び出し、その単語が存在できるか事前にチェックします。
これを理解するためには、まず「モジュール」と「再起」を理解している必要があります。
(そういうのを使わないで!となりますと、更に複雑なプログラムが必要になります。)
スクリプトにコメントをかなり詳しく書きましたが、初心者にとっては非常にハードルが高いものになるかと思います。
まずは目的のハードルを下げ、どういう順序でプログラムをすれば何ができそうか、
順を追って考えながら学習することをお勧めします。スクリプトの意味がわからなくても、流れだけでも理解するために、
★で強調されたコメントだけ読んでいくのも良いかもしれません。…ご健闘をお祈りします^^;;;
#module cross
#define size 20 //マスのサイズ(小さいほど広くなる(可視性を加味して最低14くらいまで))
#define maxnest 2 //再起最大ネスト(多いほど複雑なパズルになる可能性を秘める)
//★init 命令:単語とパズルフィールドを初期化する。
#deffunc init
//★変数mypadに単語帳を作成する。(mypadの配列に200こくらいの単語を代入する)
sdim data,30000
ts=dir_exe+"\\hsplet\\lib\\jl1.0-LICENSE.txt"
exist ts:if strsize>-1:{ //HSPLetのライセンスファイルが見つかったのでそれを単語帳にする
bload ts,data
}else{ //Android関連のライセンスファイルが見つかればそれを単語帳にする
ts=dir_exe+"\\android\\extlib\\src\\libpng\\LICENSE"
exist ts:if strsize<1:dialog "単語データが格納されているテキストファイルがありません。":end
bload ts,data
}
strrep data,"\n"," ":split data," ",word //テキストファイルの改行をスペースに置換してword配列に単語を取得
max=0
repeat stat:id=cnt
if strlen(word(id))<2:continue //短すぎる単語排除
dis=0
//半角小文字以外を持つ単語を排除
repeat strlen(word(id))
if peek(word(id),cnt)<'a' | peek(word(id),cnt)>'z':dis=1:break
loop:if dis:continue
//既に登録済みの単語は排除
repeat max
if mypad(cnt)=word(id):dis=1
loop:if dis:continue
//過去形とか複数形っぽいのを排除
t=peek(word(id),strlen(word(id))-1):if t='s'| t='d':continue
mypad(max)=word(id):max++
if max>=200:break
loop
//★パズルフィールドを画面サイズとマスサイズを元に初期化する
fldSz=ginfo_winx/size , ginfo_winy/size
sdim pzl,2,fldSz(0),fldSz(1) //マスの文字を管理する変数
dim used,max //使用済みの単語を管理する変数
dim idx,max //含めたい文字が単語内の何文字目に含まれるか管理する変数
return
//★chk 命令:
//パズルの座標nx,nyにある文字を含む単語を単語帳の中から調べ、その文字を含む場合、
//何文字目にその文字を含むか調べ、変数idxの配列にインデックスを代入する。
//ただし、その文字を持っていたとしても、パズルに配置しようとした場合、
//他の単語とぶつかって文字の矛盾が発生したりパズルからはみ出る場合はインデックスに-1を代入する。
//また、他の単語と末端が融合されてしまうような場合や、一度使われている単語も無条件に-1を代入する。
//angに単語を配置しようとしている方向(0:横 0以外:縦)を指定して実行すること。
//この命令が成功すると配置可能な単語をいくつ発見できたかをstatに返す。
#deffunc chk int nx,int ny,int ang,local myid
pt=nx,ny //調査対象パズル座標X,Y(変数に入れておくことで縦・横の座標を参照しやすくなる)
if pzl(nx,ny)="":return 0 //調査座標に文字が何も入っていなければ失敗とする。(≒含めたい文字が存在しない)
count=0 //いくつの有効単語を発見できたか管理する変数
repeat max //単語帳に登録された全単語に対して調査を行う。
if used(cnt):idx(cnt)=-1:continue //一度使われた単語はもう使えない。
myid=cnt //調査対象単語の配列ID
idx(cnt)=instr(mypad(myid),0,pzl(nx,ny)) //idxに調査対象文字が何文字目に含まれるかidxの配列に代入する。
if idx(cnt)>-1:{ //もし単語が調査対象文字を含んでいれば
//★単語を配置しようとするとフィールドをはみ出てしまう場合は除外する
if pt(ang)-idx(myid)<0 | pt(ang)-idx(myid)+strlen(mypad(myid))>=fldSz(ang):idx(myid)=-1:continue
//★単語を配置しようとすると頭文字が他の単語に融合してしまうような場合は除外する
if pt(ang)-idx(myid)>0:{ //単語の頭文字はフィールド末端より1文字以上内側に配置される
if pzl(nx-(ang=0)*(idx(myid)+1),ny-(ang!0)*(idx(myid)+1))!"":{ //頭文字の一つ前に何らかの文字がある
idx(myid)=-1:continue //融合してしまう配置はできないのでこの単語を除外する
}
}
//★単語を配置しようとすると末尾文字が他の単語に融合してしまうような場合は除外する
if pt(ang)-idx(myid)+strlen(mypad(myid))<fldSz(ang):{ //単語の末尾はフィールド末端より1文字以上内側に配置される
//末端文字の一つ向こうに何らかの文字があるかチェック。あれば除外対象。
if pzl(nx+(ang=0)*(-idx(myid)+strlen(mypad(myid))),ny+(ang!0)*(-idx(myid)+strlen(mypad(myid))))!"":{
idx(myid)=-1:continue //融合してしまう配置はできないのでこの単語を除外する
}
}
//★単語を配置しようとすると他の単語にかぶさって、矛盾してしまうような単語は除外する
dis=0
repeat strlen(mypad(myid)) //単語の文字数分チェック
if pzl(nx+(ang=0)*(cnt-idx(myid)),ny+(ang!0)*(cnt-idx(myid)))!"":{ //配置しようとした場所になんらかの文字がある
//その文字はこれから代入しようとしている文字と異なるかチェック。異なれば除外対象。
if pzl(nx+(ang=0)*(cnt-idx(myid)),ny+(ang!0)*(cnt-idx(myid)))!strmid(mypad(myid),cnt,1):{
dis=1:break //他の単語にかぶさって単語が配置できないのでこの単語を除外する
}
}
loop
if dis:idx(myid)=-1:continue //除外が申請されているのでこの単語を除外する
//★無事、ここまで来る事ができた単語のidx(cnt)には何文字目に含めたい文字(pzl(nx,ny)の文字)を持つかが代入されている。
count++
}
loop
return count
//★make 命令
//単語帳のnid番目の単語をnx,nyの座標にangの方向(0:横 1:縦)に配置する。
//nestにこの関数を実行するネストを指定する。(モジュール外からの利用では無視してよい)
#deffunc make int nid,int nx,int ny,int ang,int nest,local myid
if used(nid):return //申請された単語が既に使用済みの単語の場合、配置を拒否する。
used(nid)=1 //これから書き込む単語を使用済みとして登録する。
//★単語をパズルフィールドに書き込む
repeat strlen(mypad(nid))
if ang=0:{ //横書き
pzl(nx+cnt,ny)=strmid(mypad(nid),cnt,1)
}else{ //縦書き
pzl(nx,ny+cnt)=strmid(mypad(nid),cnt,1)
}
;redraw 0:draw:redraw 1:await 30 //★★ このコメントを解除するとどのように作成されていくかが見れます。 ★★
loop
//★融合対策として単語の頭と末尾にダミーの文字を配置
if ang=0:{ //横書き
if nx>0:pzl(nx-1,ny)="*" //単語の頭と末端にダミーの文字を配置(融合対策)
if nx+strlen(mypad(nid))<fldSz(0):pzl(nx+strlen(mypad(nid)),ny)="*"
}else{ //縦書き
if ny>0:pzl(nx,ny-1)="*" //単語の頭と末端にダミーの文字を配置(融合対策)
if ny+strlen(mypad(nid))<fldSz(1):pzl(nx,ny+strlen(mypad(nid)))="*"
}
//★再起処理を行い、単語の文字から枝を生やす(単語を差し込む)
repeat strlen(mypad(nid)) //単語の文字数分繰り返す
if ang=0:{ //横書きの場合
if (cnt+nx)\2=0 & ny\2=0 & nest<maxnest:{ //見た目の問題で枝を生やせるのは偶数マスのみ、再起ネスト範囲内のみ処理。
chk nx+cnt,ny,1:if stat:{ //配置できそうな単語を発見できた場合
repeat 10 //その中からランダムに単語を選出する。10回トライし、適当な単語が見つからなければ諦める。
myid=rnd(max):if idx(myid)>-1:break
loop
if idx(myid)>-1:{ //無事単語を選出できた場合、単語をパズルに配置する
make myid,nx+cnt,ny-idx(myid),1,nest+1
}
}
}
}else{ //縦書きの場合
if nx\2=0 & (cnt+ny)\2=0 & nest<maxnest:{ //見た目の問題で枝を生やせるのは偶数マスのみ、再起ネスト範囲内のみ処理。
chk nx,ny+cnt,0:if stat:{ //配置できそうな単語を発見できた場合
repeat 10 //その中からランダムに単語を選出する。10回トライし、適当な単語が見つからなければ諦める。
myid=rnd(max):if idx(myid)>-1:break
loop
if idx(myid)>-1:{ //無事単語を選出できた場合、単語をパズルに配置する
make myid,nx-idx(myid),ny+cnt,0,nest+1
}
}
}
}
loop
return
//★draw 命令
//パズルを描画する
#deffunc draw
font msgothic,size-4
repeat fldSz(0)*fldSz(1) //全マス描画する
if pzl(cnt\fldSz(0),cnt/fldSz(0))!"" & pzl(cnt\fldSz(0),cnt/fldSz(0))!"*":{ //マスに何かの単語が含まれる場合
pt=cnt\fldSz(0)*size,cnt/fldSz(0)*size //描画しようとしている座標
pos pt,pt(1) //マスを描画
color 128,128,128:boxf pt,pt(1),pt+size,pt(1)+size
color 255,255,255:boxf pt+2,pt(1)+2,pt+size-2,pt(1)+size-2
pos -100,-100:mes pzl(cnt\fldSz(0),cnt/fldSz(0)) //文字の横サイズをginfo_mesx取得
color:pos pt+(size-ginfo_mesx)/2+2,pt(1)+2:mes pzl(cnt\fldSz(0),cnt/fldSz(0)) //文字をマスの中央に描画
}
loop
return
#global
//★お疲れ様でした。メイン処理ここからです。
//★最初に選ばれた単語が極端に短かったり、運が悪いと単語が1つ描画されて終わったりします^^;;;
randomize //乱数初期化
init //単語帳を読み込み、パズルフィールドを初期化
//★真ん中あたりに最初の単語を配置(偶数マスしか枝が生えないので初期座標に注意)
make rnd(max@cross),fldSz@cross(0)/4*2-2,fldSz@cross(1)/4*2
draw //パズルを描画
color 255,255,255:boxf 0,0,ginfo_winx,16
font msgothic,14,1:color:pos 2,2:mes "何度も実行してどのようなパターンが生まれるか試してみましょう。"