tcpの実験用サーバー Java編

以下のプログラムのtcpreply.jarは、ここからダウンロードできます。

001
002
003
004
005
006
007
008
009
010 011
012
013
014
015
016
017
018
019
020 021
022
023
024
025
026
027
028
029
030 031
032
033
034
035
036
037
038
039
040 041
042
043
044
045
046
047
048
049
050 051
052
053
054
055
056
057
058
059
060 061
062
063
064
065
066
067
068
069
070 071
072
073
074
075
076
077
078
079
080 081
082
083
084
085
086
087
088
089
090 091
092
093
094
095
096
097
098
099
100 101
102
103
104
105
106
107
108
109
110 111
112
113
114
115
116
117
118
119
120 121
122
123
124
125
126
127
128
129
130 131
132
133
134
135
136
137
138
139
140 141
142
143
144
145
146
147
148
149
150 151
152
153
154
155
156
157
158
159
160 161
162
163
164
165
166
167
168
169
170 171
172
173
174
175
176
177
178
179
180 181
182
183
184
185
186
187
188
189
190 191
192
193
194
195
196
197
198
199
200 201
202
203
204
205
206
207
208
209
210 211
212
213
214
215
216
217
218
219
220 221
222
223
224
225
226
227
228
229
230 231
232
233
234
235
236
237
238
239
240 241
242
243
244
245
246
247
248
249
250 251
252
253
254
255
256
257
258
259
260 261
262
263
264
265
266
267
268
269
270 271
272
273
274
275
276
277
278
279
280 281
282
283
284
285
286
287
288
289
290 291
292
293
294
295
296
297
298
299
300 301
302
303
304
305
306
307
308
309
310 311
312
313
314
315
316
317
318
319
320 321
322
323
324
325
326
327
328
329
330 331
332
333
334
335
336
337
338
339
340 341
342
343
344
345
346
347
348
349
350 351
352
353
354
355
356
357
358
359
360 361
362
363
364
365
366
367
368
369
370 371
372
373
374
375
376
377
378
379
380 381
382
383
384
385
386
387
388
389
390 391
392
393
394
395
396
397
398
399
400 401
402
403
404
405
406
407
408
409
410 411
412
413
414
415
416
417
418
419
420 421
422
423
424
425
426
427
428
429
430 431
432
433
434
435
436
437
438
439
440 441
442
443
444
445
446
447
448
449
450 451
452
453
454
455
456
457
458
459
460 461
462
463
464
465
466
467
468
469
470 471
472
473
474
475
476
477
478
479
480 481
482
483
484
485
486
487
488
489
490 491
492
493
494
495
496
497
498
499
500 501
502
503
504
505
506
507
508
509
510 511
512
513
514
515
516
517
518
519
520 521
522
523
524
525
526
527
528
529
530 531
532
533
534
535
536
537
538
539
540 541
542
543
544
545
546
547
548
549
550 551
552
553
554
555
556
557
558
559
560 561
562
563
564
565
566
567
568
569
570 571
572
573
574
575

/*
接続してきたら、「はい、接続しました」のメッセージを送ります。
次で受信した先頭文字列がsから始まる6桁の数字であれば、
「状態を表示します」
「1.『sから始まる6桁』]
「2.『sから始まる6桁』] と列挙してプレイヤーを選択用リストを列挙する。
そして、「五目並べです。先手ならば『f』、後手ならば『s』を入力ください。」の表示をします。
ただし、リストでだれもいなければ、はじめから
「1・・・・・・・・・・・・・・・・・・・・」の20のマトリックを表示させ、「配置するコマの縦位置と、横位置を2つ入力させる』

20のマトリックス表示は、先手が○後手が×で表示される。
「  1 2 3 4 5 6 7 8 9 a b c d e f g h i j k」
「 1     ○  ●            」
 */
import java.io.*;
import java.net.*;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Date;
import java.util.ArrayList;
import java.util.regex.*;//Pattern, Matcher用

class Gomoku {
	public static final int SIZE = 20;
	public static String newLine = System.getProperty("line.separator");
	static final String x[] = { " 0", " 1", " 2", " 3", " 4", " 5", " 6", " 7", " 8", " 9", " a", " b", " c", " d", " e", " f", " g", " h", " i", " j", " k" };
	public char[][] coma = new char[SIZE][];

	public int downCount = 0;//残りコマ数

	public static ArrayList<Gomoku> listGame = new ArrayList<Gomoku>();

	TcpReply.User firstPlayrt;//先手プレイヤー
	TcpReply.User secondPlayrt;//後手プレイヤー

	public int m勝敗 = -2; // -1なら引き分け、0なら先手、1なら後手の勝ち

	public Gomoku() {//コンストラクタ
		for (int row = 0; row < this.coma.length; row++) {
			coma[row] = new char[SIZE];

			for (int col = 0; col < coma[row].length; col++) {
				coma[row][col] = '・';
				downCount++;
			}
		}
		downCount = 100;//この数内で判定
	}

	synchronized public static int getGameListCount() {
		int count = listGame.size();
		return count;
	}

	//先手プレイヤーと後手プレイヤーのリスト
	synchronized public static String getGameList() {
		int count = listGame.size();
		String rtnval = "";
		if (count == 0) return "ゲーム参加者は現在だれもいません。" + newLine;
		for (int i = 0; i < listGame.size(); i++) {
			Gomoku g = listGame.get(i);
			if (g == null) continue;
			rtnval += i + "番 先手:";
			rtnval += listGame.get(i).firstPlayrt.s学籍番号;
			if (g.secondPlayrt == null) {
				rtnval += "---------対戦者を待っています。" + newLine;
			}
			else {
				rtnval += "<-->後手" + g.secondPlayrt.s学籍番号;
				if (g.m勝敗 == -2) rtnval += "  で対戦中" + newLine;
				if (g.m勝敗 == -1) rtnval += "  で引き分け" + newLine;
				if (g.m勝敗 == 0) rtnval += "  で先手が勝ち" + newLine;
				if (g.m勝敗 == 1) rtnval += "  で後手が勝ち" + newLine;
			}
		}
		return rtnval;
	}

	//先手プレイヤーの登録
	synchronized public static void addFirstPlayer(TcpReply.User user) {
		listGame.add(user.gomoku);
		user.gomoku.firstPlayrt = user;
	}

