e-対戦 [e-typing]

e-typingに対戦機能を追加したい

Устаревшая версия за 06.08.2023. Перейдите к последней версии.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey, Greasemonkey или Violentmonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Violentmonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Violentmonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Userscripts.

Чтобы установить этот скрипт, сначала вы должны установить расширение браузера, например Tampermonkey.

Чтобы установить этот скрипт, вы должны установить расширение — менеджер скриптов.

(у меня уже есть менеджер скриптов, дайте мне установить скрипт!)

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

(у меня уже есть менеджер стилей, дайте мне установить скрипт!)

// ==UserScript==
// @name         e-対戦 [e-typing]
// @namespace    http://tampermonkey.net/
// @version      4.7
// @description  e-typingに対戦機能を追加したい
// @author       Toshi
// @match        https://www.e-typing.ne.jp/app/jsa_std*trysc*
// @exclude      https://www.e-typing.ne.jp/app/ad*
// @exclude      https://www.e-typing.ne.jp/app/*std.2*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=e-typing.ne.jp
// @license MIT
// @require      https://www.gstatic.com/firebasejs/10.1.0/firebase-app-compat.js
// @require      https://www.gstatic.com/firebasejs/10.1.0/firebase-auth-compat.js
// @require      https://www.gstatic.com/firebasejs/10.1.0/firebase-database-compat.js

// @grant unsafeWindow


// ==/UserScript==


const firebaseConfig = {
	//テスト用データベース
/*  	apiKey: "AIzaSyARzYljiRuZCoABA32_wnuyMkjScpcZUN8",
	databaseURL: "https://e-typing-battle-fb330-default-rtdb.firebaseio.com/" */

	//本番用データベース
 	apiKey: "AIzaSyDsHiPII5dgN_AEGwOtMehyveucoF4Twvs",
	databaseURL: "https://e-typing-battle-default-rtdb.firebaseio.com"
};


class MyResult{

	constructor(){
		this.battlePlayerResultData = {}

		this.etypingPlus = ['score','level','time','typeCount','missCount','wpm','correct','latency','rkpm']
		this.normal = ['score','level','time','typeCount','missCount','wpm','correct','weakKey']
	}



	sendResult(){
		playerStateChange.update('result')

		const RESULT_DATA = document.getElementsByClassName("result_data")[0].firstElementChild.children
		const RESULT_ITEM = RESULT_DATA.length == 9 ? this.etypingPlus : this.normal
		let updates = {}
		let sendData = {}


		for(let i=0; i<RESULT_ITEM.length; i++){
			sendData[RESULT_ITEM[i]] = RESULT_DATA[i].lastElementChild.textContent
		}




		updates['/users/' + myID + '/result/'] = sendData
		firebaseDB.ref().update(updates)


		this.createBattlePlayerResultArea()
		//相手のリザルトを取得していた場合、相手のリザルトを表示
		const BATTLE_PLAYPER_RESULT = Object.keys(this.battlePlayerResultData)

		if(BATTLE_PLAYPER_RESULT.length){
				const RESULT_DATA = document.getElementsByClassName("result_data")[1].firstElementChild.children
				const RESULT_ITEM = RESULT_DATA.length == 9 ? myResult.etypingPlus : myResult.normal

				for(let i=0;i<BATTLE_PLAYPER_RESULT.length;i++){
					const INDEX = RESULT_ITEM.indexOf(BATTLE_PLAYPER_RESULT[i])

					if(INDEX >= 0){
						RESULT_DATA[INDEX].lastElementChild.textContent = this.battlePlayerResultData[BATTLE_PLAYPER_RESULT[i]]
					}
				}

			document.getElementById("prev").firstElementChild.textContent = battleUserData.data.name

		}

	}


	createBattlePlayerResultArea(){
		const RESULT_DATA = document.getElementsByClassName("result_data")[1].firstElementChild.children
		document.getElementById("prev").firstElementChild.textContent = '待機中'

		for(let i=0; i<RESULT_DATA.length; i++){
			RESULT_DATA[i].lastElementChild.textContent = ''
			RESULT_DATA[i].lastElementChild.style.color = '#23c21f'
		}

		if(document.getElementsByClassName('RTCLine')[0].textContent == 'タイムアウトしました。'){
			battleUserData.resultTimeoutPlayer()
		}

	}


	onBattleResultDisplay(snapshot){
		const uid = snapshot._delegate.ref._path.pieces_[1];
		const Info = snapshot._delegate.ref._path.pieces_[3]
		const SnapShotValue = snapshot.val()


		myResult.battlePlayerResultData[Info] = SnapShotValue

		if(playerStateChange.prevState == 'result'){
		const RESULT_DATA = document.getElementsByClassName("result_data")[1].firstElementChild.children
		const RESULT_ITEM = RESULT_DATA.length == 9 ? myResult.etypingPlus : myResult.normal

			const INDEX = RESULT_ITEM.indexOf(Info)

			if(INDEX >= 0){
				RESULT_DATA[INDEX].lastElementChild.textContent = SnapShotValue
			}
			document.getElementById("prev").firstElementChild.textContent = battleUserData.data.name
		}
		firebaseDB.ref('usersState/').off('child_changed');
		firebaseDB.ref('usersState/').off('child_removed')
	}

