e-対戦 [e-typing]

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

Verzia zo dňa 06.08.2023. Pozri najnovšiu verziu.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, Greasemonkey alebo Violentmonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, % alebo Violentmonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, % alebo Violentmonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey alebo Userscripts.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie správcu používateľských skriptov.

(Už mám správcu používateľských skriptov, nechajte ma ho nainštalovať!)

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

(Už mám správcu používateľských štýlov, nechajte ma ho nainštalovať!)

// ==UserScript==
// @name         e-対戦 [e-typing]
// @namespace    http://tampermonkey.net/
// @version      3.8
// @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
		firebase.database().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.ref_.path.pieces_[1];
		const Info = snapshot.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
		}
		firebase.database().ref('usersState/').off('child_changed');
	}

	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){

		firebase.database().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"
				}

			}

			firebase.database().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":
				firebase.database().ref('usersState/').off('child_changed');
				firebase.database().ref('usersState/').off('child_removed')
				firebase.database().ref('usersState/').on('child_changed', battleUserData.onChangeUserState);
				firebase.database().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":
				firebase.database().ref('usersState/').off('child_changed');

				break;

			case "result":

				break;

			case "afk":

				break;

			case "timeOut":

				break;
		}

		firebase.database().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;

		firebase.database().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
		firebase.database().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)
		}

		firebase.database().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
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 = firebase.database().ref('users/' + myID);

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

	}


	versionCheck(){
		firebase.database().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]
			console.log(add)
			console.log(remove)

			if(remove && remove.id == 'hands' && playerStateChange.prevState != "soloPlay"){
				console.log('resultDisplay')
				setMutationObserver.setResultObserver()
				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('result!')
			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

		}

		firebase.database().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.stateName = {
			 "oldVersion":'マッチ不可',
			 "move":'離席中',
			 "idle":'離席中',
			 "preStart":'対戦募集',
			 "matching":'マッチ済み',
			 "ready":'マッチ済み',
			 "play":'対戦中',
			 "soloPlay":'ソロ',
			 "result":'リザルト',
			 "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><td>状態</td></tr></tbody></table>`)
	}

	createActiveUserTable(){
		document.getElementById('RTCGamePlayScene').insertAdjacentHTML('afterend',
`<div id='user-state-area'><table class='user-state-table'><tbody><tr><td>参加者</td><td>状態</td></tr></tbody></table></div>`)

		firebase.database().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){
						table.insertAdjacentHTML('beforeend',`<tr id='${USERS_KEY[i]}' class='${USERS_KEY[i] == myID ? 'mine': ''}'><td class='name'>${USERS_STATE[USERS_KEY[i]].name}</td><td class='state'>${battleArea.stateName[USERS_STATE[USERS_KEY[i]].state]}</td></tr>`)
					}else{
						battleArea.addTable()
						i--
					}
				}
			}


		});



	}


	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" ? '対戦相手を探しています' : `<a href="https://greatest.deepsurf.us/ja/scripts/471999-e-%E5%AF%BE%E6%88%A6-e-typing/versions">こちら</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.ref_.path.pieces_[1];
		const Update_Info = snapshot.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

		firebase.database().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(){
		firebase.database().ref('users/' + battleUserData.data.key + '/status').on('child_changed', battleUserData.onUpdateUserStatus);
		firebase.database().ref('users/' + battleUserData.data.key + '/result').on('child_added', myResult.onBattleResultDisplay);
	}


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

	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(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){
				table.insertAdjacentHTML('beforeend',`<tr id='${uid}'><td class='name'>${name}</td><td class='state'>${battleArea.stateName[state]}</td></tr>`)
			}else{
				battleArea.addTable()
				this.updateUserTable(uid, name, state)
			}

		}

	}

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

		if(userTable){
			userTable.remove()
		}
	}

	onChangeUserState(snapshot){
		const uid = snapshot.ref_.path.pieces_[1];
		const Update_Info = snapshot.ref_.path.pieces_[3]
		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.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()