	//後手プレイヤーの登録
	synchronized public static boolean addSecondPlayer(int idx, TcpReply.User user) {
		Gomoku gomoku = listGame.get(idx);
		if (gomoku == null) return false;
		if (gomoku.secondPlayrt != null) return false;
		gomoku.secondPlayrt = user;
		gomoku.firstPlayrt.opponent = user;
		user.opponent = gomoku.firstPlayrt;
		user.gomoku = gomoku;
		user.order = 1;//後手
		return true;
	}

	//観戦情報
	synchronized public static String getPlayString(int idx) {
		Gomoku gomoku = listGame.get(idx);
		if (gomoku == null) return "存在しないゲームです。";
		return gomoku.toString();
	}

	//コマを置けるか?
	public boolean isPutComma(char y, char x) {
		boolean rtnval = false;
		int iy = (int)(y - '0');
		if (y >= 'a' && y <= 'z') iy = 10 + ((int)y - (int)'a');
		int ix = (int)(x - '0');
		if (x >= 'a' && y <= 'z') ix = 10 + ((int)x - (int)'a');
		//System.out.println(y + ", " + x + ":" + iy + ", " + ix);
		if (this.coma[iy][ix] == '・') rtnval = true;
		return rtnval;
	}

	//再帰的に指定の方向に並ぶ同一コマ数を取得する.
	public int getCountDirect(int iy, int ix, int dy, int dx) {
		int count = 0;
		char c = this.coma[iy][ix];
		ix += dx;
		iy += dy;
		if (ix >= 0 && ix < SIZE && iy >= 0 && iy < SIZE) {
			if (c == this.coma[iy][ix]) {
				count = 1 + getCountDirect(iy, ix, dy, dx);
			}
			return count;
		}
		else {
			return 0;
		}
	}

	//コマを置く 勝負がついたら true
	public boolean putComma(char y, char x, char c) {
		int iy = (int)(y - '0');
		if (y >= 'a' && y <= 'z') iy = 10 + ((int)y - (int)'a');
		int ix = (int)(x - '0');
		if (x >= 'a' && y <= 'z') ix = 10 + ((int)x - (int)'a');
		this.coma[iy][ix] = c;
		downCount--;

		//各方向に5並んでいるか?
		int c_p = getCountDirect(iy, ix, 0, 1);
		int c_m = getCountDirect(iy, ix, 0, -1);
		int count = c_p + c_m + 1;
		//System.out.println(c_p + "+" + c_m + "=" + count);
		if (count < 5) {
			c_p = getCountDirect(iy, ix, 1, 0);
			c_m = getCountDirect(iy, ix, -1, 0);
			count = c_p + c_m + 1;
			//System.out.println(c_p + "+" + c_m + "=" + count);
		}
		if (count < 5) {
			c_p = getCountDirect(iy, ix, 1, 1);
			c_m = getCountDirect(iy, ix, -1, -1);
			count = c_p + c_m + 1;
			//System.out.println(c_p + "+" + c_m + "=" + count);
		}
		if (count < 5) {
			c_p = getCountDirect(iy, ix, 1, -1);
			c_m = getCountDirect(iy, ix, -1, 1);
			count = c_p + c_m + 1;
			//System.out.println(c_p + "+" + c_m + "=" + count);
		}
		if (count >= 5) {
			if (c == '○') m勝敗 = 0;//先手が勝ち
			if (c == '●') m勝敗 = 1;//後手が勝ち
			return true;
		}
		if (downCount <= 0) {
			m勝敗 = -1;//引き分け
			return true;
		}
		return false;
	}

	public String toString() {
		String s = " ";
		for (int col = 0; col < SIZE; col++) {
			s += x[col];
		}
		s += newLine;
		for (int row = 0; row < SIZE; row++) {
			s += x[row];
			for (int col = 0; col < SIZE; col++) {
				s += coma[row][col];
			}
			s += newLine;
		}
		return s;
	}
}

public class TcpReply implements Runnable {

	//public static String newLine = "\r\n";
	public static String newLine = System.getProperty("line.separator");
	//public static String newLine = new String(new byte[]{13,10});

	int portNumber = 49154;//ポート番号

	ServerSocket serverSock;

	byte[] buf = new byte[256];

	boolean loop = true;
	FileOutputStream logOs;//ログ用出力ストリーム

	HashMap<String, User> hashMap = new HashMap<String, User>();
	int countUser = 0;// ユーザーカウント

	public class User {
		TcpClientTread tcpClinet;

		Gomoku gomoku;
		User opponent; //対戦相手

		String s学籍番号;//key保存する時のキー

		int order = 0; //0:先手,1 後手 

		int state = 0; //0:初期表示,1:先手、後手選択, 2:ゲーム進行状態

		//		boolean bWait = false; //ターンが終わって入力待ちになったらtrue

		int userNumner; //このユーザーカウント番号 

		public User(TcpClientTread tcp) {
			tcpClinet = tcp;
			countUser++;
			userNumner = countUser;
		}