	reBuildResultAria(){

		//e-typing plus用
		document.getElementById("RTCGamePlayScene").style.marginTop = ''
		document.getElementById("RTCGamePlayScene").style.display = 'none'
		document.getElementById("battle-display-option").style.display = 'none'
		document.getElementById("comment").style.display = 'none'
		document.getElementById("result").querySelector('article').style.height = '441px'
		document.getElementById("current").style.height = '414px'
		document.getElementById("exampleList").style.height = '345px'
		document.getElementsByClassName("result_data")[0].style.height = '365px'
		document.getElementById("prev").style.height = '414px'
		document.getElementsByClassName("result_data")[1].style.height = '365px'

		for(let i=0;i<2;i++){
			document.getElementsByClassName("result_data")[i].firstElementChild.insertAdjacentHTML('beforeend',
`<li class='battle-result'><div class="data"></div></li><li class='input-mode'><div class="data"></div></li>`)
		}

	}


	checkResultDisplay(){
		myResult.sendResult()
		myResult.reBuildResultAria()
	}



}
let myResult



class DeleteUser {

	constructor(){

	}

	userTimeoutCheck(DeleteTimeStamp){

		firebaseDB.ref('users').once('value').then(users => {

			const PLAYERS_KEY = Object.keys(users.val())
			let updates = {}

			for(let i=0;i<PLAYERS_KEY.length;i++){

				const checkID = PLAYERS_KEY[i]
				const TIMEOUT_TIME = DeleteTimeStamp - users.val()[checkID].deleteTimeStamp

				if(TIMEOUT_TIME >= 100000 || !users.val()[checkID].deleteTimeStamp){
					//50秒TimeStampの更新が無ければユーザーを削除する
					updates['/users/' + checkID] = null;
					updates['/usersState/' + checkID] = null;

				}else if(TIMEOUT_TIME >= 20000){
					//20秒TimeStampの更新が無ければタイムアウト状態にする
					updates['/usersState/' + checkID + '/state'] = "timeOut"
				}

			}

			firebaseDB.ref().update(updates);
		});
	}

}
let deleteUser = new DeleteUser()


class PlayerStateChange {

	constructor(){
		this.prevState = 'idle'
	}


	update(state){

		if(this.prevState == 'oldVersion'){return;}

		let updates = {}

		if(state != 'afk' && state != 'timeOut'){
			this.prevState = state
		}

		updates['/usersState/' + myID + '/state'] = state;


		switch(state){
			case "oldVersion":

				break;

			case "move":

				break;

			case "idle":

				break;

			case "preStart":
				firebaseDB.ref('usersState/').off('child_changed');
				firebaseDB.ref('usersState/').off('child_removed')
				firebaseDB.ref('usersState/').on('child_changed', battleUserData.onChangeUserState);
				firebaseDB.ref('usersState/').on('child_removed', battleUserData.onRemoveUserState);


				break;

			case "matching":
				battleArea.displayReadyButton()

				break;


			case "ready":
				updates['/users/' + myID + '/status/' + '/lineInput'] = '準備完了';

				break;

			case "play":

				break;

			case "soloPlay":
				firebaseDB.ref('usersState/').off('child_changed');
				firebaseDB.ref('usersState/').off('child_removed')


				break;

			case "result":

				break;

			case "afk":

				break;

			case "timeOut":

				break;
		}

		firebaseDB.ref().update(updates)

	}

}
let playerStateChange



class MyData {

	constructor(){
		this.locationDateTimeStamp
		this.localDateTimeStamp
		this.myName
		this.UserCheckTimeCount = 0
		this.lineInput = ''
		this.clearCount = 0
		this.updateTimeInterval
		this.timeStampForAFK
		this.isWrittenAFKState
		this.AFK_TIMEOUT = 60000;


		myResult = new MyResult()
		deleteUser = new DeleteUser()
		playerStateChange = new PlayerStateChange()

		this.update()
		this.loggedIn()
		this.getLocationDate().then( () => myData.startingClockTime())
	}



	update(){
		var updates = {};
		this.myName = localStorage.getItem("battleName")

		//ユーザーネーム更新
		updates['/usersState/' + myID + '/name'] = this.myName;
		updates['/users/' + myID + '/version'] = GM_info.script.version
		updates['/users/' + myID + '/status/' + '/lineInput'] = '';
		updates['/users/' + myID + '/status/' + '/clearCount'] = 0;

		firebaseDB.ref().update(updates)
		playerStateChange.update(playerStateChange.prevState)
	}

	loggedIn(){

		document.getElementById("start_btn").style.display = 'block'
		document.getElementsByClassName("loading")[0].style.display = 'none'

		window.addEventListener('beforeunload', e => {
			playerStateChange.update('move')
			myData.resetResult()
		});

		if(window.parent.document.getElementsByClassName("pp_close").length){

			window.parent.document.getElementsByClassName("pp_close")[0].addEventListener('click',e => {
			playerStateChange.update('move')
			myData.resetResult()
		});

		}

		window.addEventListener('focus', e => {
			myData.update()
			myData.startingClockTime()
			myData.resetResult()
		});

	}

