-
Notifications
You must be signed in to change notification settings - Fork 0
/
README.txt
288 lines (217 loc) · 14 KB
/
README.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
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
=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=
CIS 120 Game Project README
PennKey: palmerpa (24904824)
=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=
===================
=: Core Concepts :=
===================
- List the four core concepts, the features they implement, and why each feature
is an appropriate use of the concept. Incorporate the feedback you got after
submitting your proposal.
1. Network I/O
My game is an online multiplayer. Players connect to a server that handles connections,
disconnections, the and nearly all game interactions. Particularly, the tick events
happen on the server and the server determines and notifies the clients of any changes
to the model by sending commands. The client receives these commands and updates its
copy of the model accordingly. Additionally, the client sends the server three kinds of
requests over the course of the game, also in the form of commands. Therefore, I created
a client-server architecture that is capable of managing multiple client connections
and allows for live two way communication between the client and server.
2. Appropriately modeling state using collections.
The snake itself is internally stored as a Deque<Point>. Using a Deque allows me to
very quickly and efficiently move the snake on each tick and makes it easy to grow
the snake when it eats food. The implementation that I use is a ConcurrentLinkedDeque
because it is potentially possible for two threads to attempt to access or modify
the snake simultaneously.
Additionally, the GameModel class (which stores the state of the game) uses two
ConcurrentHashMaps, one for storing IDs (Strings) to Snakes and the other for
storing IDs to Food(s). Using Maps allow me to succinctly refer to a particular
object in a command and quickly access that object when I am parsing that command.
This is very important as, for instance, when a snake eats food all of the clients
must know exactly which snake ate which food in order to keep their version of the
game model synchronized with the server's version. I used the ConcurrentHashMap
implementation to prevent ConcurrentModificationExceptions, for the same reason as
why I used a ConcurrentLinkedDeque above.
3. Using inheritance/subtyping for dynamic dispatch.
I use inheritance and subtyping extensively in order to share as much code as possible
between the client and the server. The first of these classes is Command, which is
subtyped by both the client and the server to create the commands that each one is
capable of sending. Essentially Commands accept various parameters and build a valid
String of that command that is ready to be sent over a SocketConnection. The commands
that the server sends are Create, Destroy, Grow, and Turn. A CreateCommand is used to
notify the clients that they should create a new snake or new food and add it to their
version of the model. A DestroyCommand is used to notify the clients that they should
remove a particular snake or food from their model. This is used when a snake eats a
food (and therefore the food must be destroyed so that no one else can eat it) and when
a snake dies. A GrowCommand is used to notify the client that it should add a point to
a particular snake's body (after it ate a piece of food). Finally, a turn command is
used to notify the clients that a particular snake changed its movement direction.
A client can send three commands, a StartCommand, KeyCommand, and WhoAmICommand. A
StartCommand is used whenever the client starts a new game. It results in two actions.
Firstly, it notifies the client who sent the command of all existing snakes and foods.
Secondly, it causes the server to send a CreateCommand to all clients telling them to
add a new snake to their model for that client. A KeyCommand is sent whenever a client
presses a (valid) key. It notifies the server that a particular client wants to change
the movement direction of their snake. Lastly, a WhoAmICommand requests that the server
notify the client who sent the command of the ID that represents them in the model. This
command is probably the simplest yet also the subtlest and easiest to misunderstand
command out of all of the commands. It essentially tells the client what their own
identity is and is sent almost immediately after a connection is established.
The Protocol is used to process incoming messages. It is subclassed by ServerProtocol
and ClientProtocol in the Server and Client packages respectively. All SocketConnections
need to have a protocol. It is used to determine how to react and respond to incoming
commands and requests.
A SocketConnection wraps a socket object to make I/O with it easier. On a low-level,
it makes a PrintWriter from the socket's output stream, a BufferedReader from the
socket's input stream, and starts up a thread that waits for incoming messages. All of
that functionality is needed by both the client and server. This class is subtyped by
com.palmerpaul.Server.ClientConnection and com.palmerpaul.Client.ServerConnection (no,
I did not accidentally switch Client and Server there). It is necessary for the server
and client to each have their own Protocol subclass because each one has its own
additional requirements for establishing a connection. The ServerConnection must be
capable of creating a connection using just a hostname and port (rather than an
already established Socket object) and also adds useful logging messages. The
ClientConnection overrides the killConnection method to also remove the client
from the servers map of user ID's to ClientConnections and it adds a private field
for the user ID to the connection object, which is used throughout the server-side
part of the application.
4. Using I/O to parse a novel file format.
(see https://piazza.com/class/irxmrhiejtb485?cid=2103 for permission)
I am using I/O (a PrintWriter and a BufferedReader) to send communication between
the server and clients. These communications come in many different formats depending
on what kind of information needs to be communicated. Most of the parsing of the
commands occur in the ClientProtocol and ServerProtocol classes. Additionally, classes
like Point and Direction provide static methods for parsing String representations of
objects of those types. The following is an enumeration of the formats / grammars of
all of the messages that can be sent.
CREATE FOOD <ID> <Point>
CREATE SNAKE <ID> <Direction> {<Point>;<Point>;<Point>...}
DESTROY FOOD <ID>
DESTROY SNAKE <ID>
TICK
TURN <ID> <Direction>
GROW <ID>
YOU <ID>
START
KEY <Direction>
WHOAMI
PING
<ID> is an 8 character alphanumeric identifier,
<Direction> is one of LEFT, UP, RIGHT, or DOWN
<Point> is of the format (<x-coord>,<y-coord>) (x-coord and y-coord are ints)
=========================
=: Your Implementation :=
=========================
- Provide an overview of each of the classes in your code, and what their
function is in the overall game.
CLIENT
Canvas - The main display. All graphics are drawn here.
ClientCommands - Commands that the client can send. Contains the KeyCommand,
StartCommand, and WhoAmICommand classes, which all inherit from Command.
ClientProtocol - Processes messages sent by the server and performs necessary actions.
Game - Entry point for the client. Prompts the user to input a server address,
displays the instructions window, sets up the root JFrame that holds the canvas,
and calls the canvas.setup() method, which starts the actual game.
ServerConnection - Connection to the server. Accepts incoming messages from the server
and passes them to the ClientProtocol for parsing and interpretation. Also provides a
way to send messages back to the server.
ROBOT
Robot - A dumb non-human player that pretty much just moves in a square. It interacts
with the server in the same way that a client would. In fact, it actually just
initializes a client and sends a KeyCommand every couple of hundred milliseconds to
simulate a human player pressing an arrow key. Mainly used for testing purposes.
SERVER
ClientConnection - Connection to a client. Provides a way to send messages to the
client. Also, accepts incoming messages from the client and passes them to the
ServerProtocol for parsing and interpretation. A ClientConnection object is created
on the server for each client who connects and is removed when that client disconnects.
Response - These are the things that server uses to send messages. Each response has
a message field and a recipients field. Responses can be created using Commands or
Strings.
Server - This is the real "meat" of the server. Handles client connections and
disconnections. Manages a collection of open socket connections to clients. Routes
Responses to the appropriate clients. Controls the "tick" of the game.
ServerCommands - Commands that the server can send. Contains the CreateCommand,
DestroyCommand, GrowCommand and TurnCommand classes, which all inherit from Command.
ServerMain - Entry point for the server. Prompts the user to input a port to host
the server from. Creates the Server object. Displays a small JFrame with a message
to let the user know that the server is running.
ServerProtocol - Processes messages sent by a client and performs necessary actions.
Each client gets their own ServerProtocol object so that individualized state can be
preserved throughout the lifetime of the client.
SHARED
Command - Parent class of all commands sent by the server and client. Each command
has its own associated format / grammar. Aids in the creation of commands with
potentially complicated structures.
Direction - Enumeration of the directions that a snake can move in.
Food - A food. Stores it location and a color to use to display it. The
color that is used is determined individually by each client (i.e. two clients
most likely will not see a particular food object with the same color).
GameConstants - All the "magic numbers" used in the game. Allows for easy
modification of many of the game settings, such as the size of the screen,
the snake speed, the amount of food that exists at any given time, the size
of one "grid unit" (see the Javadocs for a detailed explanation of this), etc.
GameModel - The game model used by the client and server. Stores a map of
IDs to snakes and another map of IDs to food. The model is 100% thread safe.
Point - The abstract (not in the technical sense) concept of a point.
Protocol - Processes incoming messages. Subclasses must implement the process(String)
method. ServerProtocol and ClientProtocol are both children of this class. All
SocketConnection objects must have a protocol.
Snake - A snake. Stores the points that make up its body as a Deque<Point>
and the direction that it is moving in. Provides methods for moving a snake,
growing a snake, getting the length of a snake, etc.
SocketConnection - A connection with a socket. On a high-level, it wraps a socket
object to make I/O easier. On a low-level it makes a PrintWriter from the socket's
output stream, a BufferedReader from the socket's input stream, and starts up a
thread that waits for incoming messages. ClientConnection and ServerConnection
both inherit from this class.
- Were there any significant stumbling blocks while you were implementing your
game (related to your design, or otherwise)?
This was the first time that I had written my own server-client architecture. In the
beginning I just kind of messed around with Socket objects until I figured out what
to do. Writing the server and client code involved quite a bit of trial and error.
When I first tested my game there were numerous issues with concurrency. I realized
that I needed to make my collections thread safe which itself took some time to do
correctly, as there are quite a few collections in the application and many ways of
interacting with (and potentially breaking) those collections.
Lastly, there was a very strange bug that involved the snake being drawn a significant
number of pixels away from where it actually was. This ended up being an issue with how
the client and server were syncing their models and a (now removed) alternate
constructor in my Snake class. It took several hours of debugging to properly identify
and rectify the issue due to its seemingly random behavior and the bug only appearing
under very specific circumstances.
- Evaluate your design. Is there a good separation of functionality? How well is
private state encapsulated? What would you refactor, if given the chance?
I think that I separated the functionality of the game pretty well. Splitting up
my code into packages definitely helped with my organization. Before, I had all the
classes in the default package and I was pretty much writing spaghetti code. Dividing
the code into packages forced me to consider which parts of my code depended on which
other parts and allowed me to share a considerable portion of the code between the
client and server (see the com.palmerpaul.Shared package).
========================
=: External Resources :=
========================
- Cite any external resources (libraries, images, tutorials, etc.) that you may
have used while implementing your game.
Lots and lots of Javadocs.
==========================
=: How to Compile / Run :=
==========================
Compiling
1. unzip [DIR_NAME].zip
2. cd [DIR_NAME]
3. mkdir bin (if bin directory does not already exist)
4. javac src/com/palmerpaul/*/*.java -d bin/
5. cd bin
Running the Server
1. Get the public IP number of the hosts (needed to connect clients)
- Search through the output of ifconfig
- curl ifconfig.me
2. java com.palmerpaul.Server.ServerMain
3. Enter port number in pop-up dialog (21212 works well in my tests)
Running the Client
1. Start the server
2. java com.palmerpaul.Client.Game (runs the client)
Running the Robot
1. Start the server
2. java com.palmerpaul.Robot.Robot (runs the robot)