		public void do振る舞い()throws Exception {
			boolean correct = false;
			String s = "";
			String recStr = this.tcpClinet.msgRec.substring(7);
			if (this.state == 0 || this.state == 1) {
				if (this.state == 1) {
					if (recStr.equals("N")) {//先手ゲーム参加
						this.gomoku = new Gomoku();
						this.gomoku.firstPlayrt = this;
						this.order = 0;//先手
						s += this.gomoku.toString();
						s += "残り=" + this.gomoku.downCount;
						s += ":縦の位置(0〜j)と横の位置(0〜j)をスペースで区切って入力ください" + newLine;
						this.tcpClinet.msgOut = s;
						this.tcpClinet.sendMessageln();
						this.state = 2;
						correct = true;
					}
					else if (recStr.matches("[0-9]+")) {
						int numb = Integer.parseInt(recStr);
						int listCount = Gomoku.getGameListCount();
						if (numb < listCount) {//後手対戦、または観戦
							boolean b = Gomoku.addSecondPlayer(numb, this);
							if (b == true) {//対戦
								s += Gomoku.getPlayString(numb);
								s += this.gomoku.toString();
								s += "残り=" + this.gomoku.downCount;
								s += ":縦の位置(0〜j)と横の位置(0〜j)をスペースで区切って入力ください" + newLine;
								this.tcpClinet.msgOut = s;
								this.tcpClinet.sendMessageln();
								this.state = 2;
								correct = true;
							}
							else {//観戦
								s += "観戦情報です" + newLine;
								s += Gomoku.getPlayString(numb);
							}
						}
						else {
							s += "入力が正しくないです。" + newLine;
						}
					}
					else {
						s += "入力が正しくないです。" + newLine;
					}
				}
				if (correct == false) {
					s += Gomoku.getGameList();
					s += "'N'の1文字入力で先手としてゲーム参加です。後手がいないリスト番号入力で後手対戦参加です。";
					s += "他の番号は、観戦です。";
					this.tcpClinet.msgOut = s + " 選択ください。" + newLine;
					this.tcpClinet.sendMessageln();
					this.state = 1;
				}
			}
			else if (this.state == 2) {//ゲームが始まってからコマを置く処理
				String message = "";
				if (recStr.matches("[0-9a-j] [0-9a-j]")) {
					char y = recStr.charAt(0);
					char x = recStr.charAt(2);
					if (this.gomoku.isPutComma(y, x)) {
						boolean b = this.gomoku.putComma(y, x, this.order == 0 ? '○' : '●');
						s += this.gomoku.toString();
						if (b) {//-----------------------勝負がついた。
							if (this.gomoku.m勝敗 == -1) {
								s += "引き分けです。" + newLine;
								this.tcpClinet.msgOut = s + "end" + newLine;
								this.opponent.tcpClinet.msgOut = s + "end" + newLine; ;
							}
							else if (this.gomoku.m勝敗 == 0 && this.order == 0 ||
									this.gomoku.m勝敗 == 1 && this.order == 1) {
								this.tcpClinet.msgOut = s + "あなたの勝ちです。" + newLine + "end" + newLine;
								this.opponent.tcpClinet.msgOut = s + "あなたの負けです。" + newLine + "end" + newLine; ;
							}
							else {
								this.tcpClinet.msgOut = s + "あなたの負けです。" + newLine + "end" + newLine;
								this.opponent.tcpClinet.msgOut = s + "あなたの勝ちです。" + newLine + "end" + newLine; ;
							}
							this.tcpClinet.sendMessageln();
							this.opponent.tcpClinet.sendMessageln();
							this.tcpClinet.close();
							this.opponent.tcpClinet.close();
							return;
						}
						s += "相手の動作を待っています。" + newLine;
						this.tcpClinet.msgOut = s;
						this.tcpClinet.sendMessage();
						if (this.opponent != null) {//相手がいる場合、相手を更新
							String s2 = this.gomoku.toString();
							s2 += "残り=" + this.gomoku.downCount;
							s2 += ":縦の位置(0〜j)と横の位置(0〜j)をスペースで区切って入力ください" + newLine;
							this.opponent.tcpClinet.msgOut = s2;
							this.opponent.tcpClinet.sendMessageln();
						}
						else {//相手がいない場合は、リスト更新
							Gomoku.addFirstPlayer(this);
						}
						correct = true;
					}
				}
				if (correct == false) {
					s += this.gomoku.toString();
					s += "入力が正しくないようです。\r\n";
					s += "縦の位置(0〜j)と横の位置(0〜j)をスペースで区切って入力ください " + newLine;
					this.tcpClinet.msgOut = s;
					this.tcpClinet.sendMessageln();
				}
			}
		}

	}	// Userクラスの最後--------------------------

	class TcpClientTread implements Runnable {

		Socket clientSock; // クライアント用ソケット
		BufferedReader client_br;//受信用ストリーム
		OutputStream client_os;//受信用ストリーム

		String ip;//相手のIPアドレス
		int port;//相手のポート
		String s受信時間 = "";
		String hostName;//key保存する時のキー の一つ

		String msgRec;//最後に記憶されたのメッセージ
		String msgOut;//送信メッセージ用(一時)
		byte[] outBuf;//上記送信に使う一時バッファ

		User user;

		public String getMessageString() {//ログ用文字列取得用
			String s = "";
			s = this.hostName; //相手接続先ホスト名
			s += ":" + this.ip;//IPアドレス
			s += ":" + this.port;//ポート番号
			s += this.s受信時間;
			return s;
		}

		public void run() {
			try {
				String logStr = "";

				this.s受信時間 = getDateTimeString(false);//接続時の日時を取得します。

				//クライアントと通信するためのストリームを作成
				InputStream client_is = clientSock.getInputStream();
				this.client_br = new BufferedReader(new InputStreamReader(client_is));
				this.client_os = clientSock.getOutputStream();

				InetAddress recInet = clientSock.getInetAddress();//接続相手のIP
				this.hostName = recInet.getCanonicalHostName();
				this.ip = recInet.getHostAddress();
				this.port = clientSock.getPort();
				logStr = getMessageString();
				log_out(logStr + ":connected");//ログ出力

				//クライアントへ接続したことを知らせる最初の送信(これは改行を含めない)
				this.msgOut = this.hostName + "さんへ、はい、接続しました。";
				sendMessageln();

				for (; ; ) {
					this.msgRec = this.client_br.readLine();//1行の受信
					if (this.msgRec == null) break;
					this.s受信時間 = getDateTimeString(false);//受信時の日時を取得します。

					logStr = getMessageString();
					if (this.msgRec == null) {
						logStr = getMessageString();
						log_out(logStr + "受信ストリーム終了");//ログ出力
						this.close();
						return;
					}
					log_out(logStr + "『" + this.msgRec + "』受信");//ログ出力

					if (s_より始まる6文字が番号有効でないか()) continue;//有効でない
					String s学籍番号 = this.msgRec.substring(0, 7);

					//自身をhashMapに登録する。キーはthis.hostName
					String key = this.hostName+ this.port;//デバックではポートを追加

					this.user = TcpReply.this.hashMap.get(key);
					if (this.user == null) {
						this.user = new User(this);
						this.user.s学籍番号 = s学籍番号;
						TcpReply.this.hashMap.put(key, this.user);//ホスト名で登録
					}
					else {
						if (!s学籍番号.equals(user.s学籍番号)) {
							this.msgOut = s学籍番号 + "さんへ、このマシンは、" + user.s学籍番号 + "の方が使っています。";
							this.msgOut += "異なるマシンで送信ください" + newLine;
							sendMessageln();
							logStr = getMessageString();
							log_out(logStr + ":" + s学籍番号 + "がこのマシンを使用");//ログ出力
							break;//違反ユーザーにより終了
						}
						if (this.user.tcpClinet != this) {
							this.user = new User(this);
							this.user.s学籍番号 = s学籍番号;
						}
					}
					//ゲームの進行処理
					this.user.do振る舞い();
				};

				client_is.close();//閉じる
				client_os.close();
				clientSock.close();
			}
			catch (Exception err) {
				if (this.user != null && this.user.opponent != null) {
					User u = this.user;
					if (this == this.user.tcpClinet) {//ここで、エラー
						u = this.user.opponent;
					}
					this.user.gomoku.m勝敗 = u.order;
					if (this.user.gomoku.m勝敗 != -2) {
						try {
							u.tcpClinet.msgOut = "相手が放棄したので、あなたの勝ちです。" + newLine + "end" + newLine;
							u.tcpClinet.sendMessage();
							u.tcpClinet.close();
						}
						catch (Exception err2) {
							System.out.println("勝者close失敗:" + u.order);
							//err2.printStackTrace();
						}
					}
				}
				System.out.println("失敗:" + err.getMessage());
				//err.printStackTrace();
			}
		}