	resetResult(){

		let updates = {};
		updates['/users/' + myID + '/result/'] = ''
		updates['/usersState/' + myID + '/matchPlayerKey'] = null
		firebaseDB.ref().update(updates)

	}

	updateTimeStamp(){
		const newDate = new Date().getTime()
		var updates = {};
		const deleteTimeStamp = myData.locationDateTimeStamp + (newDate - myData.localDateTimeStamp)
		updates['/users/' + myID + '/deleteTimeStamp'] = deleteTimeStamp
		myData.afkStateChange(newDate)

		//30秒に一度、ルーム内のユーザーの存在をチェックする
		if(newDate - myData.UserCheckTimeCount >= 30000){
			myData.UserCheckTimeCount = newDate
			deleteUser.userTimeoutCheck(deleteTimeStamp)
		}

		firebaseDB.ref().update(updates);
	}


	startingClockTime(){
		//ユーザー確認用タイムスタンプを更新
		this.updateTimeStamp()

		clearInterval(this.updateTimeInterval)
		this.updateTimeInterval = setInterval(this.updateTimeStamp,5000)
	}

	async getLocationDate(){
		const resp = await fetch(window.location.href)

		//サーバー時刻のタイムスタンプ
		this.locationDateTimeStamp = await new Date(resp.headers.get("date")).getTime()
		//ローカル時刻タイムスタンプ
		this.localDateTimeStamp = new Date().getTime()

		this.timeStampForAFK = new Date().getTime();

		this.afkEvent = this.afkReturnStateChange.bind(this)
		document.body.addEventListener("keydown",this.afkEvent);
		document.body.addEventListener("mousedown",this.afkEvent);
		document.body.addEventListener("mousemove",this.afkEvent);

		return true
	}


	afkReturnStateChange(){
		this.timeStampForAFK = new Date().getTime();

		if(this.isWrittenAFKState){
			this.isWrittenAFKState = false;
			playerStateChange.update(playerStateChange.prevState)
			this.updateTimeStamp()
		}

	}

	afkStateChange(newDate){
		if(newDate > this.AFK_TIMEOUT + this.timeStampForAFK && (playerStateChange.prevState == "matching" || playerStateChange.prevState == "preStart")){

			if(!this.isWrittenAFKState){
				this.isWrittenAFKState = true;
				playerStateChange.update('afk')
			}

		}


	}

}
let myData






let myID
const firebaseApp = firebase.initializeApp(firebaseConfig,"firebaseApp");
const firebaseDB = firebaseApp.database();

class LoginFirebase {

	constructor(){
		firebase.initializeApp(firebaseConfig)
		this.roginAnon()
	}


	roginAnon(){

		firebase.auth().signInAnonymously().catch(function(error) {
			// Handle Errors here.
			var errorCode = error.code;
			var errorMessage = error.message;

			console.log(errorCode);
			console.log( errorMessage);
			alert("RealTimeCombatting:Firebaseのサインインに失敗しました。");
			return false;
			// ...
		});

		firebase.auth().onAuthStateChanged(function(user) {

			if (user) {
				// User is signed in.
				myID = "U"+user.uid
				console.log('!!!')

				var path = firebaseDB.ref('users/' + myID);

				path.transaction(function(currentData) {
					//ユーザー情報を更新
					myData = new MyData()
				}).then( () => loginFirebase.versionCheck());
			}
		});

	}


	versionCheck(){
		firebaseDB.ref('newVersion').once('value').then(version => {
			const newVersion = version.val()

			if(+GM_info.script.version < +newVersion){
				playerStateChange.update('oldVersion')
			}

		}).catch(error => console.log(error));
	}
}

let loginFirebase













const typingAppMod = () => {


	//タイピング画面に移動した。

		if(setUp.battleSwitch){

			battleArea = new BattleArea()
			keyJudge = new KeyJudge()


		}

}


class SetMutationObserver {

	constructor(){
		this.observer
		this.elem = document.getElementById("app")
		this.config = {
			childList: true//「子ノード(テキストノードも含む)」の変化
		};

		this.set()
		this.startObserve()
	}

	startObserve(){
		this.observer.observe(this.elem, this.config);
	}

	stopObserve(){
		this.observer.disconnect();
	}

	set(){
		this.observer = new MutationObserver(function(event){

			const add = event[0].addedNodes[0]
			const remove = event[0].removedNodes[0]

			if(remove && remove.id == 'hands'){

				if(playerStateChange.prevState == "play"){
					setMutationObserver.setResultObserver()
				}else{
					playerStateChange.update('result')
				}

				return;
			}

			if(add && add.id == 'example_container'){
				//console.log('battleAreaSetUp')

				if(!setUp.battleSwitch){
					setMutationObserver.stopObserve()
					return;
				}

				if(battleUserData && battleUserData.data){
					battleUserData.deleteBattlePlayerEvents()
				}

				typingAppMod()
				return;
			}
		});
	}


	setResultObserver(){
		this.result = document.getElementById("result")

		this.resultObserver = new MutationObserver(function(){
			console.log('resultDisplay')
			myResult.checkResultDisplay()
			setMutationObserver.resultObserver.disconnect();

		});

		this.resultObserver.observe(this.result, this.config);
	}

}
let setMutationObserver






