# sample03_ezphp-sql.hsp

sample\sample_ezphp\ sample03_ezphp-sql.hsp

    #include "hsp3dish.as"

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

    #include "mod_ezphp.hsp"

    	;
    	;	ブロック崩し(3)
    	;	(ボール・バー・ブロック表示します)
    	;
    	;	ezphpsql によるデータベース編集 - 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
    	}

    	// データベースファイル「sample.db」を開き(ファイルが無ければ作られる)、「UserScore」テーブルを作成 (すでにテーブルがあれば作成されません)
    	// - 基本的にデータベースファイルは事前に準備しておきhtmlファイル等と共にアップロードしてください。
    	ezphpsql "sample.db", "CREATE TABLE IF NOT EXISTS UserScore (ID INTEGER PRIMARY KEY, uniqid TEXT, score INTEGER, name TEXT, UNiQUE(uniqid))"
    	// とりあえずスコア0でユーザーデータを新規挿入。ただしUNiQUE制約があるので既に同じuniqidがある(ゲームを遊んだことがある)場合はErrorが返る(stat=0、がそれは無視してOK)
    	ezphpsql "sample.db", "INSERT INTO UserScore (uniqid, score, name) VALUES (?, ?, ?)", uniqId, 0, your_name  ; 第3引数以降はプレースホルダにセットする値で「?」の位置に前から順で割り当てられます
    	// nameは常に更新。scoreは高い時だけ更新。uniqidを目印にして。
    	query = {"
    		UPDATE UserScore
    		SET
    			name = ?,
    			score = CASE
    				WHEN score < ? THEN ?
    				ELSE score
    			END
    		WHERE uniqid = ?;
    	"}
    	ezphpsql "sample.db", query, your_name, score, score, uniqId  ; ezphpsqlは中身が#defineなので第2パラメータまでのときでも直接複数行文字列を置くことはできません。
    	// score降順ですべてのユーザー分取り出す(単純な平文出力。jsonで出力したい場合はezphpsql_jsonを使ってください。)
    	ezphpsql "sample.db", "SELECT uniqid, score, name FROM UserScore ORDER BY score DESC"
    	rankText = refstr  ; シンプルなSELECT文なら平文で受け取って処理すると良いでしょう。(データ内の改行は「<BR>」で置換して出力されます)
    	notesel rankText

    	// ランキング描画
    	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 / 3  ; 自分が何項目要求したかで何人分返ってきたかを計算。(ここでは uniqid, score, name の3項目要求している)
    		noteget rank_uniqid, cnt*3 : strrep rank_uniqid, "uniqid:", ""  ; keyが邪魔なので取り除く
    		noteget rank_score, cnt*3+1	: strrep rank_score, "score:","" : rank_score = int(rank_score)
    		noteget rank_name, cnt*3+2 : strrep rank_name, "name:", ""
    		if rank_uniqid == uniqId : color 255,128,128 : 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