		public void close() throws Exception {
			client_br.close();//閉じる
			client_os.close();
			clientSock.close();
		}

		boolean s_より始まる6文字が番号有効でないか() throws Exception {//先頭が有効でない場合は、メッセージ送信まで行う。
			boolean rtnvl = false;
			//メッセージから学籍番号の部分を記憶し、適合しなければそのメッセージを送信
			if (this.msgRec.length() < 7) {
				this.msgOut = this.hostName + "さんへ! メッセージを取得しましたが、受信文字数が足りません。学籍番号がメッセージ先頭にありますか?" + newLine;
				sendMessageln();
				return true;
			}
			String msg = this.msgRec.substring(0, 7);
			if (!msg.matches("s[0-9]{6,6}.*")) {
				this.msgOut = this.hostName + "さんへ! メッセージを取得しましたが、sから始まる学籍番号がメッセージ先頭にありません?" + newLine;
				sendMessageln();
				return true;
			}
			return rtnvl;
		}

		//相手に送信します。(メッセージ最後の区切り文字を含む)
		void sendMessageln()throws Exception {
			this.client_os.write(this.msgOut.getBytes());
			this.client_os.write(newLine.getBytes());//メーッセージ最後の区切る
			this.client_os.flush();
		}
		//相手に送信しますが、区切り記号なしで送ります。
		void sendMessage()throws Exception {
			this.client_os.write(this.msgOut.getBytes());
			this.client_os.flush();
		}

	}// TcpClientTreadクラス最後--------------------

	public void run() {
		//サーバー用ソケットをポート49154で作成
		try {
			String logFileName = "tcplog" + getDateTimeString(true) + ".log";
			this.logOs = new FileOutputStream(logFileName);

			this.serverSock = new ServerSocket(this.portNumber);
			System.out.print("待機状態=>" + InetAddress.getLocalHost());
			System.out.println(":" + serverSock.getLocalPort());
		}
		catch (Exception e) {
			e.printStackTrace();
			System.exit(1);//  ----------------終了
		}

		while (this.loop) {
			try {
				TcpClientTread clientThread = new TcpClientTread();
				Thread thread = new Thread(clientThread);

				//クライアントからの接続を待ち、接続してきたら、
				//	そのクライアントと通信するソケットを取得する。
				clientThread.clientSock = serverSock.accept();
				//System.out.println("通信に使うポート:" + clientThread.clientSock.getPort());
				//System.out.println(clientThread.clientSock.toString() + ":connected");
				thread.start();

			}
			catch (Exception e) {
				e.printStackTrace();
				loop = false;
			}
		}
		//		recSocket.close();
	}

	public void log_out(String data) throws Exception {
		data += System.getProperty("line.separator");
		this.logOs.write(data.getBytes());
		this.logOs.flush();
		System.out.print(data);//ログ用文字列表示
	}


	//現在の日時と時間を取得
	// 引数が、falseではれば「2009/10/24 10:52:58」の文字列で、
	// 引数が trueであれば、「2009_10_24_10_52_58」の文字列になる。
	static String getDateTimeString(boolean file) {
		Calendar calendar = Calendar.getInstance();
		Date date = calendar.getTime();
		java.text.DateFormat dataFormat =
			java.text.DateFormat.getDateTimeInstance();
		String time = dataFormat.format(date);
		if (file) {
			time = time.replaceAll("[/ :]", "_");
		}
		return time;
	}

	public static void main(String[] args)throws Exception {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

		TcpReply tcpReply = new TcpReply();

		Thread thread = new Thread(tcpReply);
		thread.start();

		System.out.println("endで終了");
		br.readLine();
	}
}








古いバージョンのJavaでも動作するソースも以下に紹介します。