class KeyJudge {

	constructor(){
		this.wordReload = false;
		this.clearLine = 0

		if(keyJudge){
			keyJudge.removeEvent()
		}

		this.addEvent()
	}

	addEvent(){
		this.Event = this.wait.bind(this)
		this.playEvent = this.startSpaceKey.bind(this)
		window.addEventListener("keydown",this.Event)
		window.addEventListener("keydown",this.playEvent)
	}

	removeSpaceKeyEvent(){
		window.removeEventListener("keydown",this.playEvent)
	}

	removeEvent(){
		window.removeEventListener("keydown",this.Event)
	}


	wait(event){
		setTimeout(() => this.keyDown(event))
	}

	startSpaceKey(event){
		if(event.code == 'Space' || event.code == 'KeyL'){
			playerStateChange.update('soloPlay')
			this.removeSpaceKeyEvent()
			battleArea.displayKeyboard()


		}
	}


	judge(event , sentenceText){
		let result
		if(setUp.typingMode == "roma"){
			result = sentenceText.textContent.slice(-1).toLowerCase() == event.key ? true:false
		}else if(setUp.typingMode == "eng"){
			result = sentenceText.textContent.slice(-1).replace("␣", " ") == event.key ? true:false
		}else if(setUp.typingMode == "kana"){
			result = this.createKanaChar(event).includes(sentenceText.textContent.slice(-1))
		}
		return result;
	}

	keyDown(event){

		if(playerStateChange.prevState == 'soloPlay'){return}

		const sentenceText = document.getElementsByClassName("entered")[setUp.enteredClass]
		let key

		if(sentenceText){
			key = this.judge(event , sentenceText)
		}

		if(event.key == "Escape"){
			this.wordReload = false
		}

		if(!sentenceText && this.wordReload){
			this.sendWordData('')
			this.wordReload = true

			if(!sentenceText){
				this.wordReload = false
			}

		}else if(sentenceText && key){

			this.sendWordData(sentenceText.textContent)
			this.wordReload = true

			if(!sentenceText){
				this.wordReload = false
			}

		}
	}

	createKanaChar(event){
		let char = windows_keymap[event.code] ? windows_keymap[event.code] : kana_keymap[event.key];

		if(event.shiftKey){
			if(event.code == "KeyE"){char[0] = "ぃ";}
			if(event.code == "KeyZ"){char[0] = "っ";}
		}

		if(event.shiftKey && event.key === "0"){char = ["を"];}

		return char;
	}

	sendWordData(text) {

		var updates = {}
		updates['/users/' + myID + '/status/' + '/lineInput'] = text.substr( -25, 25 );

		if(!text){
			this.clearLine++
			updates['/users/' + myID + '/status/' + '/clearCount'] = this.clearLine

		}

		firebaseDB.ref().update(updates)
	}

}
let keyJudge








class BattleArea {

	constructor(){
		battleUserData = new BattleUserData()
		playerStateChange.update('preStart')

		const RTC = document.getElementById("RTCGamePlayScene")
		if(RTC){
			RTC.remove()
			document.getElementById("battle-display-option").remove()
			document.getElementById("user-state-area").remove()
		}

		this.createArea()
		myData.resetResult()
		this.Lstart = false

		this.joinLength = 0

		this.stateName = {
			 "preStart":'対戦募集',
			 "matching":'マッチ済み',
			 "ready":'マッチ済み',
			 "play":'対戦中',
			 "result":'リザルト',
			 "oldVersion":'マッチ不可',
			 "soloPlay":'ソロプレイ',
			 "move":'離席中',
			 "idle":'離席中',
			 "afk":'離席中',
			 "timeOut":'離席中'
		}
	}


	displayReadyButton(){
		document.getElementById("l-ready-button").style.display = 'block'
		document.getElementById("ready-button").style.display = 'block'
	}

	hideReadyButton(){
		document.getElementById("l-ready-button").style.display = 'none'
		document.getElementById("ready-button").style.display = 'none'
	}


	displayKeyboard(){
		document.getElementById('virtual_keyboard').style.display = 'block';
		document.getElementById('hands').style.display = 'block';
		document.getElementById('RTCGamePlayScene').style.display = 'none';
		document.getElementById("battle-display-option").style.display = 'none'
		document.getElementById("user-state-area").style.display = 'none'
	}

	hideKeyboard(){
		document.getElementById('virtual_keyboard').style.display = 'none';
		document.getElementById('hands').style.display = 'none';
		document.getElementById('RTCGamePlayScene').style.display = 'block';
		document.getElementById("battle-display-option").style.visibility = 'hidden'
		document.getElementById("user-state-area").style.display = 'none'
	}


	addTable(){
		document.getElementById('user-state-area').insertAdjacentHTML('beforeend',
`<table class='user-state-table'><tbody><tr><td>現在の参加者</td></tr></tbody></table>`)
	}

	updateJoinLength(){
			const JOIN_LENGTH_ELEMENT = document.getElementById("join-length")
			if(JOIN_LENGTH_ELEMENT){
				JOIN_LENGTH_ELEMENT.textContent = battleArea.joinLength
			}
	}

