# sample02_ezphp-saveload.hsp

sample\sample_ezphp\ sample02_ezphp-saveload.hsp

    #include "hsp3dish.as"

    #include "ezlocal-dish-js.hsp"
    ezLocalJS

    #include "mod_ezphp.hsp"

    	;
    	;	ブロック崩し(3)
    	;	(ボール・バー・ブロック表示します)
    	;
    	;	ezphpsave, ezphpload によるテキスト読み書き - 319行目からを参照してください。
    	;
    #cmpopt varname 1

    	x1=0:y1=0				; 画面左上の座標
    	x2=ginfo_winx:y2=ginfo_winy		; 画面右下の座標
    	;screen 0,x2,y2

    	title "ブロック崩し"
    	;cls 4
    *gstart
    	clrflag=0		; クリアフラグ

    	blsize=16		; ボールのサイズ
    	blspd=40		; ボールのスピード
    	blx=200:bly=200		; ボールの座標
    	bpx=4:bpy=4		; XY方向のボール座標加算値
    	bk=0			; ブロックを崩すフラグ(1=崩す)
    	mblsize=-blsize

    	barsize=64		; バーのサイズ
    	barx=240:bary=540	; バーの座標

    	barhalf=barsize/2
    	blhalf=blsize/2

    	wx=0:wy=80		; ブロックの表示開始位置(左上)
    	wsx=10:wsy=6		; ブロックの配置数(X,Y)
    	wpx=x2/wsx:wpy=16	; ブロック1個あたりのサイズ

    	dim wall,wsx,wsy	; ブロックを表示するフラグ
    				; 0=表示、1=表示しない
    	score = 0
    	gameflag = 0
    *main
    	redraw 0
    	color 0,0,128:boxf
    	mtlist touchid            ; マルチタッチ
    	mtinfo touch, touchid(0)
    	color 0,255,255:pos 8,8:mes "SCORE: "+score+"   / mousex: "+mousex+" / touch: "+touch(0)+", "+touch(1)

    	;gradf 0,0,x2,y2,1,0,128	; 画面クリア

    	; バー : マウス座標から位置を決定
    	barx=mousex-barsize/2
    	color 255,255,255
    	boxf barx,bary,barx+barsize,bary+16

    	if gameflag {
    		goto *gameover
    		stick key
    		if key&256 {
    			if mousey<300 : goto *gstart
    		}
    		goto *main2
    	}

    	; ボール : X方向の移動
    	blx=blx+bpx
    	if blx<=x1 : blx=x1 : bpx=-bpx
    	if blx>=(x2-blsize) : blx=x2-blsize : bpx=-bpx

    	; ボール : Y方向の移動
    	bly=bly+bpy
    	if bly<=y1 : bly=y1 : bpy=45 : bk=1 : blspd=45
    	if bly>=(y2-blsize) : gameflag = 1

    	; ボールとバーがぶつかったかどうか調べます
    	coly=bly+blsize
    	if (coly>=bary)&(bly<(bary+16)) {	; ボールY座標のチェック
    		; ボールX座標のチェック
    		colx=blx+blhalf
    		x=barx+barhalf
    		if abs(colx-x)<(barhalf+blhalf) {
    			bly=bary-blsize : bpy=-blspd
    			i=(colx-x)/4
    			if i!=0 : bpx=i
    			bk=1
    			if clrflag {
    				dim wall,wsx,wsy	; ブロックを復活させる
    				clrflag=0
    			}
    		}
    	}

    *main2
    	; ブロックの処理
    	colx=wpx+bsize
    	coly=wpy+bsize
    	left=0
    	repeat wsy
    	cy=cnt
    	y=cnt*wpy+wy
    	hsvcolor cnt*10,255,255
    	repeat wsx
    		cx=cnt
    		x=cnt*wpx+wx
    		if wall(cx,cy)=0 {
    			; ブロックを表示
    			left++
    			boxf x,y,x+wpx-2,y+wpy-2
    			i=blx-x:j=bly-y
    			if (i>=mblsize)&(i<colx)&(j>=mblsize)&(j<coly)&(bk) {
    				wall(cx,cy)=1
    				bpy=-bpy
    				bk=0
    				score+=wsy-cy
    			}
    		}
    	loop
    	loop

    	; ボールを表示
    	pos blx,bly:color 255,255,255
    	circle blx,bly,blx+blsize,bly+blsize
    	;font msgothic,blsize
    	;mes "●"

    	redraw 1
    	await 33

    	if left=0 : clrflag=1
    	goto *main

    *gameover
    	color 255,255,255
    	pos 160,380:mes "GAME OVER"
    	your_name = ""

    	// 既に自分の名前があるか確認
    	exist "save/myname.txt"
    	if strsize != -1 {
    		notesel your_name
    		noteload "save/myname.txt"
    		noteunsel
    	}
    	// HSP3Dish.js で input は 闇なので独自のインターフェイスを用意した方が良いと思う。。。
    	objsize 180,32
    	pos 160,330 : input your_name : id_input_yourName = stat
    	pxy = ginfo_cx, ginfo_cy
    	mes "※ 英数記号のみ"
    	objsize 80,32
    	pos 360,330 : button "送信", *send
    	// スマホでキーボードを出させる苦肉の策(要素を作成)
    	sdim strbuf, 1024  ; Androidでinput入力を捕捉する必要があるため
    	sdim mae_strbuf, 1024
    	your_name_ = your_name
    	strrep your_name_, "'", "\\'"  ; エスケープ

    	inputSizeX = 180
    	inputSizeY = 32
    	inputPosX = 160
    	inputPosY = 330
    	exec {"
    		// Androidはinput内容を捕捉する必要がある
    		var mae_hsp_input_value = '"}+your_name_+{"';

            if (navigator.userAgent.match(/iPhone|iPad|Android/)) {
    			if(document.getElementById('hsp_dummy') === null){
    				// キーボード表示用のinput作成(上部に極透明なオブジェクトを作成)
    				const inputElement = document.createElement('input');
    				inputElement.id = 'hsp_input';
    				inputElement.type = 'text';
    				inputElement.inputmode = 'email';
    				inputElement.style.position = 'fixed';
    				inputElement.style.left = '0';
    				inputElement.style.top = '0';
    				inputElement.style.fontSize = '100px';
    				inputElement.style.display = 'none';
    				inputElement.style.zIndex = -1;
        		    if (navigator.userAgent.match(/Android/)) {
    					inputElement.value = mae_hsp_input_value;
    					inputElement.style.width = '98%';
    					inputElement.style.height = '100px';
    					inputElement.style.opacity = '.2';
    				}else{
    					inputElement.style.width = '1px';
    					inputElement.style.height = '1px';
    					inputElement.style.opacity = '.01';
    				}
    				// フォーカスアウトでinputを非表示にして、dummyを表示する
    				inputElement.addEventListener('focusout', (event) => {
    					document.getElementById('hsp_input').style.display = 'none';
    					document.getElementById('hsp_dummy').style.display = '';
    				});
    				// Androidはinput内容を捕捉する必要がある
    				if (navigator.userAgent.match(/Android/)) {
    					inputElement.addEventListener('input', (event) => {
    						if(!event.target.value.match(/^[a-zA-Z0-9!-/:-@\[-`{-~ ]*$/)){
    							// 半角英数以外は入力禁止(他デバイスに合わせてる)
    							event.target.value = mae_hsp_input_value;
    						}
    						mae_hsp_input_value = event.target.value;
    						stringToUTF8Array(mae_hsp_input_value, Module.HEAP8, "} + varptr(strbuf) + {", 1024);
    					});
    				}
    				document.body.appendChild(inputElement);

    				// click->focus()のためのダミーElement
    				const dummyElement = document.createElement('div');
    				dummyElement.id = 'hsp_dummy';
    				dummyElement.style.position = 'absolute';
    				dummyElement.style.fontSize = '100px';
    				SetRectDummyDiv(dummyElement);
    				dummyElement.style.opacity = '.01';
    				//dummyElement.style.opacity = '.5';           // test用
    				//dummyElement.style.backgroundColor = 'red';  // test用
    				dummyElement.addEventListener('click', () => {
    					// スマホはタッチ系のアクション内じゃないとfocus()効かない
    					document.getElementById('hsp_input').style.display = '';
    					document.getElementById('hsp_dummy').style.display = 'none';
    					document.getElementById("hsp_input").focus();
    				});
    				document.body.appendChild(dummyElement);
    				// ダミーElementのリサイズ
    				function SetRectDummyDiv(erement){
    					if (erement === null) return;
    					const inputSizeX = "} + inputSizeX + {";
    					const inputSizeY = "} + inputSizeY + {";
    					const inputPosX = "} + inputPosX + {";
    					const inputPosY = "} + inputPosY + {";
    					const wx = ENV.HSP_WX;  // 480
    					const wy = ENV.HSP_WY;  // 800
    					const sx = ENV.HSP_SX;  // String(window.innerWidth)
    					const sy = ENV.HSP_SY;  // String(window.innerHeight)
    					const rect = document.getElementById("canvas").getBoundingClientRect();
    					if( (sx/sy) < (wx/wy) ){
    						// 横幅に余白がなく縦方向に余白があるとき
    						const rate = wy / wx * sx / sy;  // ディスプレイ高さに対してゲーム画面高さがどのくらいか
    						erement.style.left = rect.left + window.pageXOffset + rect.width*(inputPosX/wx)+'px';
    						erement.style.width = rect.width*(inputSizeX/wx)+'px';
    						erement.style.top = rect.top + window.pageYOffset + rect.height*((1-rate)/2 + (inputPosY/wy) * rate)+'px';
    						erement.style.height = rect.height*((inputSizeY/wy) * rate)+'px';
    					}else {
    						// 縦方向に余白がなく横幅に余白があるとき
    						const rate = wx / wy * sy / sx;  // ディスプレイ幅に対してゲーム画面幅がどのくらいか
    						erement.style.left = rect.left + window.pageXOffset + rect.width*((1-rate)/2 + (inputPosX/wx) * rate)+'px';
    						erement.style.width = rect.width*((inputSizeX/wx) * rate)+'px';
    						erement.style.top = rect.top + window.pageYOffset + rect.height*(inputPosY/wy)+'px';
    						erement.style.height = rect.height*(inputSizeY/wy)+'px';
    					}
    				}
    				// デバイス回転時にダミーElementをリサイズ
    				function DeviceOrientation_InputDummy() {
    					setTimeout(() => {
    						SetRectDummyDiv(document.getElementById('hsp_dummy'));
    					}, 100);
    				}
    				window.removeEventListener('orientationchange', DeviceOrientation_InputDummy);
    				window.addEventListener('orientationchange', DeviceOrientation_InputDummy);
    			}else{
    				document.getElementById('hsp_dummy').style.display = '';
    			}
    		}
    	"}
    	objsel id_input_yourName
    *gameover_loop
    	mtlist touchid            ; マルチタッチ
    	if stat {
    		// input部以外タッチでフォーカス外す
    		exec {"
    			if(document.getElementById("hsp_input")){
    				document.getElementById("hsp_input").blur();
    			}
    		"}
    	}
    	if mae_strbuf != strbuf {
    		// Androidではinput入力を捕捉する必要があるため
    		mae_strbuf = strbuf
    		your_name = strbuf
    		objprm id_input_yourName, your_name
    	}
    	redraw 0
    	color 0,0,128:boxf
    	color 0,255,255:pos 8,8:mes "SCORE: "+score
    	color 255,255,255
    	pos 200,280:mes "GAME OVER"
    	pos 30,335:mes "YOUR NAME : "
    	pos pxy(0),pxy(1) : mes "※ 英数記号のみ"
    	redraw 1
    	await 33
    	goto *gameover_loop
    	stop

    // データ保存
    *send
    	clrobj
    	// スマホでキーボードを出させる苦肉の策(要素を削除)
    	exec {"
    		if(document.getElementById('hsp_input')){
    			document.getElementById('hsp_input').remove();
    		}
    		if(document.getElementById('hsp_dummy')){
    			document.getElementById('hsp_dummy').remove();
    		}
    	"}
    	if your_name == "" : your_name = "NO NAME"
    	// 自分の名前を登録
    	notesel your_name
    	notesave "save/myname.txt"
    	noteunsel
    	devcontrol "syncfs"

    	// ユニークID生成(テキトウ20桁)
    	exist "save/uniqid.txt"
    	if strsize == -1 {
    		// まだブラウザに保存されていなかったとき
    		uniqId = TekitouUniqId(20)  ; 20桁のUniqueID生成
    		dirlist dummy,"save",5 : if stat == 0 : mkdir "save"  ; ← Windowsアプリとして実行するとき用に
    		notesel uniqId
    		notesave "save/uniqid.txt"  ; ブラウザに保存
    		devcontrol "syncfs"
    		noteunsel
    	}else {
    		// すでにブラウザにUniqueIDが保存されていたとき。読み込み
    		notesel uniqId
    		noteload "save/uniqid.txt"
    		noteunsel
    	}

    	// ランキングテキスト読み込み
    	notesel rankText
    	ezphpload "save/rank.txt", rankText
    	if stat == 0 {
    		// ファイルがまだない
    		rankText = ""+uniqId+","+score+","+your_name
    		// ランキングテキスト保存
    		ezphpsave "save/rank.txt", rankText
    	}else{
    		score_ = score
    		// 自分のデータがあるか確認
    		rankIndex = notefind(uniqId,1)
    		if rankIndex != -1 {
    			// 前回スコアより高いか名前が違えば更新(現在のデータ行を消して再挿入)
    			noteget temp, rankIndex
    			split temp, ",", rank_uniqid, rank_score, rank_name
    			if int(rank_score) < score_ || rank_name != your_name {
    				notedel rankIndex : rankIndex == -1
    				if score_ < int(rank_score) : score_ = int(rank_score)  ; 前回スコアの方が高いとき
    			}
    		}
    		if rankIndex == -1 {
    			// ランキングデータ挿入
    			repeat notemax
    				noteget temp, cnt
    				split temp, ",", rank_uniqid, rank_score, rank_name
    				if int(rank_score) < score_ {
    					// この行の人より勝ち
    					noteadd ""+uniqId+","+score_+","+your_name, cnt
    					rankIndex == 0
    					break
    				}
    			loop
    			if rankIndex == -1 {
    				// 最後まで勝てる相手を見つけられなかったときは最下位
    				noteadd ""+uniqId+","+score_+","+your_name, -1
    			}
    			// ランキングテキスト保存
    			ezphpsave "save/rank.txt", rankText
    			;  本当なら同時更新された場合の問題をもう少し考える必要があります。
    			;  ↑ この問題は ezphpload, ezphpsave の第3パラメータ(内容変更を検出するためのハッシュ値)を使うことで解決できます。
    			;     具体的な使い方は sample6_ezphp-saveload_crc.hsp を参照してください。
    		}
    	}

    	// ランキング描画
    	redraw 0
    	color 0,0,128:boxf
    	color 0,255,255:pos 8,8:mes "SCORE: "+score : mes ""
    	pos 50,
    	maeScore = -1 : maeJunni = 1
    	repeat notemax
    		noteget temp, cnt
    		split temp, ",", rank_uniqid, rank_score, rank_name
    		if rank_uniqid == uniqId : color 255,100,100 : rank_uniqid = " ("+rank_uniqid+")" : else :rank_uniqid = "" : color 255,255,255  ; 自分だけUniqID表示
    		junni = (cnt+1) : if maeScore == rank_score : junni = maeJunni : else : maeJunni = junni : maeScore = rank_score  ; 同スコアは同率順位
    		mes ""+junni+"位  "+rank_score+" - "+ rank_name + rank_uniqid
    	loop
    	redraw 1
    	end

    *owari
    	end

    // テキトウに N桁 Unique な ID を生成する関数
    #module
    #defcfunc TekitouUniqId int keta
    	randomize
    	uniqId = ""
    	repeat keta
    		uniqid += "_"
    		id = rnd(10+26+26)
    		if id >= 36 : poke uniqId, cnt, id+61 : continue  ; a-z
    		if id >= 10 : poke uniqId, cnt, id+55 : continue  ; A-Z
    		poke uniqId, cnt, id+48  ; 0-9
    	loop
    	return uniqId
    #global