sample06_ezphp-saveload_crc.hsp

sample\sample_ezphp\ sample06_ezphp-saveload_crc.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
*RETRY_SAVE
	ezphpload "save/rank.txt", rankText, crc
	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, crc  ; 第3パラメータのcrcでsave前にloadした時と内容が変わっていないかチェックできる。もし内容が変わっていたら保存されずstatに-1が代入される
			if stat == -1 {
				// loadから編集してsaveする間に誰か他のユーザーに先を越されて内容が変わっていた場合
				wait 50
				goto *RETRY_SAVE  ; もう一度ezphploadから保存処理をやり直す
			} 
		}
	}

	// ランキング描画
	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