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