	createActiveUserTable(){
		document.getElementById('RTCGamePlayScene').insertAdjacentHTML('afterend',`<div id='user-state-area'></div>`)

		this.addTable()

		firebaseDB.ref('usersState').once('value').then(usersState => {

			const USERS_STATE = usersState.val()
			const USERS_KEY = Object.keys(USERS_STATE)

			for(let i=0;i<USERS_KEY.length;i++){
				const tableClass = document.getElementsByClassName("user-state-table")
				const table = tableClass[tableClass.length-1].firstElementChild
				if(USERS_STATE[USERS_KEY[i]].name){
					if(table.children.length < 4){
						battleArea.joinLength ++
						table.insertAdjacentHTML('beforeend',`<tr id='${USERS_KEY[i]}' class='${USERS_KEY[i] == myID ? 'mine': ''}'><td class='state'>${battleArea.stateName[USERS_STATE[USERS_KEY[i]].state]}</td></tr>`)
					}else{
						battleArea.addTable()
						i--
					}
				}
			}

			battleArea.updateJoinLength()

		})

		setTimeout(() => {
			const tableClass = document.getElementsByClassName("user-state-table")
			if(tableClass[0].firstChild.children.length == 1){
				document.getElementsByClassName("RTCLine")[0].innerHTML = 'データを取得できませんでした。<br>シークレットウィンドウや別のブラウザでお試しください。'
				document.getElementsByClassName("RTCLine")[0].style.fontSize = 'larger'
			}
		},2000)


	}


	createArea(){
		document.getElementById('virtual_keyboard').style.display = 'none';
		document.getElementById('hands').style.display = 'none';

		document.getElementById('start_msg').insertAdjacentHTML("afterbegin" ,
`<div style="cursor: pointer; display: none;position: absolute;bottom: -23px;left: 28px;width: 130px;font-size: 1.1rem;" class="loading" id='ready-button'>準備完了</div>
<div style="cursor: pointer;display: none;position: absolute;bottom: -23px;width: 130px;right: 26px;font-size: 0.8rem;" class="loading" id='l-ready-button'>Lスタートで準備完了</div>`)

		document.getElementById("ready-button").addEventListener('click', event => {
			event.target.style.display = 'none'
			document.getElementById("l-ready-button").style.display = 'none'
			battleArea.Lstart = false
			playerStateChange.update('ready')
		})

		document.getElementById("l-ready-button").addEventListener('click', event => {
			event.target.style.display = 'none'
			document.getElementById("ready-button").style.display = 'none'
			battleArea.Lstart = true
			playerStateChange.update('ready')
		})


		const SEARCH_PLAYER_GUIDE = playerStateChange.prevState != "oldVersion" ? `対戦相手を探しています (<span id='join-length'>0</span>人参加中)` : `<a href="https://greatest.deepsurf.us/ja/scripts/471999-e-%E5%AF%BE%E6%88%A6-e-typing/versions" style='font-weight:bold;'>こちら</a>から最新のスクリプトに更新をお願いします`

		document.getElementById('example_container').insertAdjacentHTML('afterend',
`<div id='battle-display-option'>
<div class="display-head"><strong>表示方法</strong></div>
<div><label><input type="radio" name="layout" id="keyboard-display" ${localStorage.getItem('battle-display-option') == 'keyboard-display' || !localStorage.getItem('battle-display-option')? 'checked' : ''}>キーボード</label>
<label><input type="radio" name="layout" id="player-display" ${localStorage.getItem('battle-display-option') == 'player-display' ? 'checked' : ''}>対戦相手</label></div>
</div>
<div id="RTCGamePlayScene"><table class='user-table' rules="all" border="1"><tbody>
<tr>
<td class='user-name'></td>
<td><span  class="RTCLine" style='color:#7b7a7a;'>${SEARCH_PLAYER_GUIDE}</span></td>
<td class="InputMode"></td></tr></tbody></table></div>`)

		document.getElementById("battle-display-option").addEventListener('change', event => {
			localStorage.setItem('battle-display-option',event.target.id)
		})

		this.createActiveUserTable()
	}



	addBattleStatusTable(userName,key,inputType){
		const INPUT_TYPE = inputType != 'kana' ? 'ローマ字' : 'かな'
		document.getElementsByClassName("user-name")[0].textContent = userName
		document.getElementsByClassName("InputMode")[0].textContent = INPUT_TYPE
		document.getElementsByClassName("RTCLine")[0].textContent = '対戦相手の準備完了を待ってます'
		document.getElementsByClassName("RTCLine")[0].insertAdjacentHTML('afterend',`<div id="battle-progress-bar"></div>`)

		document.getElementById("battle-progress-bar").style.width = (document.getElementsByClassName("RTCLine")[0].parentElement.clientWidth-3) + 'px'

	}

	removeBattleStatusTable(){
		setTimeout( () => {
			if(playerStateChange.prevState == "preStart"){
				document.getElementsByClassName("RTCLine")[0].textContent = '対戦相手を探しています'
			}
		},2000)
		document.getElementsByClassName("user-name")[0].textContent = ''
		document.getElementsByClassName("InputMode")[0].textContent = ''
		document.getElementsByClassName("RTCLine")[0].style.color = '#7b7a7a'
		document.getElementsByClassName("RTCLine")[0].textContent = '離脱しました'


		if(document.getElementById("battle-progress-bar") != null){
			document.getElementById("battle-progress-bar").remove()
		}

	}

}
let battleArea



