【TIC-80】上下左右ループのタイルマップ

##「TIC-80」のタイルマップ

「[TIC-80](https://tic.computer/)」には標準でタイルマップ機能があります。[マップエディタ](https://github.com/nesbox/TIC-80/wiki#map-editor)（GUI）でタイル（BG）を選択してクリックやドラッグで配置するほか`mset(x,y,id)`という命令でプログラムから配置することも可能です。

マップエディタでの配置はファミコンの「ロードランナー」や「ナッツ＆ミルク（配置方法はこちらが近い）」みたいな感じです。
（若い人には伝わらないかも‥‥）えっと最近のゲームなら、そう、**マリオメーカー**みたいな感じです。

主な仕様は以下の通り

- 1つのセルに対し8x8ドットの背景タイル（id:0～255）を設定する
- ワールドマップは240x136セル　（1920x1088 ピクセル）

タイルマップの表示は map 命令で行ないます。
`map ([x=0, y=0], [w=30, h=17], [sx=0, sy=0], [colorkey=-1], [scale=1], [remap=nil])`

- x,y は描画を開始する（左上の）**ワールドマップ上のセル座標**
- w,h は描画する範囲の横幅と高さ（マップの**セル単位**）
- sx,sy は（マップ左上の）描画座標（画面上の**ピクセル単位**）
- colorkey は透明にしたい色番号（0～15,-1の場合透明なし）
- scale は表示倍率（1以上の整数?）
- remap は‥‥こおるばっく関数？？（便利らしいのですがサンプルも見つけられず使い方が分かりませんでした）

##タイルマップのループ

ワールドマップは上下左右がループしています。
マップエディタでWORLD MAP表示（タブキーか目玉アイコン（？）をクリック）
![右下ワールドマップ.gif](https://qiita-image-store.s3.amazonaws.com/0/284455/cf913425-140e-e49b-5915-b66b6c1f82a9.gif)
![右下.gif](https://qiita-image-store.s3.amazonaws.com/0/284455/d1e1c2bf-5d55-d145-13e5-3b9661c831c8.gif)

上の画像では表示範囲を表す赤枠は右下にしかありませんが、下の画像ではワールドマップの四隅のタイルも表示されています。
エディタ上だけでなく map 命令で表示させた場合も 240x136セル範囲外のものは反対側に回り込んで表示してくれます。

ただしこれはワールドマップでの仕様なので、それより小さな範囲でループさせたい場合は、map 命令を複数回実行（もしくはマップデータを周辺にコピー）する必要があります（多分）。

で、以前は、ワールドマップの`(0,0)`に配置した32x32セル（固定）のエリアをループ表示させるために中心と**上下左右斜め全てで合計9回** map 命令を呼んでいました。

96x96セル分描画してその内画面に表示されるのは全画面でも 32x17 セル分という。
その後、せめてもう少し減らそうということで常時5回に減らしたりしましたが
表示エリアの大きさ固定、全画面固定だったのでこの度、マップ表示部分を
書き直しました。

##実装

```loopmap.lua
function lmap(mapx,mapy,mw,mh,sx,sy,colkey,scale)
	local mx,my=mapx or 0,mapy or 0
	local lw,lh=mw or 30,mh or 17
	local xx,yy=sx or 0,sy or 0
	local ck,s=colkey or -1,scale or 1
	return function(x,y,w,h,ox,oy,noloop)
		local x,y=x%lw,y%lh
		local ww,hh=w or 30,h or 17
		local ox,oy=ox or 0,oy or 0
		map(mx+x,my+y,min(ww,lw-x),min(hh,lh-y),xx+ox,yy+oy,ck,s)
		if x+ww>lw and not noloop then
			map(mx,my+y,x+ww-lw,min(hh,lh-y),xx+(lw-x)*s*8+ox,yy+oy,ck,s)end
		if y+hh>lh and not  noloop then
			map(mx+x,my,min(ww,lw-x),y+hh-lh,xx+ox,yy+(lh-y)*s*8+oy,ck,s)end
		if x+ww>lw and y+hh>lh and not noloop then
			map(mx,my,x+ww-lw,y+hh-lh,xx+(lw-x)*s*8+ox,yy+(lh-y)*s*8+oy,ck,s)end end end

testinit=function()
	x,y=0,0
	lm=lmap(0,0,16,16,80,24)
end

testinit()

function TIC()
	if btnp(0,1,30)then y=y-1 end
	if btnp(1,1,30)then y=y+1 end
	if btnp(2,1,30)then x=x-1 end
	if btnp(3,1,30)then x=x+1 end
	lm(x,y,8,8)
end
```

こんな感じかな？

![説明.png](https://qiita-image-store.s3.amazonaws.com/0/284455/40a68899-4514-1d81-066d-26a946d02f93.png)

説明のために図を書きましたが、最初からこれを書いてから実装した方が良かった気がします。
（数式部分はパッっと書けた訳ではなく、こうかな？どうかな？みたいな感じで
　違ったら少し変更して‥とかしていたので）

間違いがありましたら、ご指摘お願いします。

使い方としては
`lmap(mapx,mapy,mw,mh,sx,sy,colkey,scale)`

- mapx,mapy （ワールドマップ座標）
- mw,mh （制限幅）

で制限（ワールドマップから切り出すイメージ）して、返り値の命令にさらに
命令`(x,y,w,h)` で相対座標と表示範囲をパラメータとして与えて実行します。

##実行例

以前に紹介したスプライトデータからテーブルに、テーブルから境界線付きマップデータに変換、
と合わせて実行するとこのような感じです。

![kabu2.gif](https://qiita-image-store.s3.amazonaws.com/0/284455/8f1c588c-fd5c-53d1-f1d6-b951af8d804e.gif)

TEIJIRO氏の「すわいん」を思い出しました（伝わる人がいたら嬉しい）

スプライトデータからマップデータを作成しているので、
スプライトをそのまま表示させると（右下）ミニマップになるのがポイント☆
最後に一応全リストを載せておきます。

```lmap.lua
min=math.min
-- common function from TIC-80 wiki
function sget(x,y) -- get spritesheet pixel
	local addr=0x4000+(x//8+y//8*16)*32
	return peek4(addr*2+x%8+y%8*8) end

-- my common function
function spr2tbl(idx,w,h,x_offset,y_offset)
	local w,h=w or 8,h or 8
	local ox,oy=x_offset or 0,y_offset or 0
	local sprX,sprY=idx%16*8,idx//16*8
	local tbl={}
	for rown=1,h do
		tbl[rown]={}
		for coln=1,w do
			tbl[rown][coln]=sget((sprX+ox+coln-1)%128,sprY+oy+rown-1)end end
	return tbl end

function area2map(t,colsync,bgcol,ldcol,zerotid,tnum,x_offset,y_offset,dosync)
	local csync=colsync or false
	local ox,oy=x_offset or -1,y_offset or -1
	local bgc,ldc=bgcol or 13,ldcol or 4
	local ztid,tn=zerotid or 0,tnum or 4
	for rown,row in ipairs(t) do
		for coln,num in ipairs(row) do
			local stid
			if csync then
				stid=num*tn else
				stid=num==0 and bgc*tn or ldc*tn end
			local dLeft=(coln==1 or num==t[rown][coln-1])and 0 or 1
			local dUp=(rown==1 or num==t[rown-1][coln])and 0 or 2
			mset(coln+ox,rown+oy,ztid+stid+dLeft+dUp)end end
	if dosync then sync(4,0,true) end end

function lmap(mapx,mapy,mw,mh,sx,sy,colkey,scale)
	local mx,my=mapx or 0,mapy or 0
	local lw,lh=mw or 30,mh or 17
	local xx,yy=sx or 0,sy or 0
	local ck,s=colkey or -1,scale or 1
	return function(x,y,w,h,ox,oy,noloop)
		local x,y=x%lw,y%lh
		local ww,hh=w or 30,h or 17
		local ox,oy=ox or 0,oy or 0
		map(mx+x,my+y,min(ww,lw-x),min(hh,lh-y),xx+ox,yy+oy,ck,s)
		if x+ww>lw and not noloop then
			map(mx,my+y,x+ww-lw,min(hh,lh-y),xx+(lw-x)*s*8+ox,yy+oy,ck,s)end
		if y+hh>lh and not noloop then
			map(mx+x,my,min(ww,lw-x),y+hh-lh,xx+ox,yy+(lh-y)*s*8+oy,ck,s)end
		if x+ww>lw and y+hh>lh and not noloop then
			map(mx,my,x+ww-lw,y+hh-lh,xx+(lw-x)*s*8+ox,yy+(lh-y)*s*8+oy,ck,s)end end end

testinit=function()
	sidx=80
	x,y=0,0
	t=spr2tbl(sidx,16,16)
	area2map(t,true)
	lm=lmap(0,0,16,16,80,24)
end

testinit()

function TIC()
	if btnp(0,1,30)then y=y-1 end
	if btnp(1,1,30)then y=y+1 end
	if btnp(2,1,30)then x=x-1 end
	if btnp(3,1,30)then x=x+1 end
	lm(x,y,8,8)
	spr(sidx,200,100,-1,1,0,0,2,2)
end

```