├── README
├── pom.xml
└── src
└── main
└── java
└── com
└── tictactoe
├── game
├── Board.java
├── Game.java
└── Player.java
└── server
├── TicTacToeServer.java
├── TicTacToeServerHandler.java
├── WebSocketServerPipelineFactory.java
└── message
├── GameOverMessageBean.java
├── HandshakeMessageBean.java
├── IncomingMessageBean.java
├── MessageBean.java
├── OutgoingMessageBean.java
└── TurnMessageBean.java
/README:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rocketpages/Netty-TicTacToe-Server/99dbb7fe544cc50ccbae23e5da125effcd245e15/README
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 |
5 | com
6 | TicTacToeServer
7 | 1.0-SNAPSHOT
8 | jar
9 |
10 | TicTacToeServer
11 | http://maven.apache.org
12 |
13 |
14 | UTF-8
15 |
16 |
17 |
18 |
19 | repository.jboss.org
20 | https://repository.jboss.org/nexus/content/repositories/releases/
21 |
22 | false
23 |
24 |
25 |
26 |
27 |
28 |
29 | junit
30 | junit
31 | 3.8.1
32 | test
33 |
34 |
35 | org.jboss.netty
36 | netty
37 | 3.2.6.Final
38 | compile
39 |
40 |
41 | com.google.code.gson
42 | gson
43 | 1.7.1
44 | compile
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/src/main/java/com/tictactoe/game/Board.java:
--------------------------------------------------------------------------------
1 | package com.tictactoe.game;
2 |
3 | import com.tictactoe.game.Game.PlayerLetter;
4 |
5 | /**
6 | * A board for a game of Tic Tac Toe. Represents the current state of the game and which cells have been selected by whom.
7 | *
8 | * @author Kevin Webber
9 | */
10 | public class Board {
11 |
12 | /* The number of winning combinations are small, so we'll keep it simple and do "brute force" matching.
13 | * For a game with a larger grid (such as Go), we would need to develop an algorithm, potentially based on
14 | * "Magic square". http://en.wikipedia.org/wiki/Magic_square
15 | */
16 | public static final int[][] WINNING = { {1,2,3}, {4,5,6}, {7,8,9}, {1,4,7}, {2,5,8}, {3,6,9}, {1,5,9}, {3,5,7} };
17 |
18 | /*
19 | * Represents a flattened game board for Tic Tac Toe. Below is the index value for each game cell.
20 | *
21 | * 1 | 2 | 3
22 | * 4 | 5 | 6
23 | * 7 | 8 | 9
24 | */
25 | PlayerLetter[] cells = new PlayerLetter[9];
26 |
27 | /**
28 | * Mark a cell with the player's selection.
29 | *
30 | * @param gridId
31 | * @param player
32 | */
33 | protected void markCell(int gridId, PlayerLetter player) {
34 | cells[gridId-1] = player;
35 | }
36 |
37 | /**
38 | * Compare the current state of the game board with the possible winning combinations to determine a win condition.
39 | * This should be checked at the end of each turn.
40 | *
41 | * @param player
42 | * @return
43 | */
44 | public boolean isWinner(PlayerLetter player) {
45 | for (int i = 0; i < WINNING.length; i++) {
46 | int[] possibleWinningCombo = WINNING[i];
47 | if (cells[possibleWinningCombo[0]-1] == player && cells[possibleWinningCombo[1]-1] == player && cells[possibleWinningCombo[2]-1] == player) {
48 | return true;
49 | }
50 | }
51 |
52 | return false;
53 | }
54 |
55 | /**
56 | * Determines if the game is tied. The game is considered tied if there is no winner and all cells have been selected.
57 | */
58 | public boolean isTied() {
59 | boolean boardFull = true;
60 | boolean tied = false;
61 |
62 | for (int i = 0; i < 9; i++) {
63 | PlayerLetter letter = cells[i];
64 | if (letter == null) {
65 | boardFull = false;
66 | }
67 | }
68 |
69 | if (boardFull && (!isWinner(PlayerLetter.X) || !isWinner(PlayerLetter.O))) {
70 | tied = true;
71 | }
72 |
73 | return tied;
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/main/java/com/tictactoe/game/Game.java:
--------------------------------------------------------------------------------
1 | package com.tictactoe.game;
2 |
3 | import java.util.Collection;
4 | import java.util.HashMap;
5 | import java.util.Map;
6 |
7 | /**
8 | * Represents a game of Tic Tac Toe. Contains players and a game board.
9 | *
10 | * @author Kevin Webber
11 | */
12 | public class Game {
13 |
14 | private static int GAME_COUNT = 0;
15 |
16 | public enum Status {
17 | WAITING, IN_PROGRESS, WON, TIED
18 | }
19 |
20 | public enum PlayerLetter {
21 | X, O
22 | }
23 |
24 | // The game ID. The server increments this count with each new game initiated.
25 | private final int id;
26 |
27 | // Status of the current game (WAITING, IN_PROGRESS, FINISHED)
28 | private Status status;
29 |
30 | private final Board board;
31 | private Map players;
32 | private PlayerLetter winner;
33 |
34 | public Game() {
35 | this.id = GAME_COUNT++;
36 | this.board = new Board();
37 | status = Status.WAITING;
38 | players = new HashMap();
39 | }
40 |
41 | /**
42 | * Adds a player to this game. Changes status of game from WAITING to IN_PROGRESS if the game fills up.
43 | *
44 | * @param p
45 | * @return
46 | * @throws RuntimeException if there are already 2 or more players assigned to this game.
47 | */
48 | public PlayerLetter addPlayer(Player p) {
49 | if (players.size() >= 2) {
50 | throw new RuntimeException("Too many players. Cannot add more than 1 player to a game.");
51 | }
52 |
53 | PlayerLetter l = (players.containsKey(PlayerLetter.X)) ? PlayerLetter.O : PlayerLetter.X;
54 | p.setLetter(l);
55 | players.put(l, p);
56 |
57 | if (players.size() == 2) {
58 | status = Status.IN_PROGRESS;
59 | }
60 |
61 | return l;
62 | }
63 |
64 | /**
65 | * Marks the selected cell of the user and updates the game's status.
66 | *
67 | * @param gridId
68 | * @param playerLetter
69 | */
70 | public void markCell(int gridId, PlayerLetter playerLetter) {
71 | board.markCell(gridId, playerLetter);
72 | setStatus(playerLetter);
73 | }
74 |
75 | /**
76 | * Updates the status of the game. Invoked after each player's turn.
77 | *
78 | * @param playerLetter
79 | */
80 | private void setStatus(PlayerLetter playerLetter) {
81 | // Checks first to see if the board has a winner.
82 | if (board.isWinner(playerLetter)) {
83 | status = Status.WON;
84 |
85 | if (playerLetter == PlayerLetter.X) {
86 | winner = PlayerLetter.X;
87 | } else {
88 | winner = PlayerLetter.O;
89 | }
90 | // Next check to see if the game has been tied.
91 | } else if (board.isTied()) {
92 | status = Status.TIED;
93 | }
94 | }
95 |
96 | public int getId() {
97 | return id;
98 | }
99 |
100 | public Collection getPlayers() {
101 | return players.values();
102 | }
103 |
104 | public Player getPlayer(PlayerLetter playerLetter) {
105 | return players.get(playerLetter);
106 | }
107 |
108 | /**
109 | * Returns the opponent given a player letter.
110 | */
111 | public Player getOpponent(String currentPlayer) {
112 | PlayerLetter currentPlayerLetter = PlayerLetter.valueOf(currentPlayer);
113 | PlayerLetter opponentPlayerLetter = currentPlayerLetter.equals(PlayerLetter.X) ? PlayerLetter.O : PlayerLetter.X;
114 | return players.get(opponentPlayerLetter);
115 | }
116 |
117 | public Board getBoard() {
118 | return board;
119 | }
120 |
121 | public Status getStatus() {
122 | return status;
123 | }
124 |
125 | public PlayerLetter getWinner() {
126 | return winner;
127 | }
128 |
129 | /**
130 | * Convenience method to determine if a specific player is the winner.
131 | */
132 | public boolean isPlayerWinner(PlayerLetter playerLetter) {
133 | if (status == Status.WON && winner == playerLetter) {
134 | return true;
135 | }
136 |
137 | return false;
138 | }
139 |
140 | /**
141 | * Convenience method to determine if the game has been tied.
142 | */
143 | public boolean isTied() {
144 | if (status == Status.TIED) {
145 | return true;
146 | }
147 |
148 | return false;
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/src/main/java/com/tictactoe/game/Player.java:
--------------------------------------------------------------------------------
1 | package com.tictactoe.game;
2 |
3 | import org.jboss.netty.channel.Channel;
4 |
5 | import com.tictactoe.game.Game.PlayerLetter;
6 |
7 | /**
8 | * Represents a player for a game of Tic Tac Toe.
9 | *
10 | * @author Kevin Webber
11 | */
12 | public class Player {
13 |
14 | // The player's websocket channel. Used for communications.
15 | private Channel channel;
16 |
17 | // The player's currently assigned letter.
18 | private PlayerLetter letter;
19 |
20 | public Player(Channel c) {
21 | channel = c;
22 | }
23 |
24 | public Channel getChannel() {
25 | return channel;
26 | }
27 |
28 | public void setLetter(PlayerLetter l) {
29 | letter = l;
30 | }
31 |
32 | public PlayerLetter getLetter() {
33 | return letter;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/main/java/com/tictactoe/server/TicTacToeServer.java:
--------------------------------------------------------------------------------
1 | package com.tictactoe.server;
2 |
3 | import java.net.InetSocketAddress;
4 | import java.util.concurrent.Executors;
5 |
6 | import org.jboss.netty.bootstrap.ServerBootstrap;
7 | import org.jboss.netty.channel.ChannelFactory;
8 | import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
9 |
10 | public class TicTacToeServer {
11 |
12 | public static void main(String[] args) throws Exception {
13 | ChannelFactory factory =
14 | new NioServerSocketChannelFactory(
15 | Executors.newCachedThreadPool(),
16 | Executors.newCachedThreadPool());
17 |
18 | ServerBootstrap bootstrap = new ServerBootstrap(factory);
19 |
20 | bootstrap.setPipelineFactory(new WebSocketServerPipelineFactory());
21 |
22 | bootstrap.setOption("child.tcpNoDelay", true);
23 | bootstrap.setOption("child.keepAlive", true);
24 |
25 | bootstrap.bind(new InetSocketAddress(9000));
26 |
27 | System.out.println("TicTacToe Server: Listening on port 9000");
28 | }
29 | }
--------------------------------------------------------------------------------
/src/main/java/com/tictactoe/server/TicTacToeServerHandler.java:
--------------------------------------------------------------------------------
1 | package com.tictactoe.server;
2 |
3 | import static com.tictactoe.server.message.GameOverMessageBean.Result.TIED;
4 | import static com.tictactoe.server.message.GameOverMessageBean.Result.YOU_WIN;
5 | import static com.tictactoe.server.message.TurnMessageBean.Turn.WAITING;
6 | import static com.tictactoe.server.message.TurnMessageBean.Turn.YOUR_TURN;
7 | import static org.jboss.netty.handler.codec.http.HttpHeaders.isKeepAlive;
8 | import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.CONNECTION;
9 | import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.ORIGIN;
10 | import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.SEC_WEBSOCKET_KEY1;
11 | import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.SEC_WEBSOCKET_KEY2;
12 | import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.SEC_WEBSOCKET_LOCATION;
13 | import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.SEC_WEBSOCKET_ORIGIN;
14 | import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.SEC_WEBSOCKET_PROTOCOL;
15 | import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.WEBSOCKET_LOCATION;
16 | import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.WEBSOCKET_ORIGIN;
17 | import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.WEBSOCKET_PROTOCOL;
18 | import static org.jboss.netty.handler.codec.http.HttpHeaders.Values.WEBSOCKET;
19 | import static org.jboss.netty.handler.codec.http.HttpVersion.HTTP_1_1;
20 |
21 | import java.security.MessageDigest;
22 | import java.util.HashMap;
23 | import java.util.Map;
24 |
25 | import org.jboss.netty.buffer.ChannelBuffer;
26 | import org.jboss.netty.buffer.ChannelBuffers;
27 | import org.jboss.netty.channel.ChannelFuture;
28 | import org.jboss.netty.channel.ChannelFutureListener;
29 | import org.jboss.netty.channel.ChannelHandlerContext;
30 | import org.jboss.netty.channel.ChannelPipeline;
31 | import org.jboss.netty.channel.ExceptionEvent;
32 | import org.jboss.netty.channel.MessageEvent;
33 | import org.jboss.netty.channel.SimpleChannelUpstreamHandler;
34 | import org.jboss.netty.handler.codec.http.DefaultHttpResponse;
35 | import org.jboss.netty.handler.codec.http.HttpHeaders;
36 | import org.jboss.netty.handler.codec.http.HttpHeaders.Names;
37 | import org.jboss.netty.handler.codec.http.HttpHeaders.Values;
38 | import org.jboss.netty.handler.codec.http.HttpMethod;
39 | import org.jboss.netty.handler.codec.http.HttpRequest;
40 | import org.jboss.netty.handler.codec.http.HttpResponse;
41 | import org.jboss.netty.handler.codec.http.HttpResponseStatus;
42 | import org.jboss.netty.handler.codec.http.HttpVersion;
43 | import org.jboss.netty.handler.codec.http.websocket.DefaultWebSocketFrame;
44 | import org.jboss.netty.handler.codec.http.websocket.WebSocketFrame;
45 | import org.jboss.netty.handler.codec.http.websocket.WebSocketFrameDecoder;
46 | import org.jboss.netty.handler.codec.http.websocket.WebSocketFrameEncoder;
47 | import org.jboss.netty.util.CharsetUtil;
48 |
49 | import com.google.gson.Gson;
50 | import com.tictactoe.game.Board;
51 | import com.tictactoe.game.Game;
52 | import com.tictactoe.game.Game.PlayerLetter;
53 | import com.tictactoe.game.Player;
54 | import com.tictactoe.server.message.GameOverMessageBean;
55 | import com.tictactoe.server.message.HandshakeMessageBean;
56 | import com.tictactoe.server.message.IncomingMessageBean;
57 | import com.tictactoe.server.message.OutgoingMessageBean;
58 | import com.tictactoe.server.message.TurnMessageBean;
59 |
60 | /**
61 | * Handles a server-side channel for a multiplayer game of Tic Tac Toe.
62 | *
63 | * @author Kevin Webber
64 | *
65 | */
66 | public class TicTacToeServerHandler extends SimpleChannelUpstreamHandler {
67 |
68 | static Map games = new HashMap();
69 |
70 | private static final String WEBSOCKET_PATH = "/websocket";
71 |
72 | /* (non-Javadoc)
73 | * @see org.jboss.netty.channel.SimpleChannelUpstreamHandler#messageReceived(org.jboss.netty.channel.ChannelHandlerContext, org.jboss.netty.channel.MessageEvent)
74 | *
75 | * An incoming message (event). Invoked when either a:
76 | *
77 | * - A player navigates to the page. The initial page load triggers an HttpRequest. We perform the WebSocket handshake
78 | * and assign them to a particular game.
79 | *
80 | * - OR A player clicks on a tic tac toe square. The message contains who clicked
81 | * on which square (1 thru 9) and which game they're playing.
82 | */
83 | @Override
84 | public void messageReceived(ChannelHandlerContext ctx, MessageEvent e)
85 | throws Exception {
86 | Object msg = e.getMessage();
87 | if (msg instanceof HttpRequest) {
88 | handleHttpRequest(ctx, (HttpRequest) msg);
89 | } else if (msg instanceof WebSocketFrame) {
90 | handleWebSocketFrame(ctx, (WebSocketFrame) msg);
91 | }
92 | }
93 |
94 | /**
95 | * Handles all HttpRequests. Must be a GET. Performs the WebSocket handshake
96 | * and assigns a player to a game.
97 | *
98 | * @param ctx
99 | * @param req
100 | * @throws Exception
101 | */
102 | private void handleHttpRequest(ChannelHandlerContext ctx, HttpRequest req)
103 | throws Exception {
104 |
105 | // Allow only GET methods.
106 | if (req.getMethod() != HttpMethod.GET) {
107 | sendHttpResponse(ctx, req, new DefaultHttpResponse(
108 | HttpVersion.HTTP_1_1, HttpResponseStatus.FORBIDDEN));
109 | return;
110 | }
111 |
112 | // Serve the WebSocket handshake request.
113 | if (req.getUri().equals(WEBSOCKET_PATH)
114 | && Values.UPGRADE.equalsIgnoreCase(req.getHeader(CONNECTION))
115 | && WEBSOCKET.equalsIgnoreCase(req.getHeader(Names.UPGRADE))) {
116 |
117 | // Create the WebSocket handshake response.
118 | HttpResponse res = new DefaultHttpResponse(
119 | HTTP_1_1,
120 | new HttpResponseStatus(101, "Web Socket Protocol Handshake"));
121 | res.addHeader(Names.UPGRADE, WEBSOCKET);
122 | res.addHeader(CONNECTION, Values.UPGRADE);
123 |
124 | // Fill in the headers and contents depending on handshake method.
125 | // New handshake specification has a challenge.
126 | if (req.containsHeader(SEC_WEBSOCKET_KEY1)
127 | && req.containsHeader(SEC_WEBSOCKET_KEY2)) {
128 |
129 | // New handshake method with challenge
130 | res.addHeader(SEC_WEBSOCKET_ORIGIN, req.getHeader(ORIGIN));
131 | res.addHeader(SEC_WEBSOCKET_LOCATION, getWebSocketLocation(req));
132 | String protocol = req.getHeader(SEC_WEBSOCKET_PROTOCOL);
133 | if (protocol != null) {
134 | res.addHeader(SEC_WEBSOCKET_PROTOCOL, protocol);
135 | }
136 |
137 | // Calculate the answer of the challenge.
138 | String key1 = req.getHeader(SEC_WEBSOCKET_KEY1);
139 | String key2 = req.getHeader(SEC_WEBSOCKET_KEY2);
140 | int a = (int) (Long.parseLong(key1.replaceAll("[^0-9]", "")) / key1
141 | .replaceAll("[^ ]", "").length());
142 | int b = (int) (Long.parseLong(key2.replaceAll("[^0-9]", "")) / key2
143 | .replaceAll("[^ ]", "").length());
144 | long c = req.getContent().readLong();
145 | ChannelBuffer input = ChannelBuffers.buffer(16);
146 | input.writeInt(a);
147 | input.writeInt(b);
148 | input.writeLong(c);
149 | ChannelBuffer output = ChannelBuffers
150 | .wrappedBuffer(MessageDigest.getInstance("MD5").digest(
151 | input.array()));
152 | res.setContent(output);
153 | } else {
154 | // Old handshake method with no challenge:
155 | res.addHeader(WEBSOCKET_ORIGIN, req.getHeader(ORIGIN));
156 | res.addHeader(WEBSOCKET_LOCATION, getWebSocketLocation(req));
157 | String protocol = req.getHeader(WEBSOCKET_PROTOCOL);
158 | if (protocol != null) {
159 | res.addHeader(WEBSOCKET_PROTOCOL, protocol);
160 | }
161 | }
162 |
163 | // Upgrade the connection and send the handshake response.
164 | ChannelPipeline p = ctx.getChannel().getPipeline();
165 | p.remove("aggregator");
166 | p.replace("decoder", "wsdecoder", new WebSocketFrameDecoder());
167 |
168 | // Write handshake response to the channel
169 | ctx.getChannel().write(res);
170 |
171 | // Upgrade encoder to WebSocketFrameEncoder
172 | p.replace("encoder", "wsencoder", new WebSocketFrameEncoder());
173 |
174 | // Initialize the game. Assign players to a game and assign them a letter (X or O)
175 | initGame(ctx);
176 |
177 | return;
178 | }
179 |
180 | // Send an error page otherwise.
181 | sendHttpResponse(ctx, req, new DefaultHttpResponse(
182 | HttpVersion.HTTP_1_1, HttpResponseStatus.FORBIDDEN));
183 | }
184 |
185 | /**
186 | * Initializes a game. Finds an open game for a player (if another player is already waiting) or creates a new game.
187 | *
188 | * @param ctx
189 | */
190 | private void initGame(ChannelHandlerContext ctx) {
191 | // Try to find a game waiting for a player. If one doesn't exist, create a new one.
192 | Game game = findGame();
193 |
194 | // Create a new instance of player and assign their channel for WebSocket communications.
195 | Player player = new Player(ctx.getChannel());
196 |
197 | // Add the player to the game.
198 | Game.PlayerLetter letter = game.addPlayer(player);
199 |
200 | // Add the game to the collection of games.
201 | games.put(game.getId(), game);
202 |
203 | // Send confirmation message to player with game ID and their assigned letter (X or O)
204 | ctx.getChannel().write(new DefaultWebSocketFrame(new HandshakeMessageBean(game.getId(), letter.toString()).toJson()));
205 |
206 | // If the game has begun we need to inform the players. Send them a "turn" message (either "waiting" or "your_turn")
207 | if (game.getStatus() == Game.Status.IN_PROGRESS) {
208 | game.getPlayer(PlayerLetter.X).getChannel().write(new DefaultWebSocketFrame(new TurnMessageBean(YOUR_TURN).toJson()));
209 | game.getPlayer(PlayerLetter.O).getChannel().write(new DefaultWebSocketFrame(new TurnMessageBean(WAITING).toJson()));
210 | }
211 | }
212 |
213 | /**
214 | * Finds an open game for a player (if another player is waiting) or creates a new game.
215 | *
216 | * @return Game
217 | */
218 | private Game findGame() {
219 | // Find an existing game and return it
220 | for (Game g : games.values()) {
221 | if (g.getStatus().equals(Game.Status.WAITING)) {
222 | return g;
223 | }
224 | }
225 |
226 | // Or return a new game
227 | return new Game();
228 | }
229 |
230 | /**
231 | * Process turn data from players. Message contains which square they clicked on. Sends turn data to their
232 | * opponent.
233 | *
234 | * @param ctx
235 | * @param frame
236 | */
237 | private void handleWebSocketFrame(ChannelHandlerContext ctx,
238 | WebSocketFrame frame) {
239 |
240 | Gson gson = new Gson();
241 | IncomingMessageBean message = gson.fromJson(frame.getTextData(), IncomingMessageBean.class);
242 |
243 | Game game = games.get(message.getGameId());
244 | Player opponent = game.getOpponent(message.getPlayer());
245 | Player player = game.getPlayer(PlayerLetter.valueOf(message.getPlayer()));
246 |
247 | // Mark the cell the player selected.
248 | game.markCell(message.getGridIdAsInt(), player.getLetter());
249 |
250 | // Get the status for the current game.
251 | boolean winner = game.isPlayerWinner(player.getLetter());
252 | boolean tied = game.isTied();
253 |
254 | // Respond to the opponent in order to update their screen.
255 | String responseToOpponent = new OutgoingMessageBean(player.getLetter().toString(), message.getGridId(), winner, tied).toJson();
256 | opponent.getChannel().write(new DefaultWebSocketFrame(responseToOpponent));
257 |
258 | // Respond to the player to let them know they won.
259 | if (winner) {
260 | player.getChannel().write(new DefaultWebSocketFrame(new GameOverMessageBean(YOU_WIN).toJson()));
261 | } else if (tied) {
262 | player.getChannel().write(new DefaultWebSocketFrame(new GameOverMessageBean(TIED).toJson()));
263 | }
264 | }
265 |
266 | private void sendHttpResponse(ChannelHandlerContext ctx, HttpRequest req,
267 | HttpResponse res) {
268 | // Generate an error page if response status code is not OK (200).
269 | if (res.getStatus().getCode() != 200) {
270 | res.setContent(ChannelBuffers.copiedBuffer(res.getStatus()
271 | .toString(), CharsetUtil.UTF_8));
272 | }
273 |
274 | // Send the response and close the connection if necessary.
275 | ChannelFuture f = ctx.getChannel().write(res);
276 | if (!isKeepAlive(req) || res.getStatus().getCode() != 200) {
277 | f.addListener(ChannelFutureListener.CLOSE);
278 | }
279 | }
280 |
281 | @Override
282 | public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e)
283 | throws Exception {
284 | e.getCause().printStackTrace();
285 | e.getChannel().close();
286 | }
287 |
288 | private String getWebSocketLocation(HttpRequest req) {
289 | return "ws://" + req.getHeader(HttpHeaders.Names.HOST) + WEBSOCKET_PATH;
290 | }
291 | }
--------------------------------------------------------------------------------
/src/main/java/com/tictactoe/server/WebSocketServerPipelineFactory.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2010 Red Hat, Inc.
3 | *
4 | * Red Hat licenses this file to you under the Apache License, version 2.0
5 | * (the "License"); you may not use this file except in compliance with the
6 | * License. You may obtain a copy of the License at:
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 | * License for the specific language governing permissions and limitations
14 | * under the License.
15 | */
16 | package com.tictactoe.server;
17 |
18 | import static org.jboss.netty.channel.Channels.pipeline;
19 |
20 | import org.jboss.netty.channel.ChannelPipeline;
21 | import org.jboss.netty.channel.ChannelPipelineFactory;
22 | import org.jboss.netty.handler.codec.http.HttpChunkAggregator;
23 | import org.jboss.netty.handler.codec.http.HttpRequestDecoder;
24 | import org.jboss.netty.handler.codec.http.HttpResponseEncoder;
25 | import org.jboss.netty.handler.codec.string.StringDecoder;
26 | import org.jboss.netty.handler.codec.string.StringEncoder;
27 |
28 | /**
29 | * @author The Netty Project
30 | * @author Trustin Lee
31 | *
32 | * @version $Rev: 2080 $, $Date: 2010-01-26 18:04:19 +0900 (Tue, 26 Jan 2010) $
33 | */
34 | public class WebSocketServerPipelineFactory implements ChannelPipelineFactory {
35 | public ChannelPipeline getPipeline() throws Exception {
36 | // Create a default pipeline implementation.
37 | ChannelPipeline pipeline = pipeline();
38 | pipeline.addLast("decoder", new HttpRequestDecoder());
39 | pipeline.addLast("aggregator", new HttpChunkAggregator(65536));
40 | pipeline.addLast("encoder", new HttpResponseEncoder());
41 | pipeline.addLast("handler", new TicTacToeServerHandler());
42 | return pipeline;
43 | }
44 | }
--------------------------------------------------------------------------------
/src/main/java/com/tictactoe/server/message/GameOverMessageBean.java:
--------------------------------------------------------------------------------
1 | package com.tictactoe.server.message;
2 |
3 | public class GameOverMessageBean extends MessageBean {
4 | public enum Result {
5 | YOU_WIN, TIED
6 | }
7 |
8 | private final String type = "game_over";
9 | private Result result;
10 |
11 | public GameOverMessageBean(Result r) {
12 | result = r;
13 | }
14 |
15 | public String getType() {
16 | return type;
17 | }
18 |
19 | public Result getResult() {
20 | return result;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/main/java/com/tictactoe/server/message/HandshakeMessageBean.java:
--------------------------------------------------------------------------------
1 | package com.tictactoe.server.message;
2 |
3 | import com.google.gson.Gson;
4 |
5 | public class HandshakeMessageBean extends MessageBean {
6 |
7 | private final String type = "handshake";
8 | private int gameId;
9 | private String player;
10 |
11 | public HandshakeMessageBean(int gameId, String player) {
12 | this.gameId = gameId;
13 | this.player = player;
14 | }
15 |
16 | public String getType() {
17 | return type;
18 | }
19 |
20 | public int getGameId() {
21 | return gameId;
22 | }
23 |
24 | public void setGameId(int gameId) {
25 | this.gameId = gameId;
26 | }
27 |
28 | public String getPlayer() {
29 | return player;
30 | }
31 |
32 | public void setPlayer(String player) {
33 | this.player = player;
34 | }
35 |
36 | public String toJson() {
37 | Gson gson = new Gson();
38 | return gson.toJson(this);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/main/java/com/tictactoe/server/message/IncomingMessageBean.java:
--------------------------------------------------------------------------------
1 | package com.tictactoe.server.message;
2 |
3 | public class IncomingMessageBean {
4 | private int gameId;
5 | private String player;
6 | private String gridId;
7 |
8 | public int getGameId() {
9 | return gameId;
10 | }
11 | public void setGameId(int gameId) {
12 | this.gameId = gameId;
13 | }
14 | public String getPlayer() {
15 | return player;
16 | }
17 | public void setPlayer(String player) {
18 | this.player = player;
19 | }
20 | public String getGridId() {
21 | return gridId;
22 | }
23 | public void setGridId(String gridId) {
24 | this.gridId = gridId;
25 | }
26 | public int getGridIdAsInt() {
27 | return Integer.valueOf(gridId.substring(gridId.length() - 1));
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/main/java/com/tictactoe/server/message/MessageBean.java:
--------------------------------------------------------------------------------
1 | package com.tictactoe.server.message;
2 |
3 | import com.google.gson.Gson;
4 |
5 | public class MessageBean {
6 |
7 | public String toJson() {
8 | Gson gson = new Gson();
9 | return gson.toJson(this);
10 | }
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/src/main/java/com/tictactoe/server/message/OutgoingMessageBean.java:
--------------------------------------------------------------------------------
1 | package com.tictactoe.server.message;
2 |
3 | public class OutgoingMessageBean extends MessageBean {
4 | private final String type = "response";
5 | private String opponent;
6 | private String gridId;
7 | private boolean winner;
8 | private boolean tied;
9 |
10 | public OutgoingMessageBean(String opponent, String grid, boolean winner, boolean tied) {
11 | this.opponent = opponent;
12 | this.gridId = grid;
13 | this.winner = winner;
14 | this.tied = tied;
15 | }
16 |
17 | public String getType() {
18 | return type;
19 | }
20 |
21 | public String getOpponent() {
22 | return opponent;
23 | }
24 |
25 | public String getGrid() {
26 | return gridId;
27 | }
28 |
29 | public boolean getWinner() {
30 | return winner;
31 | }
32 |
33 | public boolean getTied() {
34 | return tied;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/main/java/com/tictactoe/server/message/TurnMessageBean.java:
--------------------------------------------------------------------------------
1 | package com.tictactoe.server.message;
2 |
3 | public class TurnMessageBean extends MessageBean {
4 |
5 | public enum Turn {
6 | WAITING, YOUR_TURN
7 | }
8 |
9 | private final String type = "turn";
10 | private Turn turn;
11 |
12 | public TurnMessageBean(Turn t) {
13 | turn = t;
14 | }
15 |
16 | public String getType() {
17 | return type;
18 | }
19 |
20 | public Turn getTurn() {
21 | return turn;
22 | }
23 |
24 | }
25 |
--------------------------------------------------------------------------------