001
002
003
004
005
006
007
008
009
010 011
012
013
014
015
016
017
018
019
020 021
022
023
024
025
026
027
028
029
030 031
032
033
034
035
036
037
038
039
040 041
042
043
044
045
046
047
048
049
050 051
052
053
054
055
056
057
058
059
060 061
062
063
064
065
066
067
068
069
070 071
072
073
074
075
076
077
078
079
080 081
082
083
084
085
086
087
088
089
090 091
092
093
094
095
096
097
098
099
100 101
102
103
104
105
106
107
108
109
110 111
112
113
114
115
116
117
118
119
120 121
122
123
124
125
126
127
128
129
130 131
132
133
134
135
136
137
138
139
140 141
142
143
144
145
146
147
148
149
150 151
152
153
154
155
156
157
158
159
160 161
162
163
164
165
166
167
168
169
170 171
172
173
174
175
176
177
178
179
180 181
182
183
184
185
186
187
188
189
190 191
192
193
194
195
196
197
198
199
200 201
202
203
204
205
206
207
208
209
210 211
212
213
214
215
216
217
218
219
220 221
222
223
224
225
226
227
228
229
230 231
232
233
234
235
236
237
238
239
240 241
242
243
244
245
246
247
248
249
250 251
252
253
254
255
256
257
258
259
260 261
262
263
264
265
266
267
268
269
270 271
272
273
274
275
276
277
278
279
280 281
282
283
284
285
286
287
288
289
290 291
292
293
294
295
296
297
298
299
300 301
302
303
304
305
306
307
308
309
310 311
312
313
314
315
316
317
318
319
320 321
322
323
324
325
326
327
328
329
330 331
332
333
334
335
336
337
338
339
340 341
342
343
344
345
346
347
348
349
350 351
352
353
354
355
356
357
358
359
360 361
362
363
364
365
366
367
368
369
370 371
372
373
374
375
376
377
378
379
380 381
382
383
384
385
386
387
388
389
390 391
392
393
394
395
396
397
398
399
400 401
402
403
404
405
406
407
408
409
410 411
412
413
414
415
416
417
418
419
420 421
422
423
424
425
426
427
428
429
430 431
432
433
434
435
436
437
438
439
440 441
442
443
444
445
446
447
448
449
450 451
452
453
454
455
456
457
458
459
460 461
462
463
464
465
466
467
468
469
470 471
472
473
474
475
476
477
478
479
480 481
482
483
484
485
486
487
488
489
490 491
492
493
494
495
496
497
498
499
500 501
502
503
504
505
506
507
508
509
510 511
512
513
514
515
516
517
518
519
520 521
522
523
524
525
526
527
528
529
530 531
532
533
534
535
536
537
538
539
540 541
542
543
544
545
546
547
548
549
550 551
552
553
554
555
556
557
558
559
560 561
562
563
564
565
566
567
568
569
570 571
572
573
574
575
576
577
578
579
580 581
582
583
584
585
586
587
588
589
590 591
592
593
594
595
596
597
598
//linux用
/*
接続してきたら、「はい、接続しました」のメッセージを送ります。
次で受信した先頭文字列がsから始まる6桁の数字であれば、
「状態を表示します」
「1.『sから始まる6桁』]
「2.『sから始まる6桁』] と列挙してプレイヤーを選択用リストを列挙する。
そして、「五目並べです。先手ならば『f』、後手ならば『s』を入力ください。」の表示をします。
ただし、リストでだれもいなければ、はじめから
「1・・・・・・・・・・・・・・・・・・・・」の20のマトリックを表示させ、「配置 するコマの縦位置と、横位置を2つ入力させる』

20のマトリックス表示は、先手が○後手が×で表示される。
「  1 2 3 4 5 6 7 8 9 a b c d e f g h i j k」
「 1     ○  ●            」

 */

import java.io.*;
import java.net.*;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Date;
import java.util.ArrayList;
//import java.util.regex.*;//Pattern, Matcher用

class Gomoku {//5目並べの1ゲームを管理するクラス
        public static final int SIZE = 20;
        public static String newLine = System.getProperty("line.separator");
        static final String x[] = { " 0", " 1", " 2", " 3", " 4", " 5", " 6", " 7", " 8", " 9", " a", " b", " c", " d", " e", " f", " g", " h", " i", " j", " k" };
        public char[][] coma = new char[SIZE][];

        public int downCount = 0;//残りコマ数

        public static ArrayList listGame = new ArrayList();//<Gomoku>用オブジェ クト管理用

        TcpReply0.User firstPlayrt;//先手プレイヤー
        TcpReply0.User secondPlayrt;//後手プレイヤー

        public int m勝敗 = -2; // -1なら引き分け、0なら先手、1なら後手の勝ち

        public Gomoku() {//コンストラクタ
                for (int row = 0; row < this.coma.length; row++) {
                        coma[row] = new char[SIZE];

                        for (int col = 0; col < coma[row].length; col++) {
                                coma[row][col] = '・';
                                downCount++;
                        }
                }
                downCount = 100;//この数内で判定
        }

        synchronized public static int getGameListCount() {
                int count = listGame.size();
                return count;
        }

        //先手プレイヤーと後手プレイヤーのリスト
        synchronized public static String getGameList() {
                int count = listGame.size();
                String rtnval = "";
                if (count == 0) return "ゲーム参加者は現在だれもいません。" + newLine;
                for (int i = 0; i < listGame.size(); i++) {
                        Gomoku g = (Gomoku)listGame.get(i);
                        if (g == null) continue;
                        rtnval += i + "番 先手:";
                        rtnval += ((Gomoku)listGame.get(i)).firstPlayrt.s学籍番号;
                        if (g.secondPlayrt == null) {
                                rtnval += "---------対戦者を待っています。" + newLine;
                        }
                        else {
                                rtnval += "<-->後手" + g.secondPlayrt.s学籍番号;
                                if (g.m勝敗 == -2) rtnval += "  で対戦中" + newLine;
                                if (g.m勝敗 == -1) rtnval += "  で引き分け" + newLine;
                                if (g.m勝敗 == 0) rtnval += "  で先手が勝ち" + newLine;
                                if (g.m勝敗 == 1) rtnval += "  で後手が勝ち" + newLine;
                        }
                }
                return rtnval;
        }

        //先手プレイヤーの登録
        synchronized public static void addFirstPlayer(TcpReply0.User user) {
                listGame.add(user.gomoku);
                user.gomoku.firstPlayrt = user;
        }

        //後手プレイヤーの登録
        synchronized public static boolean addSecondPlayer(int idx, TcpReply0.User user) {
                Gomoku gomoku = (Gomoku)listGame.get(idx);
                if (gomoku == null) return false;
                if (gomoku.secondPlayrt != null) return false;
                gomoku.secondPlayrt = user;
                gomoku.firstPlayrt.opponent = user;
                user.opponent = gomoku.firstPlayrt;
                user.gomoku = gomoku;
                user.order = 1;//後手
                return true;
        }

        //観戦情報
        synchronized public static String getPlayString(int idx) {
                Gomoku gomoku = (Gomoku)listGame.get(idx);
                if (gomoku == null) return "存在しないゲームです。";
                return gomoku.toString();
        }