class BattleUserData{

	constructor(){
		this.data
	}


	onUpdateUserStatus(snapshot){
		const uid = snapshot._delegate.ref._path.pieces_[1];
		const Update_Info = snapshot._delegate.ref._path.pieces_[3]
		const SnapShotValue = snapshot.val()

		switch(Update_Info){
			case "clearCount":
				document.getElementById("battle-progress-bar").style.transform = `scaleX(${(100 - (+SnapShotValue / 15 * 100))/100})`

				break;

			case "lineInput":
				if(SnapShotValue){
					document.getElementsByClassName('RTCLine')[0].textContent = SnapShotValue;
				}else{
					document.getElementsByClassName('RTCLine')[0].textContent = "";
				}
				break;
		}
	}

	findBattlePlayer(name,key,state){
		battleUserData.data = {
			name:name,
			key:key,
			mode:'roma',
			state:state
		}

		battleArea.addBattleStatusTable(battleUserData.data.name , battleUserData.data.key , battleUserData.data.mode)
		this.addBattlePlayerEvents()
		playerStateChange.update('matching')

		let updates = {}
		updates['/usersState/' + myID + '/matchPlayerKey'] = battleUserData.data.key

		firebaseDB.ref().update(updates)

		document.getElementById("start_msg").getElementsByTagName('em')[0].textContent = 'スペースキーを押すと一人プレイで開始します'

		const userTable = document.getElementById(battleUserData.data.key)

		if(userTable){
			userTable.style.fontWeight = 'bold'
		}

	}

	addBattlePlayerEvents(){
		firebaseDB.ref('users/' + battleUserData.data.key + '/status').on('child_changed', battleUserData.onUpdateUserStatus);
		firebaseDB.ref('users/' + battleUserData.data.key + '/result').on('child_added', myResult.onBattleResultDisplay);
	}


	deleteBattlePlayerEvents(){
		firebaseDB.ref('users/' + battleUserData.data.key + '/status').off('child_changed');
		firebaseDB.ref('users/' + battleUserData.data.key + '/result').off('child_added');
	}

	lostBattlePlayer(){
		this.deleteBattlePlayerEvents()
		battleUserData.data = null
		battleArea.hideReadyButton()
		playerStateChange.update('preStart')
		document.getElementById("start_msg").getElementsByTagName('em')[0].textContent = 'スペースキーで開始'
		battleArea.removeBattleStatusTable()
	}


	resultTimeoutPlayer(){
		this.deleteBattlePlayerEvents()
		const RESULT_DATA = document.getElementsByClassName("result_data")[1].firstElementChild.children
		document.getElementById("prev").firstElementChild.textContent = 'タイムアウト'

		for(let i=0; i<RESULT_DATA.length; i++){
			RESULT_DATA[i].lastElementChild.textContent = '-'
			RESULT_DATA[i].lastElementChild.style.color = '#23c21f'
		}

	}

	updateUserTable(uid, name, state){
		const userTable = document.getElementById(uid)
		if(!name){return;}

		if(userTable){
			//userTable.getElementsByClassName("name")[0].textContent = name;
			userTable.getElementsByClassName("state")[0].textContent = battleArea.stateName[state];
		}else{
			const tableClass = document.getElementsByClassName("user-state-table")
			const table = tableClass[tableClass.length-1].firstElementChild

			if(table.children.length < 4){
				battleArea.joinLength ++
				table.insertAdjacentHTML('beforeend',`<tr id='${uid}'><td class='state'>${battleArea.stateName[state]}</td></tr>`)
				battleArea.updateJoinLength()
			}else{
				battleArea.addTable()
				this.updateUserTable(uid, name, state)
			}

		}

	}

	removeUserTable(uid){
		const userTable = document.getElementById(uid)

		if(userTable){
			battleArea.joinLength --
			userTable.remove()
			battleArea.updateJoinLength()
		}
	}

