/*
* Symphony - A modern community (forum/SNS/blog) platform written in Java.
* Copyright (C) 2012-2017, b3log.org & hacpai.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.b3log.symphony.processor.channel;
import org.b3log.latke.Keys;
import org.b3log.latke.ioc.LatkeBeanManager;
import org.b3log.latke.ioc.Lifecycle;
import org.b3log.latke.ioc.inject.Inject;
import org.b3log.latke.logging.Logger;
import org.b3log.latke.model.User;
import org.b3log.latke.service.ServiceException;
import org.b3log.symphony.model.Pointtransfer;
import org.b3log.symphony.service.ActivityMgmtService;
import org.b3log.symphony.service.UserQueryService;
import org.json.JSONException;
import org.json.JSONObject;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
;
/**
* Gobang game channel.
* <p>
* 状态值约定(为取值方便不做enum或者常量值了,当然日后或许重构)
* 1:聊天,2:下子,3:创建游戏,等待加入,4:加入游戏,游戏开始,5:断线重连,恢复棋盘,6:系统通知,7:请求和棋
* </p>
*
* @author <a href="http://zephyr.b3log.org">Zephyr</a>
* @author <a href="http://88250.b3log.org">Liang Ding</a>
* @version 1.0.0.3, Apr 6, 2017
* @since 2.1.0
*/
@ServerEndpoint(value = "/gobang-game-channel", configurator = Channels.WebSocketConfigurator.class)
public class GobangChannel {
/**
* Session set.
*/
public static final Map<String, Session> SESSIONS = new ConcurrentHashMap<String, Session>();
/**
* 正在进行中的棋局.
* String参数代表开局者(选手1)的userId
* ChessGame参数代表棋局
*/
public static final Map<String, ChessGame> chessPlaying = new ConcurrentHashMap<String, ChessGame>();
/**
* 对手,与正在进行的棋局Map配套使用.
* 第一个String代表player1,
* 第二个String代表player2
*/
public static final Map<String, String> antiPlayer = new ConcurrentHashMap<String, String>();
/**
* 等待的棋局队列.
*/
public static final Queue<ChessGame> chessRandomWait = new ConcurrentLinkedQueue<ChessGame>();
/**
* Logger.
*/
private static final Logger LOGGER = Logger.getLogger(GobangChannel.class);
/**
* Activity management service.
*/
@Inject
private ActivityMgmtService activityMgmtService;
// 等待指定用户的棋局(暂不实现)
/**
* Called when the socket connection with the browser is established.
*
* @param session session
*/
@OnOpen
public void onConnect(final Session session) {
final JSONObject user = (JSONObject) Channels.getHttpSessionAttribute(session, User.USER);
if (null == user) {
return;
}
final String userId = user.optString(Keys.OBJECT_ID);
final String userName = user.optString(User.USER_NAME);
boolean playing = false;
LOGGER.debug("new connection from " + userName);
if(SESSIONS.containsKey(userId)){
JSONObject sendText = new JSONObject();
sendText.put("type", 6);
sendText.put("message", "【系统】:您已在匹配队列中,请勿开始多个游戏,如需打开新的窗口,请先关闭原窗口再开始");
session.getAsyncRemote().sendText(sendText.toString());
return;
}else{
SESSIONS.put(userId, session);
}
for (String temp : chessPlaying.keySet()) {
ChessGame chessGame = chessPlaying.get(temp);
if (userId.equals(chessGame.getPlayer1())) { //玩家1返回战局
recoverGame(userId, userName, chessGame.getPlayer2(), chessGame);
chessGame.setPlayState1(true);
playing = true;
} else if (userId.equals(chessGame.getPlayer2())) { //玩家2返回战局
recoverGame(userId, userName, chessGame.getPlayer1(), chessGame);
chessGame.setPlayState2(true);
playing = true;
}
}
if (playing) {
return;
} else {
ChessGame chessGame = null;
JSONObject sendText = new JSONObject();
do {
chessGame = chessRandomWait.poll();
} while (chessRandomWait.size() > 0 && SESSIONS.get(chessGame.getPlayer1()) == null);
if (chessGame == null) {
chessGame = new ChessGame(userId, userName);
chessRandomWait.add(chessGame);
sendText.put("type", 3);
sendText.put("playerName", userName);
sendText.put("message", "【系统】:请等待另一名玩家加入游戏");
session.getAsyncRemote().sendText(sendText.toString());
} else if (userId.equals(chessGame.getPlayer1())) { //仍然在匹配队列中
chessRandomWait.add(chessGame);//重新入队
sendText.put("type", 3);
sendText.put("playerName", userName);
sendText.put("message", "【系统】:请等待另一名玩家加入游戏");
session.getAsyncRemote().sendText(sendText.toString());
} else {
final LatkeBeanManager beanManager = Lifecycle.getBeanManager();
chessGame.setPlayer2(userId);
chessGame.setName2(userName);
chessGame.setPlayState2(true);
chessGame.setStep(1);
chessPlaying.put(chessGame.getPlayer1(), chessGame);
antiPlayer.put(chessGame.getPlayer1(), chessGame.getPlayer2());
final ActivityMgmtService activityMgmtService = beanManager.getReference(ActivityMgmtService.class);
sendText.put("type", 4);
//针对开局玩家的消息
sendText.put("message", "【系统】:玩家 [" + userName + "] 已加入,游戏开始,请落子");
sendText.put("player", chessGame.getPlayer1());
SESSIONS.get(chessGame.getPlayer1()).getAsyncRemote().sendText(sendText.toString());
//针对参与玩家的消息
sendText.put("message", "游戏开始~!您正在与 [" + chessGame.getName1() + "] 对战");
sendText.put("player", chessGame.getPlayer2());
session.getAsyncRemote().sendText(sendText.toString());
JSONObject r1 = activityMgmtService.startGobang(chessGame.getPlayer1());
JSONObject r2 = activityMgmtService.startGobang(chessGame.getPlayer2());
}
}
}
/**
* Called when the connection closed.
*
* @param session session
* @param closeReason close reason
*/
@OnClose
public void onClose(final Session session, final CloseReason closeReason) {
removeSession(session);
}
/**
* Called when a message received from the browser.
*
* @param message message
*/
@OnMessage
public void onMessage(final String message) throws JSONException {
JSONObject jsonObject = new JSONObject(message);
final String player = jsonObject.optString("player");
final String anti = getAntiPlayer(player);
JSONObject sendText = new JSONObject();
final LatkeBeanManager beanManager = Lifecycle.getBeanManager();
switch (jsonObject.optInt("type")) {
case 1: //聊天
LOGGER.debug(jsonObject.optString("message"));
final UserQueryService userQueryService = beanManager.getReference(UserQueryService.class);
sendText.put("type", 1);
try {
sendText.put("player", userQueryService.getUser(player).optString(User.USER_NAME));
} catch (ServiceException e) {
LOGGER.error("service not avaliable");
}
sendText.put("message", jsonObject.optString("message"));
SESSIONS.get(anti).getAsyncRemote().sendText(sendText.toString());
break;
case 2: //落子
ChessGame chessGame = chessPlaying.keySet().contains(player) ? chessPlaying.get(player) : chessPlaying.get(anti);
int x = jsonObject.optInt("x");
int y = jsonObject.optInt("y");
int size = jsonObject.optInt("size");
if (chessGame != null) {
if (chessGame.getChess()[x / size][y / size] != 0) {
return;
}
boolean flag = false;
if (player.equals(chessGame.getPlayer1())) {
if (chessGame.getStep() != 1) {
return;
} else {
sendText.put("color", "black");
chessGame.getChess()[x / size][y / size] = 1;
flag = chessGame.chessCheck(1);
chessGame.setStep(2);
}
} else {
if (chessGame.getStep() != 2) {
return;
} else {
sendText.put("color", "white");
chessGame.getChess()[x / size][y / size] = 2;
flag = chessGame.chessCheck(2);
chessGame.setStep(1);
}
}
sendText.put("type", 2);
sendText.put("player", player);
sendText.put("posX", x);
sendText.put("posY", y);
sendText.put("chess", chessGame.getChess());
sendText.put("step", chessGame.getStep());
//chessPlaying是一个以玩家1为key的正在游戏的Map
//按道理,两个玩家不会出现在多个棋局(卧槽?好像一个人想跟多个人下也不是不讲道理啊……whatever)
//故当游戏结束时,可以按照player和anti移除两次(因为不知道哪个才是玩家1)
//总有一次能正确移除,分开写只是为了好看,没有逻辑原因
if (flag) {
sendText.put("result", "You Win");
chessPlaying.remove(player);
}
SESSIONS.get(player).getAsyncRemote().sendText(sendText.toString());
if (flag) {
sendText.put("result", "You Lose");
chessPlaying.remove(anti);
}
SESSIONS.get(anti).getAsyncRemote().sendText(sendText.toString());
if (flag) {
final ActivityMgmtService activityMgmtService = beanManager.getReference(ActivityMgmtService.class);
activityMgmtService.collectGobang(player, Pointtransfer.TRANSFER_SUM_C_ACTIVITY_GOBANG_START * 2);
SESSIONS.remove(player);
SESSIONS.remove(anti);
}
}
break;
case 7://和棋
if("request".equals(jsonObject.optString("drawType"))){
sendText.put("type",7);
SESSIONS.get(anti).getAsyncRemote().sendText(sendText.toString());
}else if("yes".equals(jsonObject.optString("drawType"))){
sendText.put("type", 6);
sendText.put("message", "【系统】:双方和棋,积分返还,游戏结束");
chessPlaying.remove(player);
chessPlaying.remove(anti);
antiPlayer.remove(player);
antiPlayer.remove(anti);
final ActivityMgmtService activityMgmtService = beanManager.getReference(ActivityMgmtService.class);
activityMgmtService.collectGobang(player, Pointtransfer.TRANSFER_SUM_C_ACTIVITY_GOBANG_START );
activityMgmtService.collectGobang(anti, Pointtransfer.TRANSFER_SUM_C_ACTIVITY_GOBANG_START);
SESSIONS.get(player).getAsyncRemote().sendText(sendText.toString());
SESSIONS.get(anti).getAsyncRemote().sendText(sendText.toString());
SESSIONS.remove(player);
SESSIONS.remove(anti);
}else if("no".equals(jsonObject.optString("drawType"))){
sendText.put("type", 6);
sendText.put("message", "【系统】:对手拒绝和棋,请继续下棋");
SESSIONS.get(player).getAsyncRemote().sendText(sendText.toString());
}
break;
}
}
/**
* Called in case of an error.
*
* @param session session
* @param error error
*/
@OnError
public void onError(final Session session, final Throwable error) {
removeSession(session);
}
/**
* Removes the specified session.
*
* @param session the specified session
*/
private void removeSession(final Session session) {
for (String player : SESSIONS.keySet()) {
if (session.equals(SESSIONS.get(player))) {
if (getAntiPlayer(player) == null) {
for (ChessGame chessGame : chessRandomWait) {
if (player.equals(chessGame.getPlayer1())) {
chessRandomWait.remove(chessGame);
}
}
} else {
if (chessPlaying.get(player) != null) { //说明玩家1断开了链接
ChessGame chessGame = chessPlaying.get(player);
chessGame.setPlayState1(false);
if (!chessGame.isPlayState2()) {
chessPlaying.remove(player);
antiPlayer.remove(player);
//由于玩家2先退出,补偿玩家1的积分
final LatkeBeanManager beanManager = Lifecycle.getBeanManager();
final ActivityMgmtService activityMgmtService = beanManager.getReference(ActivityMgmtService.class);
activityMgmtService.collectGobang(chessGame.getPlayer1(), Pointtransfer.TRANSFER_SUM_C_ACTIVITY_GOBANG_START);
} else {
JSONObject sendText = new JSONObject();
sendText.put("type", 6);
sendText.put("message", "【系统】:对手离开了棋局");
SESSIONS.get(chessGame.getPlayer2()).getAsyncRemote().sendText(sendText.toString());
}
} else if (chessPlaying.get(getAntiPlayer(player)) != null) { //说明玩家2断开了链接
String player1 = getAntiPlayer(player);
ChessGame chessGame = chessPlaying.get(player1);
chessGame.setPlayState2(false);
if (!chessGame.isPlayState1()) {
chessPlaying.remove(player1);
antiPlayer.remove(player1);
//由于玩家1先退出,补偿玩家2的积分
final LatkeBeanManager beanManager = Lifecycle.getBeanManager();
final ActivityMgmtService activityMgmtService = beanManager.getReference(ActivityMgmtService.class);
activityMgmtService.collectGobang(chessGame.getPlayer2(), Pointtransfer.TRANSFER_SUM_C_ACTIVITY_GOBANG_START);
} else {
JSONObject sendText = new JSONObject();
sendText.put("type", 6);
sendText.put("message", "【系统】:对手离开了棋局");
SESSIONS.get(chessGame.getPlayer1()).getAsyncRemote().sendText(sendText.toString());
}
}
}
SESSIONS.remove(player);
}
}
}
private String getAntiPlayer(String player) {
String anti = antiPlayer.get(player);
if (null == anti || anti.equals("")) {
for (String temp : antiPlayer.keySet()) {
if (player.equals(antiPlayer.get(temp))) {
anti = temp;
}
}
}
return anti;
}
private void recoverGame(String userId, String userName, String antiUserId, ChessGame chessGame) {
JSONObject sendText = new JSONObject();
sendText.put("type", 5);
sendText.put("chess", chessGame.getChess());
sendText.put("message", "【系统】:恢复棋盘,当前轮到玩家 [" + (chessGame.getStep() == 1 ? chessGame.getName1() : chessGame.getName2()) + "] 落子");
sendText.put("playerName", userName);
sendText.put("player", userId);
SESSIONS.get(userId).getAsyncRemote().sendText(sendText.toString());
sendText = new JSONObject();
sendText.put("type", 6);
sendText.put("message", "【系统】:对手返回了棋局,当前轮到玩家 [" + (chessGame.getStep() == 1 ? chessGame.getName1() : chessGame.getName2()) + "] 落子");
SESSIONS.get(antiUserId).getAsyncRemote().sendText(sendText.toString());
}
}
class ChessGame {
private long chessId;
private String player1;
private String player2;
private String name1;
private String name2;
private boolean playState1;
private boolean playState2;
private int state;//0空桌,1,等待,2满员
private int[][] chess = null;
private int step;//1-player1,2-player2;
private long starttime;
public ChessGame(String player1, String name1) {
this.chessId = System.currentTimeMillis();
this.player1 = player1;
this.name1 = name1;
this.playState1 = true;
this.playState2 = false;
this.chess = new int[20][20];
this.starttime = System.currentTimeMillis();
for (int i = 0; i < 20; i++) {
for (int j = 0; j < 20; j++) {
chess[i][j] = 0;
}
}
}
public boolean chessCheck(int step) {
//横向检查
for (int i = 0; i < this.chess.length; i++) {
int count = 0;
for (int j = 0; j < this.chess[i].length; j++) {
if (this.chess[i][j] == step) {
count++;
} else if (this.chess[i][j] != step && count < 5) {
count = 0;
}
}
if (count >= 5) {
return true;
}
}
//纵向检查
for (int j = 0; j < this.chess[0].length; j++) {
int count = 0;
for (int i = 0; i < this.chess.length; i++) {
if (this.chess[i][j] == step) {
count++;
} else if (this.chess[i][j] != step && count < 5) {
count = 0;
}
}
if (count >= 5) {
return true;
}
}
//左上右下检查,下一个检查点时上一个检查点横纵坐标均+1
//横向增长,横坐标先行出局
for (int x = 0, y = 0; x < this.chess.length; x++) {
int count = 0;
for (int i = x, j = y; i < this.chess.length; i++, j++) {
if (this.chess[i][j] == step) {
count++;
} else if (this.chess[i][j] != step && count < 5) {
count = 0;
}
}
if (count >= 5) {
return true;
}
}
//纵向增长,纵坐标先出局
for (int x = 0, y = 0; y < this.chess[0].length; y++) {
int count = 0;
for (int i = x, j = y; j < this.chess.length; i++, j++) {
if (this.chess[i][j] == step) {
count++;
} else if (this.chess[i][j] != step && count < 5) {
count = 0;
}
}
if (count >= 5) {
return true;
}
}
//左下右上检查x-1,y+1
//横向增长,横坐标先行出局
for (int x = 0, y = 0; x < this.chess.length; x++) {
int count = 0;
for (int i = x, j = y; i >= 0; i--, j++) {
if (this.chess[i][j] == step) {
count++;
} else if (this.chess[i][j] != step && count < 5) {
count = 0;
}
}
if (count >= 5) {
return true;
}
}
//纵向增长,纵坐标先出局
for (int x = this.chess.length - 1, y = 0; y < this.chess[0].length; y++) {
int count = 0;
for (int i = x, j = y; j < this.chess.length; i--, j++) {
if (this.chess[i][j] == step) {
count++;
} else if (this.chess[i][j] != step && count < 5) {
count = 0;
}
}
if (count >= 5) {
return true;
}
}
return false;
}
public long getChessId() {
return chessId;
}
public void setChessId(long chessId) {
this.chessId = chessId;
}
public String getPlayer1() {
return player1;
}
public void setPlayer1(String player1) {
this.player1 = player1;
}
public String getPlayer2() {
return player2;
}
public void setPlayer2(String player2) {
this.player2 = player2;
}
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
}
public int getStep() {
return step;
}
public void setStep(int step) {
this.step = step;
}
public int[][] getChess() {
return chess;
}
public void setChess(int[][] chess) {
this.chess = chess;
}
public long getStarttime() {
return starttime;
}
public void setStarttime(long starttime) {
this.starttime = starttime;
}
public boolean isPlayState1() {
return playState1;
}
public void setPlayState1(boolean playState1) {
this.playState1 = playState1;
}
public boolean isPlayState2() {
return playState2;
}
public void setPlayState2(boolean playState2) {
this.playState2 = playState2;
}
public String getName1() {
return name1;
}
public void setName1(String name1) {
this.name1 = name1;
}
public String getName2() {
return name2;
}
public void setName2(String name2) {
this.name2 = name2;
}
}