        //コマを置けるか?
        public boolean isPutComma(char y, char x) {
                boolean rtnval = false;
                int iy = (int)(y - '0');
                if (y >= 'a' && y <= 'z') iy = 10 + ((int)y - (int)'a');
                int ix = (int)(x - '0');
                if (x >= 'a' && y <= 'z') ix = 10 + ((int)x - (int)'a');
                //System.out.println(y + ", " + x + ":" + iy + ", " + ix);
                if (this.coma[iy][ix] == '・') rtnval = true;
                return rtnval;
        }

        //再帰的に指定の方向に並ぶ同一コマ数を取得する.
        public int getCountDirect(int iy, int ix, int dy, int dx) {
                int count = 0;
                char c = this.coma[iy][ix];
                ix += dx;
                iy += dy;
                if (ix >= 0 && ix < SIZE && iy >= 0 && iy < SIZE) {
                        if (c == this.coma[iy][ix]) {
                                count = 1 + getCountDirect(iy, ix, dy, dx);
                        }
                        return count;
                }
                else {
                        return 0;
                }
        }

        //コマを置く 勝負がついたら true
        public boolean putComma(char y, char x, char c) {
                int iy = (int)(y - '0');
                if (y >= 'a' && y <= 'z') iy = 10 + ((int)y - (int)'a');
                int ix = (int)(x - '0');
                if (x >= 'a' && y <= 'z') ix = 10 + ((int)x - (int)'a');
                this.coma[iy][ix] = c;
                downCount--;

                //各方向に5並んでいるか?
                int c_p = getCountDirect(iy, ix, 0, 1);
                int c_m = getCountDirect(iy, ix, 0, -1);
                int count = c_p + c_m + 1;
                //System.out.println(c_p + "+" + c_m + "=" + count);
                if (count < 5) {
                        c_p = getCountDirect(iy, ix, 1, 0);
                        c_m = getCountDirect(iy, ix, -1, 0);
                        count = c_p + c_m + 1;
                        //System.out.println(c_p + "+" + c_m + "=" + count);
                }
                if (count < 5) {
                        c_p = getCountDirect(iy, ix, 1, 1);
                        c_m = getCountDirect(iy, ix, -1, -1);
                        count = c_p + c_m + 1;
                        //System.out.println(c_p + "+" + c_m + "=" + count);
                }
                if (count < 5) {
                        c_p = getCountDirect(iy, ix, 1, -1);
                        c_m = getCountDirect(iy, ix, -1, 1);
                        count = c_p + c_m + 1;
                        //System.out.println(c_p + "+" + c_m + "=" + count);
                }
                if (count >= 5) {
                        if (c == '○') m勝敗 = 0;//先手が勝ち
                        if (c == '●') m勝敗 = 1;//後手が勝ち
                        return true;
                }
                if (downCount <= 0) {
                        m勝敗 = -1;//引き分け
                        return true;
                }
                return false;
        }

        public String toString() {
                String s = " ";
                for (int col = 0; col < SIZE; col++) {
                        s += x[col];
                }
                s += newLine;
                for (int row = 0; row < SIZE; row++) {
                        s += x[row];
                        for (int col = 0; col < SIZE; col++) {
                                s += coma[row][col];
                        }
                        s += newLine;
                }
                return s;
        }
}

public class TcpReply0 implements Runnable {

        static String IPアドレス;
        static String ホスト名;
        //public static String newLine = "\r\n";
        public static String newLine = System.getProperty("line.separator");
        //public static String newLine = new String(new byte[]{13,10});

        public static int portNumber = 49154;//ポート番号
        //public static int portNumber = 59154;//ポート番号

        ServerSocket serverSock;

        byte[] buf = new byte[256];

        boolean loop = true;
        FileOutputStream logOs;//ログ用出力ストリーム

        HashMap hashMap = new HashMap();//<String, User>
        int countUser = 0;// ユーザーカウント

        public class User {
                TcpClientTread tcpClinet;

                Gomoku gomoku;
                User opponent; //対戦相手

                String s学籍番号;//key保存する時のキー

                int order = 0; //0:先手,1 後手

                int state = 0; //0:初期表示,1:先手、後手選択, 2:ゲーム進行状態

                //              boolean bWait = false; //ターンが終わって入力待 ちになったらtrue

                int userNumner; //このユーザーカウント番号

                public User(TcpClientTread tcp) {
                        tcpClinet = tcp;
                        countUser++;
                        userNumner = countUser;
                }