	onChangeUserState(snapshot){
		const uid = snapshot._delegate.ref._path.pieces_[1];
		const name = snapshot.val().name
		const state = snapshot.val().state
		const matchPlayerKey = snapshot.val().matchPlayerKey

		//対戦相手のstateを更新する
		if(battleUserData.data && uid == battleUserData.data.key){
			battleUserData.data.state = state;
		}

		if( playerStateChange.prevState != "play"){
			battleUserData.updateUserTable(uid, name, state)
		}

		if(!battleUserData.data){


			//お互いに状態がpreStartのプレイヤーをマッチさせる
			if( playerStateChange.prevState == "preStart"){

				//先にマッチ画面で待機していたプレイヤー
				if(uid != myID && state == 'preStart'){
					battleUserData.findBattlePlayer(name , uid , state)
				}

				//後から入室したプレイヤー
				if(matchPlayerKey == myID && state == 'matching'){
					battleUserData.findBattlePlayer(name , uid , state)
				}

			}
		}


		if(battleUserData.data){
			if(battleUserData.data.state == 'ready'){
				document.getElementsByClassName("RTCLine")[0].style.color = ''
			}


			//自分と相手が準備完了したら同時に開始。
			if( (battleUserData.data.key == uid || myID == uid) && (battleUserData.data.state == 'ready' && playerStateChange.prevState == "ready") ){
				document.dispatchEvent( new KeyboardEvent("keydown",{
					// keyCode:32 スペースキー keyCode:76 Lキー
					// keyプロパティ & codeプロパティでは開始できませんでした。
					keyCode: (battleArea.Lstart ? 76 : 32)

				}))

				const layoutRadioButton = document.querySelector("input[name='layout']:checked")

				if(layoutRadioButton.id == 'keyboard-display'){
					battleArea.displayKeyboard()
				}else{
					battleArea.hideKeyboard()
				}

				document.getElementsByClassName('RTCLine')[0].textContent = '';
				playerStateChange.update('play')
				keyJudge.removeSpaceKeyEvent()
			}

			//相手が離脱した。
			if(uid == battleUserData.data.key && (state == 'soloPlay' || state == 'idle' || state == 'timeOut' || state == 'move' || state == 'afk')){

				if(playerStateChange.prevState != "play" && playerStateChange.prevState != "result"){
					battleUserData.lostBattlePlayer()
				}else if(playerStateChange.prevState == "play"){
					document.getElementsByClassName('RTCLine')[0].textContent = 'タイムアウトしました。';
					battleUserData.deleteBattlePlayerEvents()
				}else if(playerStateChange.prevState == "result"){
					battleUserData.resultTimeoutPlayer()
				}
				myData.resetResult()
			}


		}

	}

	onRemoveUserState(snapshot){
		const uid = snapshot._delegate.ref._path.pieces_[1];

		if( playerStateChange.prevState != "play"){
			battleUserData.removeUserTable(uid)
		}
	}


}
let battleUserData




////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////







const kana_keymap = {
	0: ["わ"],
	1: ["ぬ"],
	"!": ["ぬ"],
	2: ["ふ"],
	3: ["あ"],
	4: ["う"],
	5: ["え"],
	6: ["お"],
	7: ["や"],
	8: ["ゆ"],
	9: ["よ"],
	"-": ["ほ","-"],
	"q": ["た"],
	"Q": ["た"],
	"w": ["て"],
	"W": ["て"],
	"e": ["い"],
	"E": ["い"],
	"r": ["す"],
	"R": ["す"],
	"t": ["か"],
	"T": ["か"],
	"y": ["ん"],
	"Y": ["ん"],
	"u": ["な"],
	"U": ["な"],
	"i": ["に"],
	"I": ["に"],
	"o": ["ら"],
	"O": ["ら"],
	"p": ["せ"],
	"P": ["せ"],
	"a": ["ち"],
	"A": ["ち"],
	"s": ["と"],
	"S": ["と"],
	"d": ["し"],
	"D": ["し"],
	"f": ["は"],
	"F": ["は"],
	"g": ["き"],
	"G": ["き"],
	"h": ["く"],
	"H": ["く"],
	"j": ["ま"],
	"J": ["ま"],
	"k": ["の"],
	"K": ["の"],
	"l": ["り"],
	"L": ["り"],
	"z": ["つ"],
	"Z": ["つ"],
	"x": ["さ"],
	"X": ["さ"],
	"c": ["そ"],
	"C": ["そ"],
	"v": ["ひ"],
	"V": ["ひ"],
	"b": ["こ"],
	"B": ["こ"],
	"n": ["み"],
	"N": ["み"],
	"m": ["も"],
	"M": ["も"],
	",": ["ね",","],
	"<": ["、"],
	".": ["る","."],
	">": ["。"],
	"/": ["め","/"],
	"?": ["・"],
	"#": ["ぁ"],
	"$": ["ぅ"],
	"%": ["ぇ"],
	"'": ["ゃ","’","'"],
	"^": ["へ"],
	"~": ["へ"],
	"&": ["ぉ"],
	"(": ["ゅ"],
	")": ["ょ"],
	'|': ["ー"],
	"_": ["ろ"],
	"=": ["ほ"],
	"+": ["れ"],
	";": ["れ"],
	'"': ["ふ","”","“","\""],
	"@": ["゛"],
	'`': ["゛"],
	"[": ["゜"],
	']': ["む"],
	"{": ["「"],
	'}': ["」"],
	":": ["け"],
	"*": ["け"]
}

const windows_keymap = {
	'IntlYen': ["ー","¥","\\"],
	"IntlRo": ["ろ","¥","\\"],
	"Space": [" "],
	"Numpad1": [],
	"Numpad2": [],
	"Numpad3": [],
	"Numpad4": [],
	"Numpad5": [],
	"Numpad6": [],
	"Numpad7": [],
	"Numpad8": [],
	"Numpad9": [],
	"Numpad0": [],
	"NumpadDivide": [],
	"NumpadMultiply": [],
	"NumpadSubtract": [],
	"NumpadAdd": [],
	"NumpadDecimal": []
}



class SetUp {

	constructor(){

		this.typingMode = 'roma'
		this.enteredClass = 2
		this.battleSwitch = true

		this.checkDisplayMenu()
		loginFirebase = localStorage.getItem("battle-option") != "false" ? new LoginFirebase() : null
	}


