非同期通信 (WebSocket) の文字揃えゲーム作成

# 背景
過去記事：[JavaとJavaScriptでwebブラウザとのソケット通信①
](https://qiita.com/take4eng/items/d0b009c48ee8c3fe420a)
過去に非同期通信を用いてチャットアプリを作成した。
⇒改造してチャットとは異なるアプリケーションを作成。

twitterで見かけた『～揃えゲーム』を作成。
個人制作の備忘録。
![無題.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634805/1aac4371-8142-8e01-0e3c-9c4e855142df.jpeg)

## 目的
- ソケット通信で送受信されているデータを取得して編集する
- JavaScriptを用いて受信データの表示方法を学ぶ

# 実践内容
1. クライアントから送信されたデータをサーバーサイドプログラムによって編集
2. 編集したデータをクライアントへ送信
3. サーバーから送信されたデータ(文字列)を1文字ずつ順番に表示

# 成果物
1. なんでも揃えゲーム  
⇒ 送信したデータがランダムに返信されるプログラム

# 言語
サーバープログラム：Java
クライアントプログラム：JavaScript

# コード内容

1. RandomSocket.java：ソケット通信のためのサーバープログラム
2. RandomLogic.java：受信したデータを編集するプログラム
3. randomView.html：表示用のHTML
4. random.js：ソケット通信および動的表示のためのクライアントプログラム

## ソケット通信(サーバー)

```java:RandomSocket.java
package randomWeb;

import java.io.IOException;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;

import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;

@ServerEndpoint(value = "/random")
public class RandomSocket {
	private static Set<Session> user = new CopyOnWriteArraySet<>();

	@OnOpen//クライアントと接続したとき
    public void onOpen(Session mySession) {
        System.out.println("connect ID:"+mySession.getId());
        user.add(mySession);
    }

    @OnMessage//クライアントからデータが送信されたとき
    public void onMessage(String text , Session mySession) {
    	RandomLogic randomLogic=new RandomLogic(text);
    	System.out.println(text);
    	String random=randomLogic.logic();
    	for (Session user : user) {
    		user.getAsyncRemote().sendText(random);
            System.out.println(user.getId()+"番目に"+mySession.getId()+"番目のメッセージを送りました！");
    	}
    	if(text.equals("bye")) onClose(mySession);
    }

    @OnClose//クライアントが切断したとき
    public void onClose(Session mySession) {
    	System.out.println("disconnect ID:"+mySession.getId());
        user.remove(mySession);
        try {
			mySession.close();
		} catch (IOException e) {
			System.err.println("エラーが発生しました: " + e);
		}
    }

}
```

### ポイント
1. `@onMessage`でクライアントからデータを受信
2. RandomLogicインスタンスを作成しデータを渡す
3. 変数randomに編集後のデータを受け取り、それをクライアントに送信する


## データ編集

```java:RandomLogic.java
package randomWeb;

public class RandomLogic {
	private String text="";
	private String random="";

	public RandomLogic(String text) {//コンストラクタ
		this.text=text;
	}

	public String logic() {

		char[] textArray=text.toCharArray();//textを1文字ずつ配列に分割
		char[] textArray2=new char[textArray.length];//分身となる配列を作成
		int a=0;//配列の添え字
		for(int i=0;i<textArray.length;i++) {
			a=new java.util.Random().nextInt(textArray.length);
			textArray2[i]=textArray[a];//分身に本家の1文字をランダムに代入
			random+=textArray2[i];//変数randomに順番に文字を追加（for文が終了した時点で1単語分）
		}
		while(true) {
			String text2 = new String(textArray2);//分身の配列を文字列に変換
			if(text2.equals(text)) break;//分身の文字列が本家の文字列と等しいならbreak

			for(int i=0;i<textArray2.length-1;i++) {
				textArray2[i]=textArray2[i+1];//分身の文字を１文字ずつ前にずらす
			}

			a=new java.util.Random().nextInt(textArray2.length);
			textArray2[textArray2.length-1]=textArray[a];//分身の最後の文字を本家の文字からランダムに選ぶ
			random+=textArray2[textArray2.length-1];//追加された1文字を変数randomに追加する
		}
		return random;//最後にこれまでの文字列を返す
	}
}
```

### ポイント
コメントの通り。
やりたいことは文字列を編集してreturnすること。

## 表示用のHTML

```html:randomView.html
<!DOCTYPE html>
<html>
<head>
	<meta charset="UTF-8">
	<title>ランダムシステム</title>
	<script type="text/javascript" src="random.js"></script>
</head>
<body>
	<div style="width: 500px; height:500px; overflow-y: auto; border: 1px solid #333;" id="show">
	</div>
		<input type="text" size="80" id="msg" name="msg" />
		<input type="button" value="送信" onclick="sendMsg();" />
</body>
</html>
```

### ポイント
特になし。


## ソケット通信および動的表示

```javascript:random.js
// WebSocketオブジェクト生成
var wSck= new WebSocket("ws://localhost:8080/randomWeb/random");

//ソケット接続時のアクション
wSck.onopen = function() {
	document.getElementById('show').innerHTML += "接続しました。" + "<br/>";
};

//メッセージを受け取ったときのアクション
wSck.onmessage = function(e) {
	var text=e.data;
	message(text);
};
//時間間隔を空けて表示する
var nextText='';
var length=''
function message(text){
	if (text !== '') {
		if(nextText==''){
			length=text.length;
		}
		var c = text.slice(0, 1);//テキストの先頭1文字を取得
		document.getElementById('show').innerHTML += c;//1文字を表示
		nextText = text.slice(1);//テキストの先頭1文字を削除
		setTimeout('message(nextText)', 1);//1ms毎にループ
	}else{
		document.getElementById('show').innerHTML += '<br/>『'+length+'文字目』でそろったよ！<br/>';//最後に改行
	}
}

//メッセージを送信するときのアクション
var sendMsg = function(val) {
	var line = document.getElementById('msg');//入力内容を取得
	wSck.send(line.value);//ソケットに送信
	console.log(line.value);
	line.value = "";//内容をクリア
};
```

### ポイント
1. データを受信したとき、`message()`メソッドで1文字ずつ順番に表示する
2. 初回のみ`length=text.length;`で文字数を代入しておく
3. `var c = text.slice(0, 1)`で先頭1文字を取得し表示する
4. `nextText = text.slice(1);`で先頭1文字を削除
5. `setTimeout('message(nextText)', 1);`で1ms毎に`message()`メソッドが実行される
⇒ `setTimeout()`メソッドで`message()`メソッドを入れ子構造にすることで無限ループとなる
6. 全ての文字を表示し終わったら文字数等を表示して終了

# 実行結果

1. ブラウザでHTMLファイルを実行。
2. 文字列を入力し送信。(今回は『おぱんつ』)
3. 送信した文字列の中から1文字ずつランダムに表示される。
4. 元の文字列が揃えば終了。

特に意味はないが、チャットアプリケーションを応用しているため、複数ブラウザで実行すると全てのクライアントにランダムな文字群が表示される。

![ランダムシステム.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634805/a13baf32-b759-b238-d5d2-ae0d3392ca72.gif)


# その他
## 改善点

・文字列が長すぎると処理に時間がかかりすぎる
完全ランダムなため、文字列が揃う確率は指数関数的に減衰していく。
3～6文字くらいでしか遊べない。

・ボックスをオーバーフローすると自動で観測できない
はみ出た分はスクロールで見ることができるが、自動で追ってくれないため手動でスクロールバーを操作しなければならない。
⇒ jQueryのanimateメソッドを使えばうまくスクロールできそうだが、今回は断念。要検討。

## 感想
以前コンソール上で動作する文字揃えゲームを作成したことがあるが、ブラウザで表示するとなると工数がかなり増える。
特に表示方法にこだわるとJavaScriptの記述もかなり必要になってくる。

改良すれば適宜更新していく。