                public void do振る舞い()throws Exception {
                        boolean correct = false;
                        String s = "";
                        String recStr = this.tcpClinet.msgRec.substring(7);
                        if (this.state == 0 || this.state == 1) {
                                if (this.state == 1) {
                                        if (recStr.equals("N")) {//先手ゲーム参 加
                                                this.gomoku = new Gomoku();
                                                this.gomoku.firstPlayrt = this;
                                                this.order = 0;//先手
                                                s += this.gomoku.toString();
                                                s += "残り=" + this.gomoku.downCount;
                                                s += ":縦の位置(0-j)と横の位置(0-j)をスペースで区切って入力ください" + newLine;
                                                this.tcpClinet.msgOut = s;
                                                this.tcpClinet.sendMessageln();
                                                this.state = 2;
                                                correct = true;
                                        }
                                        else if (recStr.matches("[0-9]+")) {
                                                int numb = Integer.parseInt(recStr);
                                                int listCount = Gomoku.getGameListCount();
                                                if (numb < listCount) {//後手対 戦、または観戦
                                                        boolean b = Gomoku.addSecondPlayer(numb, this);
                                                        if (b == true) {//対戦
                                                                s += Gomoku.getPlayString(numb);
                                                                s += this.gomoku.toString();
                                                                s += "残り=" + this.gomoku.downCount;
                                                                s += ":縦の位置(0-j)と横の位置(0-j)をスペースで区切って入力ください" + newLine;
                                                                this.tcpClinet.msgOut = s;
                                                                this.tcpClinet.sendMessageln();
                                                                this.state = 2;
                                                                correct = true;
                                                        }
                                                        else {//観戦
                                                                s += "観戦情報です" + newLine;
                                                                s += Gomoku.getPlayString(numb);
                                                        }
                                                }
                                                else {
                                                        s += "入力が正しくないです。" + newLine;
                                                }
                                        }
                                        else {
                                                s += "入力が正しくないです。" + newLine;
                                        }
                                }
                                if (correct == false) {
                                        s += Gomoku.getGameList();
                                        s += "'N'で先手によるゲーム参加です。後 手がいないリスト番号入力で後手対戦参加です。";
                                        s += "他の番号は、観戦です。";
                                        this.tcpClinet.msgOut = s + " 選択ください。" + newLine;
                                        this.tcpClinet.sendMessageln();
                                        this.state = 1;
                                }
                        }
                        else if (this.state == 2) {//ゲームが始まってからコマを 置く処理
                                //String message = "";
                                if (recStr.matches("[0-9a-j] [0-9a-j]")) {
                                        char y = recStr.charAt(0);
                                        char x = recStr.charAt(2);
                                        if (this.gomoku.isPutComma(y, x)) {
                                                boolean b = this.gomoku.putComma(y, x, this.order == 0 ? '○' : '●');
                                                s += this.gomoku.toString();
                                                if (b) {//-----------------------勝負がついた。
                                                        if (this.gomoku.m勝敗 == -1) {
                                                                s += "引き分けです。" + newLine;
                                                                this.tcpClinet.msgOut = s + "end" + newLine;
                                                                this.opponent.tcpClinet.msgOut = s + "end" + newLine; ;
                                                        }
                                                        else if (this.gomoku.m勝敗 == 0 && this.order == 0 ||
                                                                        this.gomoku.m勝敗 == 1 && this.order == 1) {
                                                                this.tcpClinet.msgOut = s + "あなたの勝ちです。" + newLine + "end" + newLine;
                                                                this.opponent.tcpClinet.msgOut = s + "あなたの負けです。" + newLine + "end" + newLine; ;
                                                        }
                                                        else {
                                                                this.tcpClinet.msgOut = s + "あなたの負けです。" + newLine + "end" + newLine;
                                                                this.opponent.tcpClinet.msgOut = s + "あなたの勝ちです。" + newLine + "end" + newLine; ;
                                                        }
                                                        this.tcpClinet.sendMessageln();
                                                        this.opponent.tcpClinet.sendMessageln();
                                                        this.tcpClinet.close();
                                                        this.opponent.tcpClinet.close();
                                                        return;
                                                }
                                                s += "相手の動作を待っています。" + newLine;
                                                this.tcpClinet.msgOut = s;
                                                this.tcpClinet.sendMessage();
                                                if (this.opponent != null) {//相手がいる場合、相手を更新
                                                        String s2 = this.gomoku.toString();
                                                        s2 += "残り=" + this.gomoku.downCount;
                                                        s2 += ":縦の位置(0-j)と横の位置(0-j)をスペースで区切って入力ください" + newLine;
                                                        this.opponent.tcpClinet.msgOut = s2;
                                                        this.opponent.tcpClinet.sendMessageln();
                                                }
                                                else {//相手がいない場合は、リスト更新
                                                        Gomoku.addFirstPlayer(this);
                                                }
                                                correct = true;
                                        }
                                }
                                if (correct == false) {
                                        s += this.gomoku.toString();
                                        s += "入力が正しくないようです。\r\n";
                                        s += "縦の位置(0-j)と横の位置(0-j)をスペースで区切って入力ください " + newLine;
                                        this.tcpClinet.msgOut = s;
                                        this.tcpClinet.sendMessageln();
                                }
                        }
                }

        }       // Userクラスの最後--------------------------

        class TcpClientTread implements Runnable {

                Socket clientSock; // クライアント用ソケット
                BufferedReader client_br;//受信用ストリーム
                OutputStream client_os;//受信用ストリーム

                String ip;//相手のIPアドレス
                int port;//相手のポート
                String s受信時間 = "";
                String hostName;//key保存する時のキー の一つ

                String msgRec;//最後に記憶されたのメッセージ
                String msgOut;//送信メッセージ用(一時)
                byte[] outBuf;//上記送信に使う一時バッファ

                User user;

                public String getMessageString() {//ログ用文字列取得用
                        String s = "";
                        s = this.hostName; //相手接続先ホスト名
                        s += ":" + this.ip;//IPアドレス
                        s += ":" + this.port;//ポート番号
                        s += this.s受信時間;
                        return s;
                }

                public void run() {
                        try {
                                String logStr = "";

                                this.s受信時間 = getDateTimeString(false);//接続時の日時を取得します。

                                //クライアントと通信するためのストリームを作成
                                InputStream client_is = clientSock.getInputStream();
                                this.client_br = new BufferedReader(new InputStreamReader(client_is));
                                this.client_os = clientSock.getOutputStream();

                                InetAddress recInet = clientSock.getInetAddress();//接続相手のIP
                                this.hostName = recInet.getCanonicalHostName();
                                this.ip = recInet.getHostAddress();
                                this.port = clientSock.getPort();
                                logStr = getMessageString();
                                log_out(logStr + ":connected");//ログ出力

                                //クライアントへ接続したことを知らせる最初の送信(これは改行を含めない)
                                this.msgOut = this.hostName + "さんへ、はい、接 続しました。";
                                sendMessageln();

                                for (; ; ) {
                                        this.msgRec = this.client_br.readLine();//1行の受信
                                        //byte ta[] = this.msgRec.getBytes();
                                        //this.msgRec = new String(ta,"MS983");

                                        if (this.msgRec == null) break;
                                        this.s受信時間 = getDateTimeString(false);//受信時の日時を取得します。

                                        logStr = getMessageString();
                                        if (this.msgRec == null) {
                                                logStr = getMessageString();
                                                log_out(logStr + "受信ストリーム終了");//ログ出力
                                                this.close();
                                                return;
                                        }
                                        log_out(logStr + "『" + this.msgRec + " 』受信");//ログ出力

                                        if (s_より始まる6文字が番号有効でないか()) continue;//有効でない
                                        String s学籍番号 = this.msgRec.substring(0, 7);

                                        //自身をhashMapに登録する。キーはthis.hostName
                                        String key = this.hostName+ this.port;//デバックではポートを追加

                                        this.user = (User)TcpReply0.this.hashMap.get(key);
                                        if (this.user == null) {
                                                this.user = new User(this);
                                                this.user.s学籍番号 = s学籍番号;
                                                TcpReply0.this.hashMap.put(key, this.user);//ホスト名で登録
                                        }
                                        else {
                                                if (!s学籍番号.equals(user.s学籍番号)) {
                                                        this.msgOut = s学籍番号 + "さんへ、このマシンは、" + user.s学籍番号 + "の方が使っています。";
                                                        this.msgOut += "異なるマシンで送信ください" + newLine;
                                                        sendMessageln();
                                                        logStr = getMessageString();
                                                        log_out(logStr + ":" + s学籍番号 + "がこのマシンを使用");//ログ出力
                                                        break;//違反ユーザーにより終了
                                                }
                                                if (this.user.tcpClinet != this) {
                                                        this.user = new User(this);
                                                        this.user.s学籍番号 = s 学籍番号;
                                                }
                                        }
                                        //ゲームの進行処理
                                        this.user.do振る舞い();
                                };

                                client_is.close();//閉じる
                                client_os.close();
                                clientSock.close();
                        }
                        catch (Exception err) {
                                if (this.user != null && this.user.opponent != null) {
                                        User u = this.user;
                                        if (this == this.user.tcpClinet) {//ここで、エラー
                                                u = this.user.opponent;
                                        }
                                        this.user.gomoku.m勝敗 = u.order;
                                        if (this.user.gomoku.m勝敗 != -2) {
                                                try {
                                                        u.tcpClinet.msgOut = "相手が放棄したので、あなたの勝ちです。" + newLine + "end" + newLine;
                                                        u.tcpClinet.sendMessage();
                                                        u.tcpClinet.close();
                                                }
                                                catch (Exception err2) {
                                                        System.out.println("勝者close失敗:" + u.order);
                                                        //err2.printStackTrace();
                                                }
                                        }
                                }
                                System.out.println("失敗:" + err.getMessage());
                                //err.printStackTrace();
                        }
                }

