817c7517 by Barry

Initial commit

0 parents
1
2 class CommandHandler(object):
3
4 def tokenize_params(self):
5 cleaned = self.params.lower().strip()
6 tokens = []
7 for token in cleaned.split(' '):
8 token = token.strip()
9 if len(token) > 0:
10 tokens.append(token)
11 self.tokens = tokens
12
13 def look(self):
14 self.mud.send_message(self.id, "")
15 if len(self.tokens) > 0 and self.tokens[0] == 'at':
16 del self.tokens[0]
17 if len(self.tokens) == 0:
18 self.mud.send_message(self.id, self.room.get_description())
19 else:
20 subject = self.tokens[0]
21 items = self.room.get_look_items()
22 for item in items:
23 if subject in item:
24 self.mud.send_message(self.id, items[item])
25 return True
26 self.mud.send_message(self.id, "That doesn't seem to be here.")
27
28 playershere = []
29 # go through every player in the game
30 for pid, pl in self.players.items():
31 # if they're in the same room as the player
32 if self.players[pid]["room"] == self.players[self.id]["room"]:
33 # ... and they have a name to be shown
34 if self.players[pid]["name"] is not None:
35 # add their name to the list
36 playershere.append(self.players[pid]["name"])
37
38 # send player a message containing the list of players in the room
39 self.mud.send_message(self.id, "Players here: {}".format(
40 ", ".join(playershere)))
41
42 # send player a message containing the list of exits from this room
43 self.mud.send_message(self.id, "Exits are: {}".format(
44 ", ".join(self.room.get_exits())))
45
46 return True
47
48 def parse(self, id, cmd, params, room, mud, players):
49 self.id = id
50 self.cmd = cmd
51 self.params = params
52 self.tokenize_params()
53 self.room = room
54 self.mud = mud
55 self.players = players
56
57 try:
58 method = getattr(self, cmd)
59 return method()
60 except AttributeError:
61 return False
62
1 Commands:
2 say <message> - Says something out loud, e.g. 'say Hello'
3 look - Examines the surroundings, e.g. 'look'
4 go <exit> - Moves through the exit specified, e.g. 'go outside'
5 save - Saves your character
6 quit - Saves your character then closes the connection
...\ No newline at end of file ...\ No newline at end of file
1 #!/usr/bin/env python
2
3 """A simple Multi-User Dungeon (MUD) game. Players can talk to each
4 other, examine their surroundings and move between rooms.
5
6 Some ideas for things to try adding:
7 * More rooms to explore
8 * An 'emote' command e.g. 'emote laughs out loud' -> 'Mark laughs
9 out loud'
10 * A 'whisper' command for talking to individual players
11 * A 'shout' command for yelling to players in all rooms
12 * Items to look at in rooms e.g. 'look fireplace' -> 'You see a
13 roaring, glowing fire'
14 * Items to pick up e.g. 'take rock' -> 'You pick up the rock'
15 * Monsters to fight
16 * Loot to collect
17 * Saving players accounts between sessions
18 * A password login
19 * A shop from which to buy items
20
21 author: Mark Frimston - mfrimston@gmail.com
22 """
23
24 import time
25 # import datetime
26 #import io
27 import json
28
29 # import the MUD server class
30 from mudserver import MudServer
31 from commands import CommandHandler
32
33 print('STARTING MUD\r\n\r\n\r\n')
34
35 rooms = {}
36
37 # stores the players in the game
38 players = {}
39
40 # start the server
41 mud = MudServer()
42
43 cmd_handler = CommandHandler()
44
45 def load_room(room_name):
46 print("Loading room:" + room_name)
47 try:
48 if room_name in rooms:
49 return rooms[room_name]
50
51
52 module = __import__('rooms.' + room_name.lower())
53 room_module = getattr(module, room_name.lower())
54 room_class = getattr(room_module, room_name)
55 instance = room_class()
56 rooms[room_name] = instance
57
58 return instance
59 except Exception as e:
60 print(e)
61 return None
62
63 def save_object_to_file(obj, filename):
64 with open(filename, 'w', encoding='utf-8') as f:
65 f.write(json.dumps(obj, ensure_ascii=False))
66
67 def load_object_from_file(filename):
68 try:
69 with open(filename, 'r', encoding='utf-8') as f:
70 return json.loads(f.read())
71 except Exception:
72 return None
73
74 def prompt(pid):
75 if "prompt" not in players[pid]:
76 players[pid]["prompt"] = "> "
77 mud.send_message(pid, "\r\n" + players[pid]["prompt"], '')
78
79 # main game loop. We loop forever (i.e. until the program is terminated)
80 while True:
81
82 # pause for 1/5 of a second on each loop, so that we don't constantly
83 # use 100% CPU time
84 time.sleep(0.001)
85
86 # TODO: Add some cache removal if older than X
87 # now = datetime.datetime.now()
88 # delta = now - datetime.timedelta(minutes = 2)
89 # for room in rooms:
90 # if room.created < delta:
91 # del rooms[room]
92
93 # 'update' must be called in the loop to keep the game running and give
94 # us up-to-date information
95 mud.update()
96
97 # go through any newly connected players
98 for id in mud.get_new_players():
99
100 # add the new player to the dictionary, noting that they've not been
101 # named yet.
102 # The dictionary key is the player's id number. We set their room to
103 # None initially until they have entered a name
104 # Try adding more player stats - level, gold, inventory, etc
105 players[id] = {
106 "name": None,
107 "room": None,
108 "inventory": None,
109 "prompt": "> ",
110 }
111
112 # send the new player a prompt for their name
113 mud.send_message(id, "What is your name?")
114
115 # go through any recently disconnected players
116 for id in mud.get_disconnected_players():
117
118 # if for any reason the player isn't in the player map, skip them and
119 # move on to the next one
120 if id not in players:
121 continue
122
123 # go through all the players in the game
124 for pid, pl in players.items():
125 # send each player a message to tell them about the diconnected
126 # player
127 mud.send_message(pid, "{} quit the game".format(
128 players[id]["name"]))
129
130 # remove the player's entry in the player dictionary
131 del(players[id])
132
133 # go through any new commands sent from players
134 for id, command, params in mud.get_commands():
135
136 # if for any reason the player isn't in the player map, skip them and
137 # move on to the next one
138 if id not in players:
139 continue
140
141 if players[id]["room"]:
142 rm = load_room(players[id]["room"])
143 # if the player hasn't given their name yet, use this first command as
144 # their name and move them to the starting room.
145 if players[id]["name"] is None:
146
147 loaded_player = load_object_from_file("players/{}.json".format(command))
148 if loaded_player is None:
149 players[id]["name"] = command
150 players[id]["room"] = "Tavern"
151 else:
152 players[id] = loaded_player
153
154 # go through all the players in the game
155 for pid, pl in players.items():
156 # send each player a message to tell them about the new player
157 mud.send_message(pid, "{} entered the game".format(
158 players[id]["name"]))
159
160 # send the new player a welcome message
161 mud.send_message(id, "\r\n\r\nWelcome to the game, {}. ".format(
162 players[id]["name"])
163 + "\r\nType 'help' for a list of commands. Have fun!\r\n\r\n")
164
165 # send the new player the description of their current room
166 mud.send_message(id, load_room(players[id]["room"]).get_description())
167
168 # each of the possible commands is handled below. Try adding new
169 # commands to the game!
170
171 # 'help' command
172 elif command == "help":
173
174 with open('help/help.txt', 'r', encoding='utf-8') as f:
175 for line in f:
176 mud.send_message(id, line, '\r')
177 mud.send_message(id, '')
178
179 # 'help' command
180 elif command == "quit":
181
182 # send the player back the list of possible commands
183 mud.send_message(id, "Saving...")
184 save_object_to_file(players[id], "players/{}.json".format(players[id]["name"]))
185 mud.disconnect_player(id)
186
187 # 'help' command
188 elif command == "save":
189
190 # send the player back the list of possible commands
191 mud.send_message(id, "Saving...")
192 save_object_to_file(players[id], "players/{}.json".format(players[id]["name"]))
193 mud.send_message(id, "Save complete")
194
195 # 'say' command
196 elif command == "say":
197
198 # go through every player in the game
199 for pid, pl in players.items():
200 # if they're in the same room as the player
201 if players[pid]["room"] == players[id]["room"]:
202 # send them a message telling them what the player said
203 mud.send_message(pid, "{} says: {}".format(
204 players[id]["name"], params))
205
206 # 'look' command
207 elif cmd_handler.parse(id, command, params, rm, mud, players):
208
209 pass
210
211 # 'go' command
212 elif command == "go":
213
214 # store the exit name
215 ex = params.lower()
216
217 # store the player's current room
218 rm = load_room(players[id]["room"])
219
220 # if the specified exit is found in the room's exits list
221 if ex in rm.get_exits():
222
223 # go through all the players in the game
224 for pid, pl in players.items():
225 # if player is in the same room and isn't the player
226 # sending the command
227 if players[pid]["room"] == players[id]["room"] \
228 and pid != id:
229 # send them a message telling them that the player
230 # left the room
231 mud.send_message(pid, "{} left via exit '{}'".format(
232 players[id]["name"], ex))
233
234 # update the player's current room to the one the exit leads to
235 loaded = load_room(rm.get_exits()[ex])
236 if loaded == None:
237 mud.send_message(id, "An invisible force prevents you from going in that direction.")
238 continue
239 else:
240 players[id]["room"] = rm.get_exits()[ex]
241 rm = loaded
242
243 # go through all the players in the game
244 for pid, pl in players.items():
245 # if player is in the same (new) room and isn't the player
246 # sending the command
247 if players[pid]["room"] == players[id]["room"] \
248 and pid != id:
249 # send them a message telling them that the player
250 # entered the room
251 mud.send_message(pid,
252 "{} arrived via exit '{}'".format(
253 players[id]["name"], ex))
254
255 # send the player a message telling them where they are now
256 mud.send_message(id, "You arrive at '{}'".format(rm.get_title()))
257
258 # the specified exit wasn't found in the current room
259 else:
260 # send back an 'unknown exit' message
261 mud.send_message(id, "Unknown exit '{}'".format(ex))
262
263
264 # some other, unrecognised command
265 else:
266 # send back an 'unknown command' message
267 mud.send_message(id, "Unknown command '{}'".format(command))
268 prompt(id)
1 """Basic MUD server module for creating text-based Multi-User Dungeon
2 (MUD) games.
3
4 Contains one class, MudServer, which can be instantiated to start a
5 server running then used to send and receive messages from players.
6
7 author: Mark Frimston - mfrimston@gmail.com
8 """
9
10
11 import socket
12 import select
13 import time
14 import sys
15
16
17 class MudServer(object):
18 """A basic server for text-based Multi-User Dungeon (MUD) games.
19
20 Once created, the server will listen for players connecting using
21 Telnet. Messages can then be sent to and from multiple connected
22 players.
23
24 The 'update' method should be called in a loop to keep the server
25 running.
26 """
27
28 # An inner class which is instantiated for each connected client to store
29 # info about them
30
31 class _Client(object):
32 """Holds information about a connected player"""
33
34 # the socket object used to communicate with this client
35 socket = None
36 # the ip address of this client
37 address = ""
38 # holds data send from the client until a full message is received
39 buffer = ""
40 # the last time we checked if the client was still connected
41 lastcheck = 0
42
43 def __init__(self, socket, address, buffer, lastcheck):
44 self.socket = socket
45 self.address = address
46 self.buffer = buffer
47 self.lastcheck = lastcheck
48
49 # Used to store different types of occurences
50 _EVENT_NEW_PLAYER = 1
51 _EVENT_PLAYER_LEFT = 2
52 _EVENT_COMMAND = 3
53
54 # Different states we can be in while reading data from client
55 # See _process_sent_data function
56 _READ_STATE_NORMAL = 1
57 _READ_STATE_COMMAND = 2
58 _READ_STATE_SUBNEG = 3
59
60 # Command codes used by Telnet protocol
61 # See _process_sent_data function
62 _TN_INTERPRET_AS_COMMAND = 255
63 _TN_ARE_YOU_THERE = 246
64 _TN_WILL = 251
65 _TN_WONT = 252
66 _TN_DO = 253
67 _TN_DONT = 254
68 _TN_SUBNEGOTIATION_START = 250
69 _TN_SUBNEGOTIATION_END = 240
70
71 # socket used to listen for new clients
72 _listen_socket = None
73 # holds info on clients. Maps client id to _Client object
74 _clients = {}
75 # counter for assigning each client a new id
76 _nextid = 0
77 # list of occurences waiting to be handled by the code
78 _events = []
79 # list of newly-added occurences
80 _new_events = []
81
82 def __init__(self):
83 """Constructs the MudServer object and starts listening for
84 new players.
85 """
86
87 self._clients = {}
88 self._nextid = 0
89 self._events = []
90 self._new_events = []
91
92 # create a new tcp socket which will be used to listen for new clients
93 self._listen_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
94
95 # set a special option on the socket which allows the port to be
96 # immediately without having to wait
97 self._listen_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR,
98 1)
99
100 # bind the socket to an ip address and port. Port 23 is the standard
101 # telnet port which telnet clients will use, however on some platforms
102 # this requires root permissions, so we use a higher arbitrary port
103 # number instead: 1234. Address 0.0.0.0 means that we will bind to all
104 # of the available network interfaces
105 self._listen_socket.bind(("0.0.0.0", 1234))
106
107 # set to non-blocking mode. This means that when we call 'accept', it
108 # will return immediately without waiting for a connection
109 self._listen_socket.setblocking(False)
110
111 # start listening for connections on the socket
112 self._listen_socket.listen(1)
113
114 def update(self):
115 """Checks for new players, disconnected players, and new
116 messages sent from players. This method must be called before
117 up-to-date info can be obtained from the 'get_new_players',
118 'get_disconnected_players' and 'get_commands' methods.
119 It should be called in a loop to keep the game running.
120 """
121
122 # check for new stuff
123 self._check_for_new_connections()
124 self._check_for_disconnected()
125 self._check_for_messages()
126
127 # move the new events into the main events list so that they can be
128 # obtained with 'get_new_players', 'get_disconnected_players' and
129 # 'get_commands'. The previous events are discarded
130 self._events = list(self._new_events)
131 self._new_events = []
132
133 def disconnect_player(self, clid):
134 cl = self._clients[clid]
135 cl.socket.close()
136 self._handle_disconnect(clid)
137
138 def get_new_players(self):
139 """Returns a list containing info on any new players that have
140 entered the game since the last call to 'update'. Each item in
141 the list is a player id number.
142 """
143 retval = []
144 # go through all the events in the main list
145 for ev in self._events:
146 # if the event is a new player occurence, add the info to the list
147 if ev[0] == self._EVENT_NEW_PLAYER:
148 retval.append(ev[1])
149 # return the info list
150 return retval
151
152 def get_disconnected_players(self):
153 """Returns a list containing info on any players that have left
154 the game since the last call to 'update'. Each item in the list
155 is a player id number.
156 """
157 retval = []
158 # go through all the events in the main list
159 for ev in self._events:
160 # if the event is a player disconnect occurence, add the info to
161 # the list
162 if ev[0] == self._EVENT_PLAYER_LEFT:
163 retval.append(ev[1])
164 # return the info list
165 return retval
166
167 def get_commands(self):
168 """Returns a list containing any commands sent from players
169 since the last call to 'update'. Each item in the list is a
170 3-tuple containing the id number of the sending player, a
171 string containing the command (i.e. the first word of what
172 they typed), and another string containing the text after the
173 command
174 """
175 retval = []
176 # go through all the events in the main list
177 for ev in self._events:
178 # if the event is a command occurence, add the info to the list
179 if ev[0] == self._EVENT_COMMAND:
180 retval.append((ev[1], ev[2], ev[3]))
181 # return the info list
182 return retval
183
184 def send_message(self, to, message, line_ending='\r\n'):
185 """Sends the text in the 'message' parameter to the player with
186 the id number given in the 'to' parameter. The text will be
187 printed out in the player's terminal.
188 """
189 # we make sure to put a newline on the end so the client receives the
190 # message on its own line
191 self._attempt_send(to, message + line_ending)
192
193 def shutdown(self):
194 """Closes down the server, disconnecting all clients and
195 closing the listen socket.
196 """
197 # for each client
198 for cl in self._clients.values():
199 # close the socket, disconnecting the client
200 cl.socket.shutdown()
201 cl.socket.close()
202 # stop listening for new clients
203 self._listen_socket.close()
204
205 def _attempt_send(self, clid, data):
206 # python 2/3 compatability fix - convert non-unicode string to unicode
207 if sys.version < '3' and type(data) != unicode:
208 data = unicode(data, "latin1")
209 try:
210 # look up the client in the client map and use 'sendall' to send
211 # the message string on the socket. 'sendall' ensures that all of
212 # the data is sent in one go
213 if sys.version_info != (3, 4, 0):
214 bytes_to_send = bytearray(data, 'latin1')
215 else:
216 bytes_to_send = bytearray(data)
217
218 client_socket = self._clients[clid].socket
219 client_socket.sendall(bytes_to_send)
220 # KeyError will be raised if there is no client with the given id in
221 # the map
222 except KeyError:
223 pass
224 # If there is a connection problem with the client (e.g. they have
225 # disconnected) a socket error will be raised
226 except Exception as e:
227 sys.print_exception(e)
228 self._handle_disconnect(clid)
229
230 def _check_for_new_connections(self):
231
232 # 'select' is used to check whether there is data waiting to be read
233 # from the socket. We pass in 3 lists of sockets, the first being those
234 # to check for readability. It returns 3 lists, the first being
235 # the sockets that are readable. The last parameter is how long to wait
236 # - we pass in 0 so that it returns immediately without waiting
237 rlist, wlist, xlist = select.select([self._listen_socket], [], [], 0)
238
239 # if the socket wasn't in the readable list, there's no data available,
240 # meaning no clients waiting to connect, and so we can exit the method
241 # here
242 if self._listen_socket not in rlist:
243 return
244
245 # 'accept' returns a new socket and address info which can be used to
246 # communicate with the new client
247 joined_socket, addr = self._listen_socket.accept()
248
249 # set non-blocking mode on the new socket. This means that 'send' and
250 # 'recv' will return immediately without waiting
251 joined_socket.setblocking(False)
252
253 # construct a new _Client object to hold info about the newly connected
254 # client. Use 'nextid' as the new client's id number
255 self._clients[self._nextid] = MudServer._Client(joined_socket, addr[0],
256 "", time.time())
257
258 # add a new player occurence to the new events list with the player's
259 # id number
260 self._new_events.append((self._EVENT_NEW_PLAYER, self._nextid))
261
262 # add 1 to 'nextid' so that the next client to connect will get a
263 # unique id number
264 self._nextid += 1
265
266 def _check_for_disconnected(self):
267
268 # go through all the clients
269 for id, cl in list(self._clients.items()):
270
271 # if we last checked the client less than 5 seconds ago, skip this
272 # client and move on to the next one
273 if time.time() - cl.lastcheck < 5.0:
274 continue
275
276 # send the client an invisible character. It doesn't actually
277 # matter what we send, we're really just checking that data can
278 # still be written to the socket. If it can't, an error will be
279 # raised and we'll know that the client has disconnected.
280 self._attempt_send(id, "\x00")
281
282 # update the last check time
283 cl.lastcheck = time.time()
284
285 def _check_for_messages(self):
286
287 # go through all the clients
288 for id, cl in list(self._clients.items()):
289
290 # we use 'select' to test whether there is data waiting to be read
291 # from the client socket. The function takes 3 lists of sockets,
292 # the first being those to test for readability. It returns 3 list
293 # of sockets, the first being those that are actually readable.
294 rlist, wlist, xlist = select.select([cl.socket], [], [], 0)
295
296 # if the client socket wasn't in the readable list, there is no
297 # new data from the client - we can skip it and move on to the next
298 # one
299 if cl.socket not in rlist:
300 continue
301
302 try:
303 # read data from the socket, using a max length of 4096
304 data = cl.socket.recv(4096).decode("latin1")
305
306 # process the data, stripping out any special Telnet commands
307 message = self._process_sent_data(cl, data)
308
309 # if there was a message in the data
310 if message:
311
312 # remove any spaces, tabs etc from the start and end of
313 # the message
314 message = message.strip()
315
316 # separate the message into the command (the first word)
317 # and its parameters (the rest of the message)
318 command, params = (message.split(" ", 1) + ["", ""])[:2]
319
320 # add a command occurence to the new events list with the
321 # player's id number, the command and its parameters
322 self._new_events.append((self._EVENT_COMMAND, id,
323 command.lower(), params))
324
325 # if there is a problem reading from the socket (e.g. the client
326 # has disconnected) a socket error will be raised
327 except socket.error:
328 self._handle_disconnect(id)
329
330 def _handle_disconnect(self, clid):
331
332 # remove the client from the clients map
333 del(self._clients[clid])
334
335 # add a 'player left' occurence to the new events list, with the
336 # player's id number
337 self._new_events.append((self._EVENT_PLAYER_LEFT, clid))
338
339 def _process_sent_data(self, client, data):
340
341 # the Telnet protocol allows special command codes to be inserted into
342 # messages. For our very simple server we don't need to response to
343 # any of these codes, but we must at least detect and skip over them
344 # so that we don't interpret them as text data.
345 # More info on the Telnet protocol can be found here:
346 # http://pcmicro.com/netfoss/telnet.html
347
348 # start with no message and in the normal state
349 message = None
350 state = self._READ_STATE_NORMAL
351
352 # go through the data a character at a time
353 for c in data:
354
355 # handle the character differently depending on the state we're in:
356
357 # normal state
358 if state == self._READ_STATE_NORMAL:
359
360 # if we received the special 'interpret as command' code,
361 # switch to 'command' state so that we handle the next
362 # character as a command code and not as regular text data
363 if ord(c) == self._TN_INTERPRET_AS_COMMAND:
364 state = self._READ_STATE_COMMAND
365
366 # if we get a newline character, this is the end of the
367 # message. Set 'message' to the contents of the buffer and
368 # clear the buffer
369 elif c == "\n":
370 message = client.buffer
371 client.buffer = ""
372
373 # some telnet clients send the characters as soon as the user
374 # types them. So if we get a backspace character, this is where
375 # the user has deleted a character and we should delete the
376 # last character from the buffer.
377 elif c == "\x08":
378 client.buffer = client.buffer[:-1]
379
380 # otherwise it's just a regular character - add it to the
381 # buffer where we're building up the received message
382 else:
383 client.buffer += c
384
385 # command state
386 elif state == self._READ_STATE_COMMAND:
387
388 # the special 'start of subnegotiation' command code indicates
389 # that the following characters are a list of options until
390 # we're told otherwise. We switch into 'subnegotiation' state
391 # to handle this
392 if ord(c) == self._TN_SUBNEGOTIATION_START:
393 state = self._READ_STATE_SUBNEG
394
395 # if the command code is one of the 'will', 'wont', 'do' or
396 # 'dont' commands, the following character will be an option
397 # code so we must remain in the 'command' state
398 elif ord(c) in (self._TN_WILL, self._TN_WONT, self._TN_DO,
399 self._TN_DONT):
400 state = self._READ_STATE_COMMAND
401
402 # for all other command codes, there is no accompanying data so
403 # we can return to 'normal' state.
404 else:
405 state = self._READ_STATE_NORMAL
406
407 # subnegotiation state
408 elif state == self._READ_STATE_SUBNEG:
409
410 # if we reach an 'end of subnegotiation' command, this ends the
411 # list of options and we can return to 'normal' state.
412 # Otherwise we must remain in this state
413 if ord(c) == self._TN_SUBNEGOTIATION_END:
414 state = self._READ_STATE_NORMAL
415
416 # return the contents of 'message' which is either a string or None
417 return message
1 {"name": "test", "room": "Room001", "inventory": null, "prompt": "> "}
...\ No newline at end of file ...\ No newline at end of file
File mode changed
1
2 # import datetime
3
4 class BaseRoom(object):
5
6 def __init__(self, title, description, exits, look_items):
7 self.title = title
8 self.description = description
9 self.exits = exits
10 self.look_items = look_items
11 # self.created = datetime.datetime.now()
12
13 def get_title(self):
14 return self.title
15
16 def get_description(self):
17 return self.description
18
19 def get_exits(self):
20 return self.exits
21
22 def get_look_items(self):
23 return self.look_items
1 from .baseroom import BaseRoom
2
3 class Room001(BaseRoom):
4
5 def __init__(self):
6
7 title = "Behind the bar"
8 description = "The back of the bar gives a full view of the tavern. The bar\r\n top is a large wooden plank thrown across some barrels."
9 exits = {"tavern": "Tavern"}
10 look_items = {
11 "bar": "The bar is a long wooden plank thrown over roughly hewn barrels.",
12 "barrel,barrels": "The old barrels bands are thick with oxidation and stained\r\n with the purple of spilled wine.",
13 "wooden,oak,plank": "An old solid oak plank that has a large number of chips\r\n and drink marks."
14 }
15
16 super(Room001, self).__init__(title, description, exits, look_items)
1 from .baseroom import BaseRoom
2
3 class Tavern(BaseRoom):
4
5 def __init__(self):
6
7 title = "Tavern"
8 description = "You're in a cozy tavern warmed by an open fire."
9 exits = {"outside": "Outside", "behind bar": "Room001"}
10 look_items = {
11 "bar": "The bar is a long wooden plank thrown over roughly hewn barrels.",
12 "barrel,barrels": "The old barrels bands are thick with oxidation and stained\r\n with the purple of spilled wine.",
13 "wooden,oak,plank": "An old solid oak plank that has a large number of chips\r\n and drink marks.",
14 "fire": "The fire crackles quietly in the corner providing a small amount of light and heat."
15 }
16
17 super(Tavern, self).__init__(title, description, exits, look_items)
1 #!/usr/bin/env python
2
3 """A simple Multi-User Dungeon (MUD) game. Players can talk to each
4 other, examine their surroundings and move between rooms.
5
6 Some ideas for things to try adding:
7 * More rooms to explore
8 * An 'emote' command e.g. 'emote laughs out loud' -> 'Mark laughs
9 out loud'
10 * A 'whisper' command for talking to individual players
11 * A 'shout' command for yelling to players in all rooms
12 * Items to look at in rooms e.g. 'look fireplace' -> 'You see a
13 roaring, glowing fire'
14 * Items to pick up e.g. 'take rock' -> 'You pick up the rock'
15 * Monsters to fight
16 * Loot to collect
17 * Saving players accounts between sessions
18 * A password login
19 * A shop from which to buy items
20
21 author: Mark Frimston - mfrimston@gmail.com
22 """
23
24 import time
25
26 # import the MUD server class
27 from mudserver import MudServer
28
29
30 # structure defining the rooms in the game. Try adding more rooms to the game!
31 rooms = {
32 "Tavern": {
33 "description": "You're in a cozy tavern warmed by an open fire.",
34 "exits": {"outside": "Outside"},
35 },
36 "Outside": {
37 "description": "You're standing outside a tavern. It's raining.",
38 "exits": {"inside": "Tavern"},
39 }
40 }
41
42 # stores the players in the game
43 players = {}
44
45 # start the server
46 mud = MudServer()
47
48 # main game loop. We loop forever (i.e. until the program is terminated)
49 while True:
50
51 # pause for 1/5 of a second on each loop, so that we don't constantly
52 # use 100% CPU time
53 time.sleep(0.2)
54
55 # 'update' must be called in the loop to keep the game running and give
56 # us up-to-date information
57 mud.update()
58
59 # go through any newly connected players
60 for id in mud.get_new_players():
61
62 # add the new player to the dictionary, noting that they've not been
63 # named yet.
64 # The dictionary key is the player's id number. We set their room to
65 # None initially until they have entered a name
66 # Try adding more player stats - level, gold, inventory, etc
67 players[id] = {
68 "name": None,
69 "room": None,
70 }
71
72 # send the new player a prompt for their name
73 mud.send_message(id, "What is your name?")
74
75 # go through any recently disconnected players
76 for id in mud.get_disconnected_players():
77
78 # if for any reason the player isn't in the player map, skip them and
79 # move on to the next one
80 if id not in players:
81 continue
82
83 # go through all the players in the game
84 for pid, pl in players.items():
85 # send each player a message to tell them about the diconnected
86 # player
87 mud.send_message(pid, "{} quit the game".format(
88 players[id]["name"]))
89
90 # remove the player's entry in the player dictionary
91 del(players[id])
92
93 # go through any new commands sent from players
94 for id, command, params in mud.get_commands():
95
96 # if for any reason the player isn't in the player map, skip them and
97 # move on to the next one
98 if id not in players:
99 continue
100
101 # if the player hasn't given their name yet, use this first command as
102 # their name and move them to the starting room.
103 if players[id]["name"] is None:
104
105 players[id]["name"] = command
106 players[id]["room"] = "Tavern"
107
108 # go through all the players in the game
109 for pid, pl in players.items():
110 # send each player a message to tell them about the new player
111 mud.send_message(pid, "{} entered the game".format(
112 players[id]["name"]))
113
114 # send the new player a welcome message
115 mud.send_message(id, "Welcome to the game, {}. ".format(
116 players[id]["name"])
117 + "Type 'help' for a list of commands. Have fun!")
118
119 # send the new player the description of their current room
120 mud.send_message(id, rooms[players[id]["room"]]["description"])
121
122 # each of the possible commands is handled below. Try adding new
123 # commands to the game!
124
125 # 'help' command
126 elif command == "help":
127
128 # send the player back the list of possible commands
129 mud.send_message(id, "Commands:")
130 mud.send_message(id, " say <message> - Says something out loud, "
131 + "e.g. 'say Hello'")
132 mud.send_message(id, " look - Examines the "
133 + "surroundings, e.g. 'look'")
134 mud.send_message(id, " go <exit> - Moves through the exit "
135 + "specified, e.g. 'go outside'")
136
137 # 'say' command
138 elif command == "say":
139
140 # go through every player in the game
141 for pid, pl in players.items():
142 # if they're in the same room as the player
143 if players[pid]["room"] == players[id]["room"]:
144 # send them a message telling them what the player said
145 mud.send_message(pid, "{} says: {}".format(
146 players[id]["name"], params))
147
148 # 'look' command
149 elif command == "look":
150
151 # store the player's current room
152 rm = rooms[players[id]["room"]]
153
154 # send the player back the description of their current room
155 mud.send_message(id, rm["description"])
156
157 playershere = []
158 # go through every player in the game
159 for pid, pl in players.items():
160 # if they're in the same room as the player
161 if players[pid]["room"] == players[id]["room"]:
162 # ... and they have a name to be shown
163 if players[pid]["name"] is not None:
164 # add their name to the list
165 playershere.append(players[pid]["name"])
166
167 # send player a message containing the list of players in the room
168 mud.send_message(id, "Players here: {}".format(
169 ", ".join(playershere)))
170
171 # send player a message containing the list of exits from this room
172 mud.send_message(id, "Exits are: {}".format(
173 ", ".join(rm["exits"])))
174
175 # 'go' command
176 elif command == "go":
177
178 # store the exit name
179 ex = params.lower()
180
181 # store the player's current room
182 rm = rooms[players[id]["room"]]
183
184 # if the specified exit is found in the room's exits list
185 if ex in rm["exits"]:
186
187 # go through all the players in the game
188 for pid, pl in players.items():
189 # if player is in the same room and isn't the player
190 # sending the command
191 if players[pid]["room"] == players[id]["room"] \
192 and pid != id:
193 # send them a message telling them that the player
194 # left the room
195 mud.send_message(pid, "{} left via exit '{}'".format(
196 players[id]["name"], ex))
197
198 # update the player's current room to the one the exit leads to
199 players[id]["room"] = rm["exits"][ex]
200 rm = rooms[players[id]["room"]]
201
202 # go through all the players in the game
203 for pid, pl in players.items():
204 # if player is in the same (new) room and isn't the player
205 # sending the command
206 if players[pid]["room"] == players[id]["room"] \
207 and pid != id:
208 # send them a message telling them that the player
209 # entered the room
210 mud.send_message(pid,
211 "{} arrived via exit '{}'".format(
212 players[id]["name"], ex))
213
214 # send the player a message telling them where they are now
215 mud.send_message(id, "You arrive at '{}'".format(
216 players[id]["room"]))
217
218 # the specified exit wasn't found in the current room
219 else:
220 # send back an 'unknown exit' message
221 mud.send_message(id, "Unknown exit '{}'".format(ex))
222
223 # some other, unrecognised command
224 else:
225 # send back an 'unknown command' message
226 mud.send_message(id, "Unknown command '{}'".format(command))
1 {"test": "value"}
...\ No newline at end of file ...\ No newline at end of file