	checkDisplayMenu(){
		const config = {
			childList: true//「子ノード(テキストノードも含む)」の変化
		};
		const Observe = new MutationObserver(function(event){
			setUp.createMenu()
			setMutationObserver = new SetMutationObserver()
			Observe.disconnect();
		});

		Observe.observe(document.getElementById("app"), config);

	}


	checkTypingMode(){

		if(location.href.match(/kana\.1/)){
			this.typingMode = "kana"
			this.enteredClass = 1
		}else if(location.href.match(/std\.2/) || location.href.match(/lstn\.4/)){
			this.typingMode = "eng"
			this.enteredClass = 1
		}else{
			this.typingMode = "roma"
			this.enteredClass = 2
		}

	}

	createMenu(){

		const NAME = localStorage.getItem("battleName")
		setUp.battleSwitch = localStorage.getItem("battle-option") == "false" ? false : true;

		if(!localStorage.getItem("battle-option")){
			localStorage.setItem("battle-option",'true')
		}
		if(loginFirebase){
			document.getElementById("start_btn").style.display = setUp.battleSwitch == false ? '' : 'none'
			document.getElementById("start_btn").insertAdjacentHTML('afterend',`<div class='loading'>対戦データベースに接続中</div>`)
		}

		this.addCss()
		const FUNC_VIEW = document.getElementById("func_view")

		FUNC_VIEW.style.height = document.getElementById("func_view").clientHeight + 30 + "px"
		FUNC_VIEW.insertAdjacentHTML('beforeend' ,
									 `<div><div>
<label><small>対戦機能</small>
<input id="battle-option" type="checkbox" style="display:none;" ${setUp.battleSwitch == false ? "" : "checked"}>
<div id="sound-effect-btn" style="margin-left:4px;" class="switch_btn"><a class="on_btn btn show">ON</a>
<a class="off_btn btn" style="display:${setUp.battleSwitch == false ? "block" : ""};">OFF</a></div>
</label>
<input type="text" id="battle-name" placeholder="Name" value="${NAME ? NAME : 'Guest'}" maxlength="10" style="display:${setUp.battleSwitch == false ? "none" : "inline"}; position: absolute;width: 7rem;margin: 2px;right: 85px;">
</div></div>`)

		if(!NAME){
			localStorage.setItem("battleName" , 'Guest')
		}


		document.getElementById("battle-name").addEventListener("change", event => {
			localStorage.setItem("battleName" , event.target.value)
			myData.update()
		})

		document.getElementById("battle-option").addEventListener("change" , event => {
			localStorage.setItem("battle-option" , event.target.checked);

			if(event.target.checked){
				document.querySelector("#sound-effect-btn .off_btn").style.display = ""
				document.getElementById("battle-name").style.display = "inline"
				setUp.battleSwitch = true;

				if(!loginFirebase){
					loginFirebase = new LoginFirebase()
					document.getElementById("start_btn").style.display = setUp.battleSwitch == false ? '' : 'none'
					document.getElementById("start_btn").insertAdjacentHTML('afterend',`<div class='loading'>対戦データベースに接続中</div>`)
				}

			}else{
				document.querySelector("#sound-effect-btn .off_btn").style.display = "block"
				document.getElementById("battle-name").style.display = "none"
				setUp.battleSwitch = false;
			}

		})
	}

	addCss(){

		document.getElementById("app").insertAdjacentHTML('afterend',`<style>


.loading{
    color: #fff;
    font-size: 12px;
    font-weight: bold;
    background-color: #057fff;
    width: 160px;
    height: 45px;
    margin: 0 auto;
    text-align: center;
    line-height: 45px;
    overflow: hidden;
    border-radius: 3px;
}

.user-table{
width: 100%;
position: relative;
height: 68px;
left: 0;
right: 0;
margin: auto;
}

.user-table tr{
font-weight:bold;
height: 3rem;
}

.user-name{
    font-size: 1rem;
    width: 90px;
text-align: center;
}

.RTCLine{
max-width: 350px;
white-space: nowrap;
overflow:hidden;
width: 68%;
color:#ffd0a6;
font-size: 26px;
font-weight: normal;
}

.InputMode{
font-size: 1rem;
    width: 95px;
text-align: center;
}

.clear-line{
font-size: 1rem;
text-align: center;
}

#RTCGamePlayScene{
margin: 8px;
margin-top:1rem;
}

.display-head{
margin-left: 0.2rem;
font-size: 1rem;
margin-bottom: 0.3rem;
}

#battle-display-option{
margin-left: 4rem;
display: flex;
flex-direction: column;
}


#battle-progress-bar{
    position: absolute;
    bottom: 1px;
    background-color: #bcbcbc;
    height: 4px;
    transform-origin: left top;
}

#user-state-area{
    display: flex;
    justify-content: space-evenly;
    align-items: flex-start;
    margin-top: 1.3rem;
}


.user-state-table, .user-state-table td, .user-state-table th{
	border: 1px solid #595959;
	border-collapse: collapse;
    text-align: center;
}


.user-state-table td, .user-state-table th {
	padding: 3px;
	width: 100px;
	height: 25px;

}

.mine{
font-weight:bold;
}
</style>`)

	}



}

const setUp = new SetUp()