                public void close() throws Exception {
                        client_br.close();//閉じる
                        client_os.close();
                        clientSock.close();
                }

                boolean s_より始まる6文字が番号有効でないか() throws Exception {//先頭が有効でない場合は、メッセージ送信まで行う。
                        boolean rtnvl = false;
                        //メッセージから学籍番号の部分を記憶し、適合しなければそのメッセージを送信
                        if (this.msgRec.length() < 7) {
                                this.msgOut = this.hostName + "さんへ! メッセ ージを取得しましたが、受信文字数が足りません。学籍番号がメッセージ先頭にありますか?" + newLine;
                                sendMessageln();
                                return true;
                        }
                        String msg = this.msgRec.substring(0, 7);
                        if (!msg.matches("s[0-9]{6,6}.*")) {
                                this.msgOut = this.hostName + "さんへ! メッセ ージを取得しましたが、sから始まる学籍番号がメッセージ先頭にありません?" + newLine;
                                sendMessageln();
                                return true;
                        }
                        return rtnvl;
                }

                //相手に送信します。(メッセージ最後の区切り文字を含む)
                void sendMessageln()throws Exception {
                        this.client_os.write(this.msgOut.getBytes("MS932"));
                        this.client_os.write(newLine.getBytes("MS932"));//メーッセージ最後の区切る
                        this.client_os.flush();
                }
                //相手に送信しますが、区切り記号なしで送ります。
                void sendMessage()throws Exception {
                        this.client_os.write(this.msgOut.getBytes("MS932"));
                        this.client_os.flush();
                }

        }// TcpClientTreadクラス最後--------------------

        public void run() {
                //サーバー用ソケットをportNumberのポートで作成
                try {
                        String logFileName = "tcplog" + getDateTimeString(true) + ".log";
                        this.logOs = new FileOutputStream(logFileName);

                        this.serverSock = new ServerSocket(TcpReply0.portNumber);
                        System.out.print("待機状態=>" + InetAddress.getLocalHost());
                        System.out.println(":" + serverSock.getLocalPort());
                }
                catch (Exception e) {
                        e.printStackTrace();
                        System.exit(1);//  ----------------終了
                }

                while (this.loop) {
                        try {
                                TcpClientTread clientThread = new TcpClientTread();
                                Thread thread = new Thread(clientThread);

                                //クライアントからの接続を待ち、接続してきたら、
                                //      そのクライアントと通信するソケットを取得する。
                                clientThread.clientSock = serverSock.accept();
                                //System.out.println("通信に使うポート:" + clientThread.clientSock.getPort());
                                //System.out.println(clientThread.clientSock.toString() + ":connected");
                                thread.start();

                        }
                        catch (Exception e) {
                                e.printStackTrace();
                                loop = false;
                        }
                }
                //              recSocket.close();
        }

        public void log_out(String data) throws Exception {
                data += System.getProperty("line.separator");
                this.logOs.write(data.getBytes());
                this.logOs.flush();
                System.out.print(data);//ログ用文字列表示
        }


        //現在の日時と時間を取得
        // 引数が、falseではれば「2009/10/24 10:52:58」の文字列で、
        // 引数が trueであれば、「2009_10_24_10_52_58」の文字列になる。
        static String getDateTimeString(boolean file) {
                Calendar calendar = Calendar.getInstance();
                Date date = calendar.getTime();
                //java.text.DateFormat dataFormat =
                //      java.text.DateFormat.getDateTimeInstance();
                //String time = dataFormat.format(date);
                String time = date.toString();
                if (file) {
                        //time = time.replaceAll("[/ :]", "_");
                        time = time.replaceAll("[?/ :]","_");
                }
                return time;
        }

        public static void main(String[] args)throws Exception {
                //ローカルのInetAddress(IPアドレス)を取得
                InetAddress inet  = InetAddress.getLocalHost();

                System.out.println("IPアドレス:" + (IPアドレス = inet.getHostAddress()));
                ホスト名 = inet.getHostName();
                System.out.println("ホスト名「" +ホスト名+"」で、"+TcpReply0.portNumber+"ポートで待機");

                TcpReply0 tcpReply = new TcpReply0();

                Thread thread = new Thread(tcpReply);
                thread.start();
                System.out.println("endで終了");

                BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
                for(;;){
                        String s = br.readLine();
                        if(s.equals("end")) {
                                tcpReply.loop = false;
                                System.exit(0);
                        